Skip to content

Commit dd809f7

Browse files
add thermal control and related actions
1 parent 276e5f6 commit dd809f7

File tree

12 files changed

+393
-53
lines changed

12 files changed

+393
-53
lines changed

src/devices/eeprom.py

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ def __init__(self):
1313
The constructor function for the EEPROM class
1414
"""
1515
self._google_sheet_interval = 20
16-
self._heat = bool(True)
1716
self._kd_value = 36.0
1817
self._ki_value = 28.0
1918
self._kp_value = 20.0
@@ -27,14 +26,6 @@ def get_google_sheet_interval(self, default):
2726
return default
2827
return self._google_sheet_interval
2928

30-
def get_heat(self, default):
31-
"""
32-
Get the heat setting from EEPROM
33-
"""
34-
if self._heat is None:
35-
return default
36-
return self._heat
37-
3829
def get_kd(self, default):
3930
"""
4031
Get the Kd value from EEPROM
@@ -73,12 +64,6 @@ def set_google_sheet_interval(self, value):
7364
"""
7465
self._google_sheet_interval = value
7566

76-
def set_heat(self, value):
77-
"""
78-
Set the heat setting in EEPROM
79-
"""
80-
self._heat = value
81-
8267
def set_kd(self, value):
8368
"""
8469
Set the Kd value in EEPROM

src/devices/thermal_control.py

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
"""
2+
A file for the ThermalControl class
3+
"""
4+
5+
import time
6+
7+
8+
class ThermalControl:
9+
"""
10+
The class for the ThermalControl
11+
"""
12+
13+
FLAT_TYPE = 0
14+
RAMP_TYPE = 1
15+
SINE_TYPE = 2
16+
17+
def __init__(self, titrator):
18+
"""
19+
The constructor function for the ThermalControl class
20+
"""
21+
self.titrator = titrator
22+
self._heat = bool(True)
23+
self._base_thermal_target = 78
24+
self._current_thermal_target = 67
25+
self._thermal_function_type = ThermalControl.FLAT_TYPE
26+
self._ramp_time_start_seconds = 0
27+
self._ramp_time_end_seconds = 0
28+
self._ramp_initial_value = 0.0
29+
30+
def get_heat(self, default):
31+
"""
32+
Get the heat setting from EEPROM
33+
"""
34+
if self._heat is None:
35+
return default
36+
return self._heat
37+
38+
def set_heat(self, value):
39+
"""
40+
Set the heat setting in EEPROM
41+
"""
42+
self._heat = value
43+
44+
def get_base_thermal_target(self):
45+
"""
46+
Get the base thermal target
47+
"""
48+
return self._base_thermal_target
49+
50+
def set_base_thermal_target(self, value):
51+
"""
52+
Set the base thermal target
53+
"""
54+
self._base_thermal_target = value
55+
56+
def get_current_thermal_target(self):
57+
"""
58+
Get the current thermal target
59+
"""
60+
return self._current_thermal_target
61+
62+
def set_current_thermal_target(self, value):
63+
"""
64+
Set the current thermal target
65+
"""
66+
self._current_thermal_target = value
67+
68+
def get_thermal_function_type(self):
69+
"""
70+
Get the current thermal function type.
71+
"""
72+
return self._thermal_function_type
73+
74+
def set_thermal_function_type(self, function_type):
75+
"""
76+
Set the current thermal function type.
77+
"""
78+
if function_type in (
79+
ThermalControl.FLAT_TYPE,
80+
ThermalControl.RAMP_TYPE,
81+
ThermalControl.SINE_TYPE,
82+
):
83+
self._thermal_function_type = function_type
84+
else:
85+
raise ValueError("Invalid thermal function type")
86+
87+
def get_ramp_time_start(self):
88+
"""
89+
Get the ramp time start in seconds.
90+
"""
91+
return (
92+
self._ramp_time_start_seconds
93+
if self._thermal_function_type != ThermalControl.FLAT_TYPE
94+
else 0
95+
)
96+
97+
def get_ramp_time_end(self):
98+
"""
99+
Get the ramp time end in seconds.
100+
"""
101+
return (
102+
self._ramp_time_end_seconds
103+
if self._thermal_function_type != ThermalControl.FLAT_TYPE
104+
else 0
105+
)
106+
107+
def set_ramp_duration_hours(self, new_ph_ramp_duration):
108+
"""
109+
Set the ramp duration in hours. If the duration is greater than 0, configure ramp parameters;
110+
otherwise, set the function type to FLAT_TYPE.
111+
"""
112+
if new_ph_ramp_duration > 0:
113+
current_ramp_time = (
114+
self._ramp_time_end_seconds - self._ramp_time_start_seconds
115+
)
116+
current_ramp_time_str = f"{current_ramp_time:.3f}"
117+
new_ramp_duration_str = f"{new_ph_ramp_duration:.3f}"
118+
print(
119+
f"Change ramp time from {current_ramp_time_str} to {new_ramp_duration_str}"
120+
)
121+
122+
self._ramp_time_start_seconds = int(time.monotonic())
123+
self._ramp_time_end_seconds = self._ramp_time_start_seconds + int(
124+
new_ph_ramp_duration * 3600
125+
)
126+
127+
self._ramp_initial_value = self.titrator.thermal_probe.get_running_average()
128+
self._thermal_function_type = ThermalControl.RAMP_TYPE
129+
else:
130+
self._ramp_time_end_seconds = 0
131+
self._thermal_function_type = ThermalControl.FLAT_TYPE
132+
print("Set ramp time to 0")

