Skip to content

Commit dd20410

Browse files
add set therm sine and make thermal related tests
1 parent dd809f7 commit dd20410

File tree

8 files changed

+415
-2
lines changed

8 files changed

+415
-2
lines changed

src/devices/thermal_control.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ def __init__(self, titrator):
2626
self._ramp_time_start_seconds = 0
2727
self._ramp_time_end_seconds = 0
2828
self._ramp_initial_value = 0.0
29+
self._amplitude = 0.0
30+
self._period_in_seconds = 0
2931

3032
def get_heat(self, default):
3133
"""
@@ -130,3 +132,32 @@ def set_ramp_duration_hours(self, new_ph_ramp_duration):
130132
self._ramp_time_end_seconds = 0
131133
self._thermal_function_type = ThermalControl.FLAT_TYPE
132134
print("Set ramp time to 0")
135+
136+
def get_amplitude(self):
137+
"""
138+
Get the amplitude for the pH function.
139+
"""
140+
return self._amplitude
141+
142+
def set_amplitude(self, amplitude):
143+
"""
144+
Set the amplitude for the pH function.
145+
"""
146+
self._amplitude = amplitude
147+
148+
def get_period_in_seconds(self):
149+
"""
150+
Get the period in seconds for the pH function.
151+
"""
152+
return self._period_in_seconds
153+
154+
def set_sine_amplitude_and_hours(self, amplitude, period_in_hours):
155+
"""
156+
Set the amplitude and period (in hours) for the sine wave pH function.
157+
"""
158+
if amplitude > 0 and period_in_hours > 0:
159+
self._amplitude = amplitude
160+
self._period_in_seconds = int(period_in_hours * 3600)
161+
self._thermal_function_type = ThermalControl.SINE_TYPE
162+
else:
163+
raise ValueError("Amp and period !> than 0.")

src/ui_state/set_menu/set_thermal_sine_wave.py

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,57 @@
22
The file to hold the Set Thermal Sine Wave 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 SetThermalSineWave(UIState):
8+
class SetThermalSineWave(UserValue):
99
"""
1010
This is a class for the SetThermalSineWave 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 T Set Point", "Set Amplitude:", "Set Period hrs:"]
20+
self.values = [0.0] * 3
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_sine_amplitude_and_hours(
41+
self.values[1], self.values[2]
42+
)
43+
temperature = f"New Temp={self.values[0]:.2f}"
44+
amplitude_and_period = f"A={self.values[1]:.2f} P={self.values[2]:.3f}"
45+
self.titrator.lcd.print(temperature, line=1)
46+
self.titrator.lcd.print(amplitude_and_period, line=2)
47+
48+
self.return_to_main_menu(ms_delay=3000)
49+
50+
def handle_key(self, key):
51+
"""
52+
Handles key presses and updates the display accordingly.
53+
"""
54+
if key == "A" and self.value not in ("", "."):
55+
self.save_value()
56+
self.value = ""
57+
else:
58+
super().handle_key(key)

tests/devices/thermal_control_test.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,9 @@ def test_get_and_set_thermal_function_type():
6868
thermal_control.set_thermal_function_type(ThermalControl.RAMP_TYPE)
6969
assert thermal_control.get_thermal_function_type() == ThermalControl.RAMP_TYPE
7070

