Skip to content

Commit 2b52821

Browse files
Ph device features (#34)
* PH device functions draft * add test and tweaks to ph related files * rename for linting * reorder device functions alphabetically and new pH classes * Add ph calibration states * Correcting merge * Formatting. * Clean up main menu merge --------- Co-authored-by: Preston Carman <preston.carman@wallawalla.edu>
1 parent 0d1e025 commit 2b52821

28 files changed

+1808
-18
lines changed

src/devices/eeprom.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ def __init__(self):
1313
The constructor function for the EEPROM class
1414
"""
1515
self._google_sheet_interval = 20
16+
self._ignore_bad_ph_slope = False
1617
self._kd_value = 36.0
1718
self._ki_value = 28.0
1819
self._kp_value = 20.0
@@ -26,6 +27,14 @@ def get_google_sheet_interval(self, default):
2627
return default
2728
return self._google_sheet_interval
2829

30+
def get_ignore_bad_ph_slope(self, default):
31+
"""
32+
Get the ignore bad pH slope setting from EEPROM
33+
"""
34+
if self._ignore_bad_ph_slope is None:
35+
return default
36+
return self._ignore_bad_ph_slope
37+
2938
def get_kd(self, default):
3039
"""
3140
Get the Kd value from EEPROM
@@ -64,6 +73,12 @@ def set_google_sheet_interval(self, value):
6473
"""
6574
self._google_sheet_interval = value
6675

76+
def set_ignore_bad_ph_slope(self, value):
77+
"""
78+
Set the ignore bad pH slope setting in EEPROM
79+
"""
80+
self._ignore_bad_ph_slope = value
81+
6782
def set_kd(self, value):
6883
"""
6984
Set the Kd value in EEPROM
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
"""
2+
The file to hold the PH Calibration Warning class
3+
"""
4+
5+
import time
6+
7+
from src.ui_state.ui_state import UIState
8+
from src.ui_state.view_menu.view_ph_calibration import ViewPHCalibration
9+
10+
11+
class PHCalibrationWarning(UIState):
12+
"""
13+
Constructor for the PHCalibrationWarning class.
14+
"""
15+
16+
def __init__(self, titrator, previous_state=None):
17+
super().__init__(titrator)
18+
self.start_time = time.monotonic()
19+
self.previous_state = previous_state
20+
21+
def loop(self):
22+
"""
23+
Handle the blinking warning message and user response prompts.
24+
"""
25+
elapsed_time = (time.monotonic() - self.start_time) * 1000
26+
27+
if elapsed_time % 8000 < 5000:
28+
if elapsed_time % 1000 < 700:
29+
self.titrator.lcd.print("BAD CALIBRATION?", line=1)
30+
else:
31+
self.titrator.lcd.print("", line=1)
32+
33+
slope_response = self.titrator.ph_probe.get_slope()
34+
self.titrator.lcd.print(slope_response, line=2)
35+
else:
36+
self.titrator.lcd.print("A: Accept/ignore", line=1)
37+
self.titrator.lcd.print("C: Clear calibra", line=2)
38+
39+
def handle_key(self, key):
40+
"""
41+
Docstring for handle_key A and C
42+
"""
43+
if key == "A":
44+
print("Setting ignore_bad_ph_slope to True")
45+
self.titrator.ph_probe.eeprom.set_ignore_bad_ph_slope(True)
46+
print("Ignore flag set. Returning to previous state.")
47+
self._set_next_state(self.previous_state, True)
48+
elif key == "C":
49+
self.titrator.ph_probe.clear_calibration()
50+
self._set_next_state(ViewPHCalibration(self.titrator, self), True)

src/devices/ph_control.py

Lines changed: 135 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,148 @@
22
The file for the PH Control class
33
"""
44

5+
import time
6+
57

68
class PHControl:
79
"""
810
The class for the PH Control
911
"""
1012

11-
def __init__(self):
13+
FLAT_TYPE = 0
14+
RAMP_TYPE = 1
15+
SINE_TYPE = 2
16+
17+
def __init__(self, titrator):
1218
"""
1319
The constructor function for the PH Control class
1420
"""
21+
self.titrator = titrator
1522
self.use_pid = bool(True)
23+
self._base_target_ph = 8.125
24+
self._current_target_ph = 8.5
25+
self._ph_function_type = PHControl.FLAT_TYPE # Default to 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_target_ph(self):
39+
"""
40+
Get the base target pH value
41+
"""
42+
return self._base_target_ph
43+
44+
def get_current_target_ph(self):
45+
"""
46+
Get the current target pH value
47+
"""
48+
return self._current_target_ph
49+
50+
def get_period_in_seconds(self):
51+
"""
52+
Get the period in seconds for the pH function.
53+
"""
54+
return self._period_in_seconds
55+
56+
def get_ph_function_type(self):
57+
"""
58+
Get the current pH function type.
59+
"""
60+
return self._ph_function_type
61+
62+
def get_ramp_time_end(self):
63+
"""
64+
Get the ramp time end in seconds.
65+
"""
66+
return (
67+
self._ramp_time_end_seconds
68+
if self._ph_function_type != PHControl.FLAT_TYPE
69+
else 0
70+
)
71+
72+
def get_ramp_time_start(self):
73+
"""
74+
Get the ramp time start in seconds.
75+
"""
76+
return (
77+
self._ramp_time_start_seconds
78+
if self._ph_function_type != PHControl.FLAT_TYPE
79+
else 0
80+
)
81+
82+
def set_amplitude(self, amplitude):
83+
"""
84+
Set the amplitude for the pH function.
85+
"""
86+
self._amplitude = amplitude
87+
88+
def set_base_target_ph(self, target_ph):
89+
"""
90+
Set the base target pH value
91+
"""
92+
self._base_target_ph = target_ph
93+
94+
def set_current_target_ph(self, target_ph):
95+
"""
96+
Set the current target pH value
97+
"""
98+
self._current_target_ph = target_ph
99+
100+
def set_ph_function_type(self, function_type):
101+
"""
102+
Set the current pH function type.
103+
"""
104+
if function_type in (
105+
PHControl.FLAT_TYPE,
106+
PHControl.RAMP_TYPE,
107+
PHControl.SINE_TYPE,
108+
):
109+
self._ph_function_type = function_type
110+
else:
111+
raise ValueError("Invalid pH function type")
112+
113+
def set_ramp_duration_hours(self, new_ph_ramp_duration):
114+
"""
115+
Set the ramp duration in hours. If the duration is greater than 0, configure ramp parameters;
116+
otherwise, set the function type to FLAT_TYPE.
117+
"""
118+
if new_ph_ramp_duration > 0:
119+
current_ramp_time = (
120+
self._ramp_time_end_seconds - self._ramp_time_start_seconds
121+
)
122+
current_ramp_time_str = f"{current_ramp_time:.3f}"
123+
new_ramp_duration_str = f"{new_ph_ramp_duration:.3f}"
124+
print(
125+
f"Change ramp time from {current_ramp_time_str} to {new_ramp_duration_str}"
126+
)
127+
128+
self._ramp_time_start_seconds = int(time.monotonic())
129+
self._ramp_time_end_seconds = self._ramp_time_start_seconds + int(
130+
new_ph_ramp_duration * 3600
131+
)
132+
133+
self._ramp_initial_value = self.titrator.ph_probe.get_ph_value()
134+
self._ph_function_type = PHControl.RAMP_TYPE
135+
else:
136+
self._ramp_time_end_seconds = 0
137+
self._ph_function_type = PHControl.FLAT_TYPE
138+
print("Set ramp time to 0")
139+
140+
def set_sine_amplitude_and_hours(self, amplitude, period_in_hours):
141+
"""
142+
Set the amplitude and period (in hours) for the sine wave pH function.
143+
"""
144+
if amplitude > 0 and period_in_hours > 0:
145+
self._amplitude = amplitude
146+
self._period_in_seconds = int(period_in_hours * 3600)
147+
self._ph_function_type = PHControl.SINE_TYPE
148+
else:
149+
raise ValueError("Amp and period !> than 0.")

src/devices/ph_probe_mock.py

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
"""
2+
The file for the Mock pH probe
3+
"""
4+
5+
6+
class PHProbe:
7+
"""
8+
Docstring for PHProbe
9+
"""
10+
11+
def __init__(self, eeprom):
12+
"""
13+
The constructor for the PHProbe class
14+
"""
15+
self.eeprom = eeprom
16+
self._value = 3.125
17+
self._calibration_response = "?CAL,3"
18+
self._slope_response = "99.7,100.3, -0.89"
19+
self.slope_is_out_of_range = False
20+
self._highpoint_calibration = None
21+
self._lowpoint_calibration = None
22+
self._midpoint_calibration = None
23+
24+
def clear_calibration(self):
25+
"""
26+
Clear the calibration response string
27+
"""
28+
self.slope_is_out_of_range = False
29+
self.eeprom.set_ignore_bad_ph_slope(False)
30+
self._calibration_response = ""
31+
32+
def get_calibration(self):
33+
"""
34+
Get the calibration response string
35+
"""
36+
return self._calibration_response
37+
38+
def get_ph_value(self):
39+
"""
40+
Get the current pH value from the mock probe
41+
"""
42+
return self._value
43+
44+
def get_slope(self):
45+
"""
46+
Get the slope response string
47+
"""
48+
return self._slope_response
49+
50+
def set_ph_value(self, ph_value):
51+
"""
52+
Set the current pH value for the mock probe
53+
"""
54+
self._value = ph_value
55+
56+
def set_highpoint_calibration(self, highpoint):
57+
"""
58+
Set the highpoint calibration value for the pH probe.
59+
"""
60+
self.slope_is_out_of_range = False
61+
self.eeprom.set_ignore_bad_ph_slope(False)
62+
self._highpoint_calibration = highpoint
63+
buffer = f"Cal,High,{int(highpoint)}.{int(highpoint * 1000 + 0.5) % 1000}\r"
64+
print(buffer) # Simulate sending the string to the Atlas Scientific product
65+
print(
66+
f"PHProbe::setHighpointCalibration({int(highpoint)}.{int(highpoint * 1000) % 1000})"
67+
)
68+
69+
def set_lowpoint_calibration(self, lowpoint):
70+
"""
71+
Set the lowpoint calibration value for the pH probe.
72+
"""
73+
self.slope_is_out_of_range = False
74+
self.eeprom.set_ignore_bad_ph_slope(False)
75+
self._lowpoint_calibration = lowpoint
76+
buffer = f"Cal,low,{int(lowpoint)}.{int(lowpoint * 1000 + 0.5) % 1000}\r"
77+
print(buffer) # Simulate sending the string to the Atlas Scientific product
78+
print(
79+
f"PHProbe::setLowpointCalibration({int(lowpoint)}.{int(lowpoint * 1000) % 1000})"
80+
)
81+
82+
def set_midpoint_calibration(self, midpoint):
83+
"""
84+
Set the midpoint calibration value for the pH probe.
85+
"""
86+
self.slope_is_out_of_range = False
87+
self.eeprom.set_ignore_bad_ph_slope(False)
88+
self._midpoint_calibration = midpoint
89+
buffer = f"Cal,mid,{int(midpoint)}.{int(midpoint * 1000 + 0.5) % 1000}\r"
90+
print(buffer) # Simulate sending the string to the Atlas Scientific product
91+
print(
92+
f"PHProbe::setMidpointCalibration({int(midpoint)}.{int(midpoint * 1000) % 1000})"
93+
)
94+
95+
def should_warn_about_calibration(self):
96+
"""
97+
Determine if a calibration warning should be shown based on the slope and ignore settings.
98+
"""
99+
if not self.slope_is_out_of_range:
100+
return False
101+
if self.slope_is_out_of_range and not self.eeprom.get_ignore_bad_ph_slope(
102+
False
103+
):
104+
return True
105+
if self.slope_is_out_of_range and self.eeprom.get_ignore_bad_ph_slope(False):
106+
return False
107+
return False

src/titrator.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,13 @@
1010
Heater,
1111
Keypad,
1212
LiquidCrystal,
13-
PHProbe,
1413
StirControl,
1514
SyringePump,
1615
TemperatureControl,
1716
TemperatureProbe,
1817
)
1918
from src.devices.ph_control import PHControl
19+
from src.devices.ph_probe_mock import PHProbe
2020
from src.devices.pid import PID
2121
from src.devices.sd import SD
2222
from src.devices.thermal_control import ThermalControl
@@ -50,7 +50,7 @@ def __init__(self):
5050
self.pid = PID(self.eeprom)
5151

5252
# Initialize PH Control
53-
self.ph_control = PHControl()
53+
self.ph_control = PHControl(self)
5454

5555
# Initialize Thermal Control
5656
self.thermal_control = ThermalControl(self)
@@ -68,7 +68,7 @@ def __init__(self):
6868
self.keypad = Keypad()
6969

7070
# Initialize pH Probe
71-
self.ph_probe = PHProbe()
71+
self.ph_probe = PHProbe(self.eeprom)
7272

7373
# Initialize Syringe Pump
7474
self.pump = SyringePump()

0 commit comments

Comments
 (0)