Skip to content

Commit

Permalink
wip first stable
Browse files Browse the repository at this point in the history
  • Loading branch information
s-t-a-n committed Jun 21, 2024
1 parent 7b7d3db commit ed0c49d
Show file tree
Hide file tree
Showing 35 changed files with 70,657 additions and 70,009 deletions.
4 changes: 3 additions & 1 deletion src/kaskas/component.hpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#pragma once

#include "kaskas/io/stack.hpp"
#include "io/software_stack.hpp"
#include "kaskas/io/hardware_stack.hpp"
#include "kaskas/prompt/prompt.hpp"

#include <spine/eventsystem/eventsystem.hpp>
Expand All @@ -27,6 +28,7 @@ class Component : public EventHandler {
virtual void safe_shutdown(State state = State::SAFE) { assert(!"Virtual base function called"); }

virtual std::unique_ptr<prompt::RPCRecipe> rpc_recipe() { assert(!"Virtual base function called"); }
virtual void sideload_providers(io::VirtualStackFactory& ssf) { assert(!"Virtual base function called"); }

protected:
io::HardwareStack& _hws;
Expand Down
13 changes: 7 additions & 6 deletions src/kaskas/data_providers.hpp
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
#pragma once

namespace Providers {
enum ProvidersEnum {
// The names of the providers below are part of the prompt API
enum class DataProviders {
CLIMATE_TEMP, //
CLIMATE_HUMIDITY,
OUTSIDE_TEMP,
AMBIENT_TEMP,
CLOCK,
SOIL_MOISTURE,
SOIL_MOISTURE_SETPOINT,
CLIMATE_FAN,
HEATING_POWER,
HEATER_SURFACE_TEMP,
HEATING_SETPOINT,
HEATING_SURFACE_TEMP,
HEATING_SURFACE_FAN,
HEATING_ELEMENT,
HEATING_POWER_RELAY,
VIOLET_SPECTRUM,
BROAD_SPECTRUM,
PUMP,
SIZE
};
}
1 change: 0 additions & 1 deletion src/kaskas/events.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ enum class Events {
HeatingCycleCheck,
HeatingCycleStart,
HeatingCycleStop,
WaterLevelCheck,
WaterInjectCheck,
WaterInjectStart,
WaterInjectFollowUp,
Expand Down
224 changes: 115 additions & 109 deletions src/kaskas/io/controllers/heater.hpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#pragma once

#include "kaskas/io/stack.hpp"
#include "kaskas/io/hardware_stack.hpp"

#include <magic_enum/magic_enum.hpp>
#include <spine/controller/pid.hpp>
Expand All @@ -23,8 +23,8 @@ using spn::filter::EWMA;

// todo: put this in a configuration file

constexpr double MIN_OUTSIDE_TEMPERATURE = 5.0;
constexpr double MAX_OUTSIDE_TEMPERATURE = 45.0;
constexpr double MIN_AMBIENT_TEMPERATURE = 5.0;
constexpr double MAX_AMBIENT_TEMPERATURE = 45.0;

constexpr double MIN_INSIDE_TEMPERATURE = 8.0;
constexpr double MAX_INSIDE_TEMPERATURE = 40.0;
Expand All @@ -49,6 +49,108 @@ class Heater {

using Value = double;

class ThermalRunAway {
public:
// todo: make an autotuner to find limits of normal usage experimentally
struct Config {
// maximal error from setpoint in degrees that is allowed to happen for timewindow stable_timewindow until
// thermal runaway is considered
// double stable_hysteresis_c = 1.0;
time_s stable_timewindow = time_m(10);

// minimal change in degrees within timewindow changing_timewindow
double heating_minimal_rising_c = 0.1;
double heating_minimal_dropping_c = 0.01;
time_s heating_timewindow = time_m(20);

bool guard_stable_state = true;
bool guard_changing_state = true;
};

ThermalRunAway(const Config&& cfg)
: _cfg(std::move(cfg)), _current_state_duration(Timer{}), _heating_time_window(_cfg.heating_timewindow) {}

void update(State state, double temperature) {
if (state != _current_state) {
// heater changed states
_last_state = _current_state;
_current_state = state;
reset_timers();
_temperature_time_window = temperature;
return;
}

if (_current_state == State::HEATING) {
if (_cfg.guard_stable_state and _last_state == State::STEADY_STATE) {
// heater came out of steady state
if (time_s(_current_state_duration.timeSinceLast(false)) > _cfg.stable_timewindow) {
// heater went outside of steady state for too long
DBGF("expired: %is, timewindow: %i",
time_s(_current_state_duration.timeSinceLast(false)).printable(),
time_s(_cfg.stable_timewindow).printable())
DBG("Heater went outside of steady state for too long!");
_is_runaway = true;
}
return;
}

if (_cfg.guard_changing_state and _heating_time_window.expired()) {
if (temperature < _setpoint) {
// heater is trying to rise temperature
const auto error = temperature - _temperature_time_window;
if (error < _cfg.heating_minimal_rising_c) {
// temperature didnt rise fast enough
DBGF("Temperature didnt rise fast enough: delta:%.2fC", error);
_is_runaway = true;
}

_temperature_time_window = temperature;
}

if (temperature > _setpoint) {
// heater should be dropping temperature
const auto error = _temperature_time_window - temperature;
if (error < _cfg.heating_minimal_dropping_c) {
// temperature didnt drop fast enough
DBGF("temperature didnt drop fast enough, delta:%.2fC", error);
_is_runaway = true;
}
_temperature_time_window = temperature;
}
}
}
}

void adjust_setpoint(double setpoint) {
_setpoint = setpoint;
reset_timers();
_current_state = State::UNDEFINED;
_last_state = State::UNDEFINED;
}

bool is_runaway() const { return _is_runaway; };

protected:
void reset_timers() {
_current_state_duration.reset();
_heating_time_window.reset();
}

private:
const Config _cfg;

Timer _current_state_duration;
State _current_state = State::UNDEFINED;
State _last_state = State::UNDEFINED;

double _temperature_time_window = 0;
IntervalTimer _heating_time_window;

bool _is_runaway = false;
double _setpoint = 0.0;
};

public:
struct Config {
PID::Config pid_cfg;
double max_heater_setpoint = 40.0;
Expand All @@ -58,6 +160,8 @@ class Heater {
HardwareStack::Idx climate_temperature_idx;
HardwareStack::Idx heating_surface_temperature_idx;
HardwareStack::Idx heating_element_idx;

ThermalRunAway::Config climate_trp_cfg; // run away protection with regards to climate sensor
};

public:
Expand All @@ -67,7 +171,7 @@ class Heater {
_surface_temperature(_hws.analog_sensor(_cfg.heating_surface_temperature_idx)),
_climate_temperature(_hws.analog_sensor(_cfg.climate_temperature_idx)),
_temperature_source(&_surface_temperature), _update_interval(IntervalTimer(_cfg.pid_cfg.sample_interval)),
_runaway_tracker(ThermalRunAway::Config{}) {}
_climate_trp(std::move(_cfg.climate_trp_cfg)) {}

void initialize() {
_pid.initialize();
Expand All @@ -93,7 +197,7 @@ class Heater {
_heating_element.fade_to(guarded_setpoint(normalized_response));
update_state();

_runaway_tracker.update(state(), temperature());
_climate_trp.update(state(), _climate_temperature.value());
}
}

Expand Down Expand Up @@ -153,7 +257,7 @@ class Heater {

void set_setpoint(const Value setpoint) {
_pid.set_target_setpoint(std::min(_cfg.max_heater_setpoint, setpoint));
_runaway_tracker.adjust_setpoint(setpoint);
_climate_trp.adjust_setpoint(setpoint);
}
Value setpoint() const { return _pid.setpoint(); }

Expand Down Expand Up @@ -195,10 +299,10 @@ class Heater {
spn::throw_exception(spn::assertion_error("Heater: Climate temperature out of limits"));
}

if (_runaway_tracker.is_runaway()) {
DBGF("Runaway detected: surfaceT %.2f, climateT %.2f, throttle: %i/255, state: %s",
_surface_temperature.value(), _climate_temperature.value(), int(throttle() * 255),
std::string(as_stringview(state())).c_str());
if (_climate_trp.is_runaway()) {
HAL::printf("Runaway detected: surfaceT %.2f, climateT %.2f, throttle: %i/255, state: %s",
_surface_temperature.value(), _climate_temperature.value(), int(throttle() * 255),
std::string(as_stringview(state())).c_str());
spn::throw_exception(spn::assertion_error("Heater: Run away detected"));
}
}
Expand All @@ -225,104 +329,6 @@ class Heater {
_cooled_down_for.reset();
}

protected:
class ThermalRunAway {
public:
struct Config {
// maximal error from setpoint in degrees that is allowed to happen for timewindow stable_timewindow until
// thermal runaway is considered
// double stable_hysteresis_c = 1.0;
time_s stable_timewindow = time_m(10);

// minimal change in degrees within timewindow changing_timewindow
double heating_minimal_rising_c = 0.3;
double heating_minimal_dropping_c = 0.01;
time_s heating_timewindow = time_m(20);
};

ThermalRunAway(const Config&& cfg)
: _cfg(cfg), _current_state_duration(Timer{}), _heating_time_window(_cfg.heating_timewindow) {}

void update(State state, double temperature) {
if (state != _current_state) {
// heater changed states
_last_state = _current_state;
_current_state = state;
reset_tracker();
_temperature_time_window = temperature;
return;
}

if (_current_state == State::HEATING) {
if (_last_state == State::STEADY_STATE) {
// heater came out of steady state
// todo: investigate why certain < conditions misfire -> mismatch time_ms, time_s, time_m ?
if (time_s(_current_state_duration.timeSinceLast(false)) > _cfg.stable_timewindow) {
// heater went outside of steady state for too long
DBGF("expired: %is, timewindow: %i",
time_s(_current_state_duration.timeSinceLast(false)).printable(),
time_s(_cfg.stable_timewindow).printable())
DBG("heater went outside of steady state for too long");
_is_runaway = true;
}
return;
}

if (_heating_time_window.expired()) {
if (temperature < _setpoint) {
// heater is trying to rise temperature
const auto error = _temperature_time_window - temperature;
if (error < _cfg.heating_minimal_rising_c) {
// temperature didnt rise fast enough
DBG("temperature didnt rise fast enough");
_is_runaway = true;
}

_temperature_time_window = temperature;
}

if (temperature > _setpoint) {
// heater should be dropping temperature
const auto error = _temperature_time_window - temperature;
if (error > _cfg.heating_minimal_dropping_c) {
// temperature didnt drop fast enough
DBG("temperature didnt drop fast enough");
_is_runaway = true;
}
_temperature_time_window = temperature;
}
}
}
}

void adjust_setpoint(double setpoint) {
_setpoint = setpoint;
reset_tracker();
_last_state = State::UNDEFINED;
}

bool is_runaway() const { return _is_runaway; };

protected:
void reset_tracker() {
_current_state_duration.reset();
_heating_time_window.reset();
}

private:
const Config& _cfg;

Timer _current_state_duration;
State _current_state = State::UNDEFINED;
State _last_state = State::UNDEFINED;

double _temperature_time_window = 0;
IntervalTimer _heating_time_window;

bool _is_runaway = false;
double _setpoint = 0.0;
};

private:
const Config _cfg;
HardwareStack& _hws;
Expand All @@ -344,7 +350,7 @@ class Heater {

IntervalTimer _update_interval;

ThermalRunAway _runaway_tracker;
ThermalRunAway _climate_trp;
};

} // namespace kaskas::io
Loading

0 comments on commit ed0c49d

Please sign in to comment.