Skip to content

Commit

Permalink
some refactor of thermostat - previously we weren't clamping the temp…
Browse files Browse the repository at this point in the history
… in __init__
  • Loading branch information
CamDavidsonPilon committed Jul 29, 2024
1 parent 6f140e9 commit 9e2a746
Show file tree
Hide file tree
Showing 4 changed files with 60 additions and 12 deletions.
25 changes: 15 additions & 10 deletions pioreactor/automations/temperature/thermostat.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,23 +26,32 @@ class Thermostat(TemperatureAutomationJob):
def __init__(self, target_temperature: float | str, **kwargs) -> None:
super().__init__(**kwargs)
assert target_temperature is not None, "target_temperature must be set"
self.target_temperature = float(target_temperature)

self.pid = PID(
Kp=config.getfloat("temperature_automation.thermostat", "Kp"),
Ki=config.getfloat("temperature_automation.thermostat", "Ki"),
Kd=config.getfloat("temperature_automation.thermostat", "Kd"),
setpoint=self.target_temperature,
setpoint=None,
unit=self.unit,
experiment=self.experiment,
job_name=self.job_name,
target_name="temperature",
output_limits=(-25, 25), # avoid whiplashing
)

self.set_target_temperature(target_temperature)

if not is_pio_job_running("stirring"):
self.logger.warning("It's recommended to have stirring on when using the thermostat.")

def _clamp_target_temperature(self, target_temperature: float) -> float:
if target_temperature > self.MAX_TARGET_TEMP:
self.logger.warning(
f"Values over {self.MAX_TARGET_TEMP}℃ are not supported. Setting to {self.MAX_TARGET_TEMP}℃."
)

return clamp(0.0, target_temperature, self.MAX_TARGET_TEMP)

def execute(self) -> UpdatedHeaterDC:
while not hasattr(self, "pid"):
# sometimes when initializing, this execute can run before the subclasses __init__ is resolved.
Expand All @@ -61,10 +70,12 @@ def execute(self) -> UpdatedHeaterDC:
data={
"current_dc": self.heater_duty_cycle,
"delta_dc": output,
"target_temperature": self.target_temperature,
"latest_temperature": self.latest_temperature,
},
)

def set_target_temperature(self, target_temperature: float) -> None:
def set_target_temperature(self, target_temperature: float | str) -> None:
"""
Parameters
Expand All @@ -77,11 +88,5 @@ def set_target_temperature(self, target_temperature: float) -> None:
"""
target_temperature = float(target_temperature)
if target_temperature > self.MAX_TARGET_TEMP:
self.logger.warning(
f"Values over {self.MAX_TARGET_TEMP}℃ are not supported. Setting to {self.MAX_TARGET_TEMP}℃."
)

target_temperature = clamp(0, target_temperature, self.MAX_TARGET_TEMP)
self.target_temperature = target_temperature
self.target_temperature = self._clamp_target_temperature(target_temperature)
self.pid.set_setpoint(self.target_temperature)
2 changes: 2 additions & 0 deletions pioreactor/plugin_management/list_plugins.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
@click.command(name="list", short_help="list the installed plugins")
@click.option("--json", is_flag=True, help="output as json")
def click_list_plugins(json: bool) -> None:
# this is to initialize all the modules, to plugins don't fail when being loaded.
from pioreactor.cli import run # noqa: F403, F401
from pioreactor.plugin_management import get_plugins

if not json:
Expand Down
42 changes: 41 additions & 1 deletion pioreactor/tests/test_temperature_approximation_1_0.py
Original file line number Diff line number Diff line change
Expand Up @@ -897,7 +897,7 @@ def test_temperature_approximation17(self) -> None:

assert 25.295 <= self.t.approximate_temperature_1_0(features) <= 25.430

def test_temperature_approximation19(self) -> None:
def test_temperature_approximation21(self) -> None:
# this was real data from a user

ts_of_temps = [
Expand Down Expand Up @@ -944,3 +944,43 @@ def test_temperature_approximation19(self) -> None:
}

assert better_room_temp < self.t.approximate_temperature_1_0(features) <= 25

def test_temperature_approximation19(self) -> None:
# this was real data from a user
features = {
"previous_heater_dc": 1.3,
"room_temp": 22.0,
"time_series_of_temp": [
56.4375,
56.375,
56.3125,
56.302083333333336,
56.25,
56.25,
56.1875,
56.1875,
56.177083333333336,
56.135416666666664,
56.125,
56.125,
56.104166666666664,
56.083333333333336,
56.0625,
56.0625,
56.0625,
56.0625,
56.0625,
56.041666666666664,
56.052083333333336,
56.020833333333336,
56.0,
56.0,
56.0,
56.0,
56.0,
56.0,
56.0,
],
}

assert 55.5 <= self.t.approximate_temperature_1_0(features) <= 56.5
3 changes: 2 additions & 1 deletion pioreactor/utils/streaming_calculations.py
Original file line number Diff line number Diff line change
Expand Up @@ -428,7 +428,7 @@ def __init__(
Kp: float,
Ki: float,
Kd: float,
setpoint: float,
setpoint: float | None,
output_limits: tuple[Optional[float], Optional[float]] = (None, None),
sample_time: Optional[float] = None,
unit: Optional[str] = None,
Expand Down Expand Up @@ -478,6 +478,7 @@ def update(self, input_: float, dt: float = 1.0) -> float:
Updates the controller's internal state with the current error and time step,
and returns the controller output.
"""
assert isinstance(self.setpoint, float)

error = self.setpoint - input_
# Update error sum with clamping for anti-windup
Expand Down

0 comments on commit 9e2a746

Please sign in to comment.