diff --git a/src/devices/led_mock.py b/src/devices/led_mock.py new file mode 100644 index 0000000..96c30af --- /dev/null +++ b/src/devices/led_mock.py @@ -0,0 +1,23 @@ +""" +Mock LED device for the Tank Controller. +""" + + +class LED: + """Mock LED that tracks an ON/OFF state.""" + + def __init__(self): + """Initialize the LED as off.""" + self.is_on = False + + def on(self): + """Turn the LED on.""" + self.is_on = True + + def off(self): + """Turn the LED off.""" + self.is_on = False + + def toggle(self): + """Toggle the LED between on and off.""" + self.is_on = not self.is_on diff --git a/src/devices/library.py b/src/devices/library.py index 4b2e2da..0602636 100644 --- a/src/devices/library.py +++ b/src/devices/library.py @@ -17,6 +17,7 @@ from src.devices.digital_mock import DigitalInOut from src.devices.heater_mock import Heater from src.devices.keypad_mock import Keypad + from src.devices.led_mock import LED from src.devices.liquid_crystal_mock import LiquidCrystal from src.devices.max31865_mock import MAX31865 from src.devices.serial_mock import Serial diff --git a/src/gui.py b/src/gui.py index 1409117..bbe4656 100644 --- a/src/gui.py +++ b/src/gui.py @@ -34,15 +34,31 @@ def __init__(self, titrator): # Initialize the GUI Frame self.root = tk.Tk() - self.root.geometry("280x200") + self.root.geometry("560x200") self.root.title("Tank Controller") self.root.configure(background="black") + + # Split window into left and right sections self.root.columnconfigure(0, weight=1) + self.root.columnconfigure(1, weight=1) self.root.rowconfigure(0, weight=1) + # --------------------------------------------------------- + # LEFT SIDE - LCD AND KEYPAD + # --------------------------------------------------------- + + left_frame = tk.Frame(self.root, bg="lightgray") + left_frame.grid(row=0, column=0, sticky=STICKY) + + left_frame.columnconfigure(0, weight=1) + left_frame.rowconfigure(0, weight=1) + left_frame.rowconfigure(1, weight=1) + # Initialize the Labels - label_frame = tk.Frame(self.root) + label_frame = tk.Frame(left_frame) label_frame.config(bg=BG) + + label_frame.columnconfigure(0, weight=1) label_frame.rowconfigure(0, weight=1) label_frame.rowconfigure(1, weight=1) label_frame.rowconfigure(2, weight=1) @@ -95,7 +111,7 @@ def __init__(self, titrator): label_frame.grid(row=0, column=0, sticky=STICKY) # Initialize the Buttons - buttonframe = tk.Frame(self.root) + buttonframe = tk.Frame(left_frame) buttonframe.columnconfigure(0, weight=1) buttonframe.columnconfigure(1, weight=1) buttonframe.columnconfigure(2, weight=1) @@ -215,7 +231,50 @@ def __init__(self, titrator): buttonframe.grid(row=1, column=0, sticky=STICKY) - self.thread = threading.Thread(target=self.update_lcd, daemon=True) + # --------------------------------------------------------- + # RIGHT SIDE - BOARD LED + # --------------------------------------------------------- + + right_frame = tk.Frame(self.root, bg="darkgray") + right_frame.grid(row=0, column=1, sticky=STICKY) + + right_frame.columnconfigure(0, weight=1) + right_frame.columnconfigure(1, weight=1) + right_frame.rowconfigure(0, weight=1) + + self.led_canvas = tk.Canvas( + right_frame, + width=80, + height=80, + bg="darkgray", + highlightthickness=0, + ) + self.led_canvas.grid(row=0, column=0, padx=5, pady=10, sticky=tk.NE) + + self.led_circle = self.led_canvas.create_oval( + 4, + 4, + 30, + 30, + fill="red", + outline="black", + width=3, + ) + + self.led_label = tk.Label( + right_frame, + text="Board LED", + font=("Arial", 12), + bg="darkgray", + fg="black", + ) + self.led_label.grid(row=0, column=1, padx=2, pady=10, sticky=tk.NW) + + # Start GUI update thread + self.thread = threading.Thread( + target=self.update_gui, + daemon=True, + ) self.thread.start() self.root.mainloop() @@ -226,12 +285,13 @@ def button_press(self, key): """ self.titrator.keypad.set_key(key) - def update_lcd(self): + def update_gui(self): """ - The function to update the GUI LCD + The function to update the GUI LCD and LED """ while True: time.sleep(0.001) + self.line_1.config( text=self.titrator.lcd.get_line(1), anchor=self.titrator.lcd.get_style(1), @@ -248,3 +308,14 @@ def update_lcd(self): text=self.titrator.lcd.get_line(4), anchor=self.titrator.lcd.get_style(4), ) + + if self.titrator.led.is_on: + self.led_canvas.itemconfig( + self.led_circle, + fill="yellow", + ) + else: + self.led_canvas.itemconfig( + self.led_circle, + fill="black", + ) diff --git a/src/titrator.py b/src/titrator.py index cc7f1a1..77d8630 100644 --- a/src/titrator.py +++ b/src/titrator.py @@ -7,6 +7,7 @@ from src.devices.date_time import DateTime from src.devices.eeprom import EEPROM from src.devices.library import ( + LED, Heater, Keypad, LiquidCrystal, @@ -64,6 +65,9 @@ def __init__(self): # Initialize LCD self.lcd = LiquidCrystal() + # Initialize LED + self.led = LED() + # Initialize Keypad self.keypad = Keypad() diff --git a/src/ui_state/main_menu.py b/src/ui_state/main_menu.py index 8204608..bac8601 100644 --- a/src/ui_state/main_menu.py +++ b/src/ui_state/main_menu.py @@ -11,6 +11,7 @@ from src.ui_state.set_menu.set_kd import SetKD from src.ui_state.set_menu.set_ki import SetKI from src.ui_state.set_menu.set_kp import SetKP +from src.ui_state.set_menu.set_led import SetLED from src.ui_state.set_menu.set_ph_calibration import PHCalibration from src.ui_state.set_menu.set_ph_calibration_clear import ResetPHCalibration from src.ui_state.set_menu.set_ph_sine_wave import SetPHSineWave @@ -77,6 +78,7 @@ def __init__(self, titrator): "Set chill/heat", "Set Google mins", "Set KD", + "Set LED", "Set KI", "Set KP", "Set pH target", @@ -114,6 +116,7 @@ def __init__(self, titrator): SetChillOrHeat, # Set chill/heat SetGoogleSheetInterval, # Set Google mins SetKD, # Set KD + SetLED, # Set LED SetKI, # Set KI SetKP, # Set KP SetPHTarget, # Set pH target diff --git a/src/ui_state/set_menu/set_led.py b/src/ui_state/set_menu/set_led.py new file mode 100644 index 0000000..e00dccb --- /dev/null +++ b/src/ui_state/set_menu/set_led.py @@ -0,0 +1,51 @@ +""" +The file to hold the Set LED class +""" + +from src.devices.library import Keypad +from src.ui_state.ui_state import UIState + + +class SetLED(UIState): + """ + This is a class for the SetLED state of the Tank Controller + """ + + def __init__(self, titrator, previous_state=None): + super().__init__(titrator) + self.previous_state = previous_state + + def loop(self): + """ + The main loop for the SetLED state + """ + self.titrator.lcd.print("LED 1:on; 9:off", line=1) + + if self.titrator.led.is_on: + self.titrator.lcd.print("Currently on", line=2) + else: + self.titrator.lcd.print("Currently off", line=2) + + def handle_key(self, key): + """ + Handle key presses to control the LED. + """ + if key == Keypad.KEY_1: + self.titrator.led.on() + self.titrator.lcd.print("LED on", line=2) + self.return_to_main_menu(ms_delay=3000) + + if key == Keypad.KEY_9: + self.titrator.led.off() + self.titrator.lcd.print("LED off", line=2) + self.return_to_main_menu(ms_delay=3000) + + if key == Keypad.KEY_A: + if self.titrator.led.is_on: + self.titrator.lcd.print("LED on", line=2) + else: + self.titrator.lcd.print("LED off", line=2) + self.return_to_main_menu(ms_delay=3000) + + if key == Keypad.KEY_D: + self._set_next_state(self.previous_state, True) diff --git a/tests/devices/led_mock_test.py b/tests/devices/led_mock_test.py new file mode 100644 index 0000000..485c764 --- /dev/null +++ b/tests/devices/led_mock_test.py @@ -0,0 +1,50 @@ +""" +The file to test the Mock LED class. +""" + +from src.devices.led_mock import LED + + +def test_led_starts_off(): + """ + LED should be off when initialized. + """ + led = LED() + + assert led.is_on is False + + +def test_led_turns_on(): + """ + LED should turn on when on() is called. + """ + led = LED() + + led.on() + + assert led.is_on is True + + +def test_led_turns_off(): + """ + LED should turn off when off() is called. + """ + led = LED() + led.on() + + led.off() + + assert led.is_on is False + + +def test_led_toggle(): + """ + LED should switch between on and off when toggle() is called. + """ + led = LED() + + led.toggle() + assert led.is_on is True + + led.toggle() + assert led.is_on is False diff --git a/tests/ui_state/set_menu/set_led_test.py b/tests/ui_state/set_menu/set_led_test.py new file mode 100644 index 0000000..1f5d65c --- /dev/null +++ b/tests/ui_state/set_menu/set_led_test.py @@ -0,0 +1,28 @@ +""" +The file to test the SetLED class +""" + +from src.devices.library import Keypad +from src.titrator import Titrator +from src.ui_state.set_menu.set_led import SetLED + + +def test_led_on(): + """Test turning the LED on.""" + titrator = Titrator() + state = SetLED(titrator) + + state.handle_key(Keypad.KEY_1) + + assert titrator.led.is_on is True + + +def test_led_off(): + """Test turning the LED off.""" + titrator = Titrator() + state = SetLED(titrator) + + titrator.led.on() + state.handle_key(Keypad.KEY_9) + + assert titrator.led.is_on is False