Skip to content

Commit 0d1e025

Browse files
Thermal device features (#36)
* develop thermal probe actions branch * Add set calibration and clear * add thermal control and related actions * add set therm sine and make thermal related tests * add thermal line 2 to Idle * fix thermal main menu test * format * reorder device functions alphabetically and new view temp feature * format
1 parent c538cda commit 0d1e025

23 files changed

Lines changed: 1023 additions & 110 deletions

src/devices/eeprom.py

Lines changed: 0 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,10 @@ 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
2019
self._tank_id = 1
21-
self._thermal_correction = 12
2220

2321
def get_google_sheet_interval(self, default):
2422
"""
@@ -28,14 +26,6 @@ def get_google_sheet_interval(self, default):
2826
return default
2927
return self._google_sheet_interval
3028

31-
def get_heat(self, default):
32-
"""
33-
Get the heat setting from EEPROM
34-
"""
35-
if self._heat is None:
36-
return default
37-
return self._heat
38-
3929
def get_kd(self, default):
4030
"""
4131
Get the Kd value from EEPROM
@@ -68,26 +58,12 @@ def get_tank_id(self, default):
6858
return default
6959
return self._tank_id
7060

71-
def get_thermal_correction(self, default):
72-
"""
73-
Get the thermal correction value from EEPROM
74-
"""
75-
if self._thermal_correction is None:
76-
return default
77-
return float(self._thermal_correction)
78-
7961
def set_google_sheet_interval(self, value):
8062
"""
8163
Set the google sheet interval in EEPROM
8264
"""
8365
self._google_sheet_interval = value
8466

85-
def set_heat(self, value):
86-
"""
87-
Set the heat setting in EEPROM
88-
"""
89-
self._heat = value
90-
9167
def set_kd(self, value):
9268
"""
9369
Set the Kd value in EEPROM

src/devices/thermal_control.py

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
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+
self._amplitude = 0.0
30+
self._period_in_seconds = 0
31+
32+
def get_amplitude(self):
33+
"""
34+
Get the amplitude for the pH function.
35+
"""
36+
return self._amplitude
37+
38+
def get_base_thermal_target(self):
39+
"""
40+
Get the base thermal target
41+
"""
42+
return self._base_thermal_target
43+
44+
def get_current_thermal_target(self):
45+
"""
46+
Get the current thermal target
47+
"""
48+
return self._current_thermal_target
49+
50+
def get_heat(self, default):
51+
"""
52+
Get the heat setting from EEPROM
53+
"""
54+
if self._heat is None:
55+
return default
56+
return self._heat
57+
58+
def get_period_in_seconds(self):
59+
"""
60+
Get the period in seconds for the pH function.
61+
"""
62+
return self._period_in_seconds
63+
64+
def get_ramp_time_end(self):
65+
"""
66+
Get the ramp time end in seconds.
67+
"""
68+
return (
69+
self._ramp_time_end_seconds
70+
if self._thermal_function_type != ThermalControl.FLAT_TYPE
71+
else 0
72+
)
73+
74+
def get_ramp_time_start(self):
75+
"""
76+
Get the ramp time start in seconds.
77+
"""
78+
return (
79+
self._ramp_time_start_seconds
80+
if self._thermal_function_type != ThermalControl.FLAT_TYPE
81+
else 0
82+
)
83+
84+
def get_thermal_function_type(self):
85+
"""
86+
Get the current thermal function type.
87+
"""
88+
return self._thermal_function_type
89+
90+
def set_amplitude(self, amplitude):
91+
"""
92+
Set the amplitude for the pH function.
93+
"""
94+
self._amplitude = amplitude
95+
96+
def set_base_thermal_target(self, value):
97+
"""
98+
Set the base thermal target
99+
"""
100+
self._base_thermal_target = value
101+
102+
def set_current_thermal_target(self, value):
103+
"""
104+
Set the current thermal target
105+
"""
106+
self._current_thermal_target = value
107+
108+
def set_heat(self, value):
109+
"""
110+
Set the heat setting in EEPROM
111+
"""
112+
self._heat = value
113+
114+
def set_ramp_duration_hours(self, new_ph_ramp_duration):
115+
"""
116+
Set the ramp duration in hours. If the duration is greater than 0, configure ramp parameters;
117+
otherwise, set the function type to FLAT_TYPE.
118+
"""
119+
if new_ph_ramp_duration > 0:
120+
current_ramp_time = (
121+
self._ramp_time_end_seconds - self._ramp_time_start_seconds
122+
)
123+
current_ramp_time_str = f"{current_ramp_time:.3f}"
124+
new_ramp_duration_str = f"{new_ph_ramp_duration:.3f}"
125+
print(
126+
f"Change ramp time from {current_ramp_time_str} to {new_ramp_duration_str}"
127+
)
128+
129+
self._ramp_time_start_seconds = int(time.monotonic())
130+
self._ramp_time_end_seconds = self._ramp_time_start_seconds + int(
131+
new_ph_ramp_duration * 3600
132+
)
133+
134+
self._ramp_initial_value = self.titrator.thermal_probe.get_running_average()
135+
self._thermal_function_type = ThermalControl.RAMP_TYPE
136+
else:
137+
self._ramp_time_end_seconds = 0
138+
self._thermal_function_type = ThermalControl.FLAT_TYPE
139+
print("Set ramp time to 0")
140+
141+
def set_sine_amplitude_and_hours(self, amplitude, period_in_hours):
142+
"""
143+
Set the amplitude and period (in hours) for the sine wave pH function.
144+
"""
145+
if amplitude > 0 and period_in_hours > 0:
146+
self._amplitude = amplitude
147+
self._period_in_seconds = int(period_in_hours * 3600)
148+
self._thermal_function_type = ThermalControl.SINE_TYPE
149+
else:
150+
raise ValueError("Amp and period !> than 0.")
151+
152+
def set_thermal_function_type(self, function_type):
153+
"""
154+
Set the current thermal function type.
155+
"""
156+
if function_type in (
157+
ThermalControl.FLAT_TYPE,
158+
ThermalControl.RAMP_TYPE,
159+
ThermalControl.SINE_TYPE,
160+
):
161+
self._thermal_function_type = function_type
162+
else:
163+
raise ValueError("Invalid thermal function type")

src/devices/thermal_probe.py

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,85 @@
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
1418
"""
1519
self.eeprom = eeprom
1620

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

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/main_menu.py

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
The file for the MainMenu class
33
"""
44

5+
import time
6+
57
from src.devices.library import Keypad
68
from src.ui_state.set_menu.set_chill_or_heat import SetChillOrHeat
79
from src.ui_state.set_menu.set_google_mins import SetGoogleSheetInterval
@@ -31,6 +33,7 @@
3133
from src.ui_state.view_menu.view_ph_calibration import ViewPHCalibration
3234
from src.ui_state.view_menu.view_pid_constants import ViewPIDConstants
3335
from src.ui_state.view_menu.view_tank_id import ViewTankID
36+
from src.ui_state.view_menu.view_thermal import ViewThermal
3437
from src.ui_state.view_menu.view_thermal_correction import (
3538
ViewThermalCorrection,
3639
)
@@ -59,6 +62,7 @@ def __init__(self, titrator):
5962
"View pH slope",
6063
"View PID",
6164
"View tank ID",
65+
"View temp",
6266
"View temp cal",
6367
"View time",
6468
"View version",
@@ -93,6 +97,7 @@ def __init__(self, titrator):
9397
ViewPHCalibration, # View pH slope
9498
ViewPIDConstants, # View PID constants
9599
ViewTankID, # View Tank ID
100+
ViewThermal, # View Temperature
96101
ViewThermalCorrection, # View Thermal Correction
97102
ViewTime, # View Time
98103
ViewVersion, # View Version
@@ -220,7 +225,27 @@ def idle(self):
220225
"""
221226
lcd = self.titrator.lcd
222227
lcd.print("Idle Line 1", line=1)
223-
lcd.print("Idle Line 2", line=2)
228+
229+
thermal_control = self.titrator.thermal_control
230+
temperature = self.titrator.thermal_probe.get_running_average()
231+
status = "h" if thermal_control.get_heat(True) else "c"
232+
233+
output = [" "] * 20
234+
output[0] = "T"
235+
output[1] = "=" if int(time.monotonic()) % 2 == 0 else " "
236+
237+
buffer = f"{temperature:5.2f}"
238+
output[2:7] = list(buffer[:5])
239+
240+
output[7] = " "
241+
output[8] = status
242+
output[9] = " "
243+
244+
thermal_target = thermal_control.get_current_thermal_target()
245+
buffer = f"{thermal_target:5.2f}"
246+
output[10:15] = list(buffer[:5])
247+
248+
self.titrator.lcd.print("".join(output), line=2)
224249

225250
def loop(self):
226251
"""

0 commit comments

Comments
 (0)