src/devices/thermal_probe.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,16 @@
22
The file for the ThermalProbe class
33
"""
44

5+
import time
6+
57

68
class ThermalProbe:
79
"""
810
The class for the ThermalProbe
911
"""
1012

13+
HISTORY_SIZE = 10
14+
1115
def __init__(self, eeprom):
1216
"""
1317
The constructor function for the ThermalProbe class
@@ -16,6 +20,11 @@ def __init__(self, eeprom):
1620

1721
self._correction = 12
1822

23+
self.history = [0.0] * self.HISTORY_SIZE
24+
self.history_index = 0
25+
self.first_time = True
26+
self.last_time = 0
27+
1928
def get_thermal_correction(self):
2029
"""
2130
Get the thermal correction value from EEPROM
@@ -33,3 +42,45 @@ def set_thermal_correction(self, value):
3342
Set the thermal correction value in EEPROM
3443
"""
3544
self._correction = value
45+
46+
def get_uncorrected_running_average(self):
47+
"""
48+
Calculate the uncorrected running average of temperature readings.
49+
"""
50+
current_time = time.time() # Get current time in seconds
51+
if (
52+
self.first_time or self.last_time + 1 <= current_time
53+
): # Check if 1 second has passed
54+
temperature = self.get_raw_temperature()
55+
if self.first_time:
56+
# Initialize the history buffer with the first temperature reading
57+
self.history = [temperature] * self.HISTORY_SIZE
58+
self.first_time = False
59+
60+
# Update the history buffer with the new temperature reading
61+
self.history_index = (self.history_index + 1) % self.HISTORY_SIZE
62+
self.history[self.history_index] = temperature
63+
self.last_time = current_time
64+
65+
# Calculate the average of the history buffer, ignoring unused slots
66+
valid_readings = self.history[: self.history_index + 1]
67+
return sum(valid_readings) / len(valid_readings)
68+
69+
def get_running_average(self):
70+
"""
71+
Return the corrected running average within the range of 00.00-99.99
72+
"""
73+
temperature = self.get_uncorrected_running_average() + self._correction
74+
if temperature < 0.0:
75+
temperature = 0.0
76+
elif temperature > 99.99:
77+
temperature = 99.99
78+
return temperature
79+
80+
def get_raw_temperature(self):
81+
"""
82+
Simulate reading the raw temperature from a sensor.
83+
In a real implementation, this method would interface with hardware.
84+
"""
85+
# Placeholder for actual sensor reading logic
86+
return 25.0 # return thermo.temperature(RTDnominal, refResistor);

src/titrator.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
from src.devices.ph_control import PHControl
2020
from src.devices.pid import PID
2121
from src.devices.sd import SD
22+
from src.devices.thermal_control import ThermalControl
2223
from src.devices.thermal_probe import ThermalProbe
2324
from src.ui_state.main_menu import MainMenu
2425
from src.version import VERSION
@@ -51,6 +52,9 @@ def __init__(self):
5152
# Initialize PH Control
5253
self.ph_control = PHControl()
5354

55+
# Initialize Thermal Control
56+
self.thermal_control = ThermalControl(self)
57+
5458
# Initialize Thermal Probe
5559
self.thermal_probe = ThermalProbe(self.eeprom)
5660

