Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions src/devices/eeprom.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ def __init__(self):
The constructor function for the EEPROM class
"""
self._google_sheet_interval = 20
self._ignore_bad_ph_slope = False
self._kd_value = 36.0
self._ki_value = 28.0
self._kp_value = 20.0
Expand All @@ -26,6 +27,14 @@ def get_google_sheet_interval(self, default):
return default
return self._google_sheet_interval

def get_ignore_bad_ph_slope(self, default):
"""
Get the ignore bad pH slope setting from EEPROM
"""
if self._ignore_bad_ph_slope is None:
return default
return self._ignore_bad_ph_slope

def get_kd(self, default):
"""
Get the Kd value from EEPROM
Expand Down Expand Up @@ -64,6 +73,12 @@ def set_google_sheet_interval(self, value):
"""
self._google_sheet_interval = value

def set_ignore_bad_ph_slope(self, value):
"""
Set the ignore bad pH slope setting in EEPROM
"""
self._ignore_bad_ph_slope = value

def set_kd(self, value):
"""
Set the Kd value in EEPROM
Expand Down
50 changes: 50 additions & 0 deletions src/devices/ph_calibration_warning.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
"""
The file to hold the PH Calibration Warning class
"""

import time

from src.ui_state.ui_state import UIState
from src.ui_state.view_menu.view_ph_calibration import ViewPHCalibration


class PHCalibrationWarning(UIState):
"""
Constructor for the PHCalibrationWarning class.
"""

def __init__(self, titrator, previous_state=None):
super().__init__(titrator)
self.start_time = time.monotonic()
self.previous_state = previous_state

def loop(self):
"""
Handle the blinking warning message and user response prompts.
"""
elapsed_time = (time.monotonic() - self.start_time) * 1000

if elapsed_time % 8000 < 5000:
if elapsed_time % 1000 < 700:
self.titrator.lcd.print("BAD CALIBRATION?", line=1)
else:
self.titrator.lcd.print("", line=1)

slope_response = self.titrator.ph_probe.get_slope()
self.titrator.lcd.print(slope_response, line=2)
else:
self.titrator.lcd.print("A: Accept/ignore", line=1)
self.titrator.lcd.print("C: Clear calibra", line=2)