71+
thermal_control.set_thermal_function_type(ThermalControl.SINE_TYPE)
72+
assert thermal_control.get_thermal_function_type() == ThermalControl.SINE_TYPE
73+
7174
try:
7275
thermal_control.set_thermal_function_type(99)
7376
except ValueError as err:
@@ -90,3 +93,21 @@ def test_set_ramp_duration_hours():
9093
thermal_control.set_ramp_duration_hours(0)
9194
assert thermal_control.get_ramp_time_end() == 0
9295
assert thermal_control.get_thermal_function_type() == ThermalControl.FLAT_TYPE
96+
97+
98+
def test_set_sine_amplitude_and_hours():
99+
"""
100+
Test setting the sine amplitude and period in hours.
101+
"""
102+
mock_titrator = Mock()
103+
thermal_control = ThermalControl(mock_titrator)
104+
105+
thermal_control.set_sine_amplitude_and_hours(1.5, 4)
106+
assert thermal_control.get_amplitude() == 1.5
107+
assert thermal_control.get_period_in_seconds() == 14400
108+
assert thermal_control.get_thermal_function_type() == ThermalControl.SINE_TYPE
109+
110+
try:
111+
thermal_control.set_sine_amplitude_and_hours(-1, 4)
112+
except ValueError as err:
113+
assert str(err) == "Amp and period !> than 0."
File renamed without changes.
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
"""
2+
The file to test the ResetPHCalibration class
3+
"""
4+
5+
from unittest import mock
6+
7+
from src.devices.library import LiquidCrystal
8+
from src.titrator import Titrator
9+
from src.ui_state.main_menu import MainMenu
10+
from src.ui_state.set_menu.set_thermal_calibration_clear import (
11+
ResetThermalCalibration,
12+
)
13+
14+
15+
@mock.patch.object(LiquidCrystal, "print")
16+
def test_reset_ph_calibration(print_mock):
17+
"""
18+
Test that pressing 'A' clears pH calibration and transitions to ViewPHCalibration.
19+
"""
20+
titrator = Titrator()
21+
state = ResetThermalCalibration(titrator, MainMenu(titrator))
22+
titrator.thermal_probe.clear_thermal_correction = mock.Mock()
23+
24+
state.loop()
25+
print_mock.assert_any_call("A: Clear TempCal", line=1)
26+
27+
state.handle_key("A")
28+
titrator.thermal_probe.clear_thermal_correction.assert_called_once()
29+
print_mock.assert_any_call("Cleared TempCali", line=1)
30+
31+
assert isinstance(titrator.state.next_state, MainMenu)
32+
33+
34+
def test_set_calibration_get_label():
35+
"""
36+
Test the label returned by get_label.
37+
"""
38+
titrator = Titrator()
39+
state = ResetThermalCalibration(titrator, MainMenu(titrator))
40+
41+
assert state.get_label() == "A: Clear TempCal"
42+
43+
44+
def test_handle_key_d():
45+
"""
46+
The function to test the reset handle keys
47+
"""
48+
titrator = Titrator()
49+
titrator.state = ResetThermalCalibration(titrator, MainMenu(titrator))
50+
51+
titrator.state.handle_key("D")
52+
assert isinstance(titrator.state, MainMenu)
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
"""
2+
The file to test the SetThermalCalibration class
3+
"""
4+
5+
from unittest import mock
6+
7+
from src.devices.library import LiquidCrystal
8+
from src.titrator import Titrator
9+
from src.ui_state.main_menu import MainMenu
10+
from src.ui_state.set_menu.set_thermal_calibration import SetThermalCalibration
11+
12+
13+
@mock.patch.object(LiquidCrystal, "print")
14+
def test_set_thermal_calibration_valid_input(print_mock):
15+
"""
16+
Unittest that entering a valid tank ID sets EEPROM and shows confirmation.
17+
"""
18+
titrator = Titrator()
19+
titrator.thermal_probe.set_thermal_correction(2.5)
20+
state = SetThermalCalibration(titrator, MainMenu(titrator))
21+
assert state.value == "2.5"
22+
23+
state.save_value()
24+
assert titrator.thermal_probe.get_thermal_correction() == 2.5
25+
print_mock.assert_any_call("New correction=2.5", line=2)
26+
27+
assert isinstance(titrator.state.next_state, MainMenu)
28+
29+
30+
@mock.patch.object(LiquidCrystal, "print")
31+
def test_user_thermal_calibration_string_input(print_mock):
32+
"""
33+
Test entering a tank ID value through the UserValue interface.
34+
"""
35+
titrator = Titrator()
36+
state = SetThermalCalibration(titrator, MainMenu(titrator))
37+
38+
state.handle_key("C")
39+
assert state.value == ""
40+
state.handle_key("1")
41+
assert state.value == "1"
42+
state.handle_key("0")
43+
assert state.value == "10"
44+
state.handle_key("A")
45+
46+
assert titrator.thermal_probe.get_thermal_correction() == 10
47+
print_mock.assert_any_call("New correction=10.0", line=2)
48+
49+
assert isinstance(titrator.state.next_state, MainMenu)
50+
51+
52+
def test_set_thermal_calibration_get_label():
53+
"""
54+
Test the label returned by get_label.
55+
"""
56+
titrator = Titrator()
57+
state = SetThermalCalibration(titrator, MainMenu(titrator))
58+
59+
assert state.get_label() == "Real Temperature"
60+
61+
62+
def test_handle_key_d():
63+
"""
64+
The function to test the reset handle keys
65+
"""
66+
titrator = Titrator()
67+
titrator.state = SetThermalCalibration(titrator, MainMenu(titrator))
68+
69+
titrator.state.handle_key("D")
70+
assert isinstance(titrator.state, MainMenu)
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
"""
2+
Test file for the Set Thermal Sine Wave UI state
3+
"""
4+
5+
from unittest import mock
6+
7+
from src.devices.library import LiquidCrystal
8+
from src.titrator import Titrator
9+
from src.ui_state.main_menu import MainMenu
10+
from src.ui_state.set_menu.set_thermal_sine_wave import SetThermalSineWave
11+
12+
13+
@mock.patch.object(LiquidCrystal, "print")
14+
def test_set_thermal_sine_wave_handle_key(print_mock):
15+
"""
16+
Test that handle_key processes key presses correctly.
17+
"""
18+
titrator = Titrator()
19+
state = SetThermalSineWave(titrator, MainMenu(titrator))
20+
21+
state.loop()
22+
print_mock.assert_any_call("Set T Set Point", line=1)
23+
24+
state.handle_key("7")
25+
state.handle_key("A")
26+
27+
assert state.sub_state == 1
28+
assert state.values[0] == 7
29+
30+
state.loop()
31+
print_mock.assert_any_call("Set Amplitude:", line=1)
32+
print_mock.assert_any_call("", style="center", line=2)
33+
34+
35+
def test_set_thermal_sine_wave_advances_substates():
36+
"""
37+
Test that the state advances through substates correctly.
38+
"""
39+
titrator = Titrator()
40+
state = SetThermalSineWave(titrator, MainMenu(titrator))
41+
42+
state.value = "7.5"
43+
state.save_value()
44+
assert state.sub_state == 1
45+
state.value = "2.0"
46+
state.save_value()
47+
assert state.sub_state == 2
48+
state.value = "24.0"
49+
state.save_value()
50+
assert state.sub_state == 3
51+
52+
53+
@mock.patch.object(LiquidCrystal, "print")
54+
def test_set_thermal_sine_wave_valid_input(print_mock):
55+
"""
56+
Test that valid pH mean, amplitude, and period inputs are saved and displayed correctly.
57+
"""
58+
titrator = Titrator()
59+
titrator.thermal_control = mock.Mock()
60+
state = SetThermalSineWave(titrator, MainMenu(titrator))
61+
62+
state.value = "7.5"
63+
state.save_value()
64+
state.value = "2.0"
65+
state.save_value()
66+
state.value = "24.0"
67+
state.save_value()
68+
69+
titrator.thermal_control.set_base_thermal_target.assert_called_once_with(7.5)
70+
titrator.thermal_control.set_sine_amplitude_and_hours.assert_called_once_with(
71+
2.0, 24.0
72+
)
73+
74+
print_mock.assert_any_call("New Temp=7.50", line=1)
75+
print_mock.assert_any_call("A=2.00 P=24.000", line=2)
76+
assert isinstance(titrator.state.next_state, MainMenu)
77+
78+
79+
def test_set_thermal_sine_wave_get_label():
80+
"""
81+
Test the label returned by get_label.
82+
"""
83+
titrator = Titrator()
84+
state = SetThermalSineWave(titrator, MainMenu(titrator))
85+
86+
assert state.get_label() == "Set T Set Point"
87+
state.sub_state = 1
88+
assert state.get_label() == "Set Amplitude:"
89+
state.sub_state = 2
90+
assert state.get_label() == "Set Period hrs:"
91+
92+
93+
def test_handle_key_d():
94+
"""
95+
Test that entering 'D' returns to the main menu.
96+
"""
97+
titrator = Titrator()
98+
state = SetThermalSineWave(titrator, MainMenu(titrator))
99+
100+
state.handle_key("D")
101+
assert isinstance(titrator.state, MainMenu)

0 commit comments

Comments
 (0)