src/ui_state/set_menu/set_chill_or_heat.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ def loop(self):
2121
"""
2222
self.titrator.lcd.print("1:Chill; 9:Heat", line=1)
2323

24-
if self.titrator.eeprom.get_heat(True):
24+
if self.titrator.thermal_control.get_heat(True):
2525
self.titrator.lcd.print("Currently: Heat", line=2)
2626
else:
2727
self.titrator.lcd.print("Currently: Chill", line=2)
@@ -31,17 +31,17 @@ def handle_key(self, key):
3131
Handle key presses to return to the previous state.
3232
"""
3333
if key == Keypad.KEY_1:
34-
self.titrator.eeprom.set_heat(False)
34+
self.titrator.thermal_control.set_heat(False)
3535
self.titrator.lcd.print("Use chiller", line=2)
3636
self.return_to_main_menu(ms_delay=3000)
3737

3838
if key == Keypad.KEY_9:
39-
self.titrator.eeprom.set_heat(True)
39+
self.titrator.thermal_control.set_heat(True)
4040
self.titrator.lcd.print("Use heater", line=2)
4141
self.return_to_main_menu(ms_delay=3000)
4242

4343
if key == Keypad.KEY_A:
44-
if self.titrator.eeprom.get_heat(True):
44+
if self.titrator.thermal_control.get_heat(True):
4545
self.titrator.lcd.print("Use heater", line=2)
4646
else:
4747
self.titrator.lcd.print("Use chiller", line=2)

src/ui_state/set_menu/set_thermal_calibration.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ class SetThermalCalibration(UserValue):
99
"""
1010
This is a class for the SetThermalCalibration state of the Tank Controller
1111
"""
12+
1213
def __init__(self, titrator, previous_state=None):
1314
super().__init__(titrator, previous_state)
1415
self.previous_state = previous_state
@@ -25,5 +26,8 @@ def save_value(self):
2526
Saves the thermal calibration value to the thermal probe.
2627
"""
2728
self.titrator.thermal_probe.set_thermal_correction(self.value)
28-
self.titrator.lcd.print(f"New correction={self.titrator.thermal_probe.get_thermal_correction()}", line=2)
29+
self.titrator.lcd.print(
30+
f"New correction={self.titrator.thermal_probe.get_thermal_correction()}",
31+
line=2,
32+
)
2933
self.return_to_main_menu(ms_delay=3000)

src/ui_state/set_menu/set_thermal_target.py

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,54 @@
22
The file to hold the Set Thermal Target class
33
"""
44

5-
from src.ui_state.ui_state import UIState
5+
from src.ui_state.user_value import UserValue
66

77

8-
class SetThermalTarget(UIState):
8+
class SetThermalTarget(UserValue):
99
"""
1010
This is a class for the SetThermalTarget state of the Tank Controller
1111
"""
12+
13+
def __init__(self, titrator, previous_state=None):
14+
"""
15+
Constructor for the SetThermalTarget class
16+
"""
17+
super().__init__(titrator, previous_state)
18+
self.previous_state = previous_state
19+
self.prompts = ["Set Temperature", "Set ramp hours:"]
20+
self.values = [0.0] * 2
21+
self.sub_state = 0
22+
23+
def get_label(self):
24+
"""
25+
Returns the label for the user value input.
26+
"""
27+
return self.prompts[self.sub_state]
28+
29+
def save_value(self):
30+
"""
31+
Saves the entered value for the current sub-state and advances to the next sub-state.
32+
"""
33+
self.values[self.sub_state] = float(self.value)
34+
self.sub_state += 1
35+
36+
if self.sub_state < len(self.values):
37+
self.value = ""
38+
else:
39+
self.titrator.thermal_control.set_base_thermal_target(self.values[0])
40+
self.titrator.thermal_control.set_ramp_duration_hours(self.values[1])
41+
42+
self.titrator.lcd.print(f"New Temp={self.values[0]:.2f}", line=1)
43+
self.titrator.lcd.print(f"New Ramp={self.values[1]:.2f}", line=2)
44+
45+
self.return_to_main_menu(ms_delay=3000)
46+
47+
def handle_key(self, key):
48+
"""
49+
Handles key presses and updates the display accordingly.
50+
"""
51+
if key == "A" and self.value not in ("", "."):
52+
self.save_value()
53+
self.value = ""
54+
else:
55+
super().handle_key(key)

src/ui_state/view_menu/view_thermal_correction.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,9 @@ def loop(self):
2323
The loop function for the ViewThermalCorrection class
2424
"""
2525
self.titrator.lcd.print("Temp Cal Offset:", line=1)
26-
self.titrator.lcd.print(f"{self.titrator.thermal_probe.get_thermal_correction()}", line=2)
26+
self.titrator.lcd.print(
27+
f"{self.titrator.thermal_probe.get_thermal_correction()}", line=2
28+
)
2729

2830
def handle_key(self, key):
2931
"""

0 commit comments

Comments
 (0)