def handle_key(self, key):
"""
Docstring for handle_key A and C
"""
if key == "A":
print("Setting ignore_bad_ph_slope to True")
self.titrator.ph_probe.eeprom.set_ignore_bad_ph_slope(True)
print("Ignore flag set. Returning to previous state.")
self._set_next_state(self.previous_state, True)
elif key == "C":
self.titrator.ph_probe.clear_calibration()
self._set_next_state(ViewPHCalibration(self.titrator, self), True)
136 changes: 135 additions & 1 deletion src/devices/ph_control.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,148 @@
The file for the PH Control class
"""

import time


class PHControl:
"""
The class for the PH Control
"""

def __init__(self):
FLAT_TYPE = 0
RAMP_TYPE = 1
SINE_TYPE = 2

def __init__(self, titrator):
"""
The constructor function for the PH Control class
"""
self.titrator = titrator
self.use_pid = bool(True)
self._base_target_ph = 8.125
self._current_target_ph = 8.5
self._ph_function_type = PHControl.FLAT_TYPE # Default to FLAT_TYPE
self._ramp_time_start_seconds = 0
self._ramp_time_end_seconds = 0
self._ramp_initial_value = 0.0
self._amplitude = 0.0
self._period_in_seconds = 0

def get_amplitude(self):
"""
Get the amplitude for the pH function.
"""
return self._amplitude

def get_base_target_ph(self):
"""
Get the base target pH value
"""
return self._base_target_ph

def get_current_target_ph(self):
"""
Get the current target pH value
"""
return self._current_target_ph

def get_period_in_seconds(self):
"""
Get the period in seconds for the pH function.
"""
return self._period_in_seconds

def get_ph_function_type(self):
"""
Get the current pH function type.
"""
return self._ph_function_type

def get_ramp_time_end(self):
"""
Get the ramp time end in seconds.
"""
return (
self._ramp_time_end_seconds
if self._ph_function_type != PHControl.FLAT_TYPE
else 0
)

def get_ramp_time_start(self):
"""
Get the ramp time start in seconds.
"""
return (
self._ramp_time_start_seconds
if self._ph_function_type != PHControl.FLAT_TYPE
else 0
)

def set_amplitude(self, amplitude):
"""
Set the amplitude for the pH function.
"""
self._amplitude = amplitude

def set_base_target_ph(self, target_ph):
"""
Set the base target pH value
"""
self._base_target_ph = target_ph

def set_current_target_ph(self, target_ph):
"""
Set the current target pH value
"""
self._current_target_ph = target_ph

def set_ph_function_type(self, function_type):
"""
Set the current pH function type.
"""
if function_type in (
PHControl.FLAT_TYPE,
PHControl.RAMP_TYPE,
PHControl.SINE_TYPE,
):
self._ph_function_type = function_type
else:
raise ValueError("Invalid pH function type")

def set_ramp_duration_hours(self, new_ph_ramp_duration):
"""
Set the ramp duration in hours. If the duration is greater than 0, configure ramp parameters;
otherwise, set the function type to FLAT_TYPE.
"""
if new_ph_ramp_duration > 0:
current_ramp_time = (
self._ramp_time_end_seconds - self._ramp_time_start_seconds
)
current_ramp_time_str = f"{current_ramp_time:.3f}"
new_ramp_duration_str = f"{new_ph_ramp_duration:.3f}"
print(
f"Change ramp time from {current_ramp_time_str} to {new_ramp_duration_str}"
)

self._ramp_time_start_seconds = int(time.monotonic())
self._ramp_time_end_seconds = self._ramp_time_start_seconds + int(
new_ph_ramp_duration * 3600
)

self._ramp_initial_value = self.titrator.ph_probe.get_ph_value()
self._ph_function_type = PHControl.RAMP_TYPE
else:
self._ramp_time_end_seconds = 0
self._ph_function_type = PHControl.FLAT_TYPE
print("Set ramp time to 0")

def set_sine_amplitude_and_hours(self, amplitude, period_in_hours):
"""
Set the amplitude and period (in hours) for the sine wave pH function.
"""
if amplitude > 0 and period_in_hours > 0:
self._amplitude = amplitude
self._period_in_seconds = int(period_in_hours * 3600)
self._ph_function_type = PHControl.SINE_TYPE
else:
raise ValueError("Amp and period !> than 0.")
107 changes: 107 additions & 0 deletions src/devices/ph_probe_mock.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
"""
The file for the Mock pH probe
"""


class PHProbe:
"""
Docstring for PHProbe
"""

def __init__(self, eeprom):
"""
The constructor for the PHProbe class
"""
self.eeprom = eeprom
self._value = 3.125
self._calibration_response = "?CAL,3"
self._slope_response = "99.7,100.3, -0.89"
self.slope_is_out_of_range = False
self._highpoint_calibration = None
self._lowpoint_calibration = None
self._midpoint_calibration = None

def clear_calibration(self):
"""
Clear the calibration response string
"""
self.slope_is_out_of_range = False
self.eeprom.set_ignore_bad_ph_slope(False)
self._calibration_response = ""

def get_calibration(self):
"""
Get the calibration response string
"""
return self._calibration_response

def get_ph_value(self):
"""
Get the current pH value from the mock probe
"""
return self._value

def get_slope(self):
"""
Get the slope response string
"""
return self._slope_response

def set_ph_value(self, ph_value):
"""
Set the current pH value for the mock probe
"""
self._value = ph_value

def set_highpoint_calibration(self, highpoint):
"""
Set the highpoint calibration value for the pH probe.
"""
self.slope_is_out_of_range = False
self.eeprom.set_ignore_bad_ph_slope(False)
self._highpoint_calibration = highpoint
buffer = f"Cal,High,{int(highpoint)}.{int(highpoint * 1000 + 0.5) % 1000}\r"
print(buffer) # Simulate sending the string to the Atlas Scientific product
print(
f"PHProbe::setHighpointCalibration({int(highpoint)}.{int(highpoint * 1000) % 1000})"
)

def set_lowpoint_calibration(self, lowpoint):
"""
Set the lowpoint calibration value for the pH probe.
"""
self.slope_is_out_of_range = False
self.eeprom.set_ignore_bad_ph_slope(False)
self._lowpoint_calibration = lowpoint
buffer = f"Cal,low,{int(lowpoint)}.{int(lowpoint * 1000 + 0.5) % 1000}\r"
print(buffer) # Simulate sending the string to the Atlas Scientific product
print(
f"PHProbe::setLowpointCalibration({int(lowpoint)}.{int(lowpoint * 1000) % 1000})"
)

def set_midpoint_calibration(self, midpoint):
"""
Set the midpoint calibration value for the pH probe.
"""
self.slope_is_out_of_range = False
self.eeprom.set_ignore_bad_ph_slope(False)
self._midpoint_calibration = midpoint
buffer = f"Cal,mid,{int(midpoint)}.{int(midpoint * 1000 + 0.5) % 1000}\r"
print(buffer) # Simulate sending the string to the Atlas Scientific product
print(
f"PHProbe::setMidpointCalibration({int(midpoint)}.{int(midpoint * 1000) % 1000})"
)

def should_warn_about_calibration(self):
"""
Determine if a calibration warning should be shown based on the slope and ignore settings.
"""
if not self.slope_is_out_of_range:
return False
if self.slope_is_out_of_range and not self.eeprom.get_ignore_bad_ph_slope(
False
):
return True
if self.slope_is_out_of_range and self.eeprom.get_ignore_bad_ph_slope(False):
return False
return False
6 changes: 3 additions & 3 deletions src/titrator.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@
Heater,
Keypad,
LiquidCrystal,
PHProbe,
StirControl,
SyringePump,
TemperatureControl,
TemperatureProbe,
)
from src.devices.ph_control import PHControl
from src.devices.ph_probe_mock import PHProbe
from src.devices.pid import PID
from src.devices.sd import SD
from src.devices.thermal_control import ThermalControl
Expand Down Expand Up @@ -50,7 +50,7 @@ def __init__(self):
self.pid = PID(self.eeprom)

# Initialize PH Control
self.ph_control = PHControl()
self.ph_control = PHControl(self)

# Initialize Thermal Control
self.thermal_control = ThermalControl(self)
Expand All @@ -68,7 +68,7 @@ def __init__(self):
self.keypad = Keypad()

# Initialize pH Probe
self.ph_probe = PHProbe()
self.ph_probe = PHProbe(self.eeprom)

# Initialize Syringe Pump
self.pump = SyringePump()
Expand Down
Loading