From bb0436b139f906a09a955cba3886e01e0a7be1f8 Mon Sep 17 00:00:00 2001 From: Lidya Langana Date: Wed, 19 Aug 2026 11:23:36 -0700 Subject: [PATCH 01/11] Add mock LED backend integration --- src/devices/led_mock.py | 24 +++++++++++++++ src/devices/library.py | 1 + src/titrator.py | 4 +++ src/ui_state/main_menu.py | 3 ++ src/ui_state/set_menu/set_led.py | 52 ++++++++++++++++++++++++++++++++ 5 files changed, 84 insertions(+) create mode 100644 src/devices/led_mock.py create mode 100644 src/ui_state/set_menu/set_led.py diff --git a/src/devices/led_mock.py b/src/devices/led_mock.py new file mode 100644 index 0000000..c25f393 --- /dev/null +++ b/src/devices/led_mock.py @@ -0,0 +1,24 @@ +""" +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 + \ No newline at end of file diff --git a/src/devices/library.py b/src/devices/library.py index 4b2e2da..2a7c41f 100644 --- a/src/devices/library.py +++ b/src/devices/library.py @@ -21,6 +21,7 @@ from src.devices.max31865_mock import MAX31865 from src.devices.serial_mock import Serial from src.devices.spi_mock import SPI + from src.devices.led_mock import LED else: import adafruit_ads1x15.ads1115 as ADS import board diff --git a/src/titrator.py b/src/titrator.py index cc7f1a1..b67cf77 100644 --- a/src/titrator.py +++ b/src/titrator.py @@ -14,6 +14,7 @@ SyringePump, TemperatureControl, TemperatureProbe, + LED, ) from src.devices.ph_control import PHControl from src.devices.ph_probe_mock import PHProbe @@ -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..91959df 100644 --- a/src/ui_state/main_menu.py +++ b/src/ui_state/main_menu.py @@ -9,6 +9,7 @@ from src.ui_state.set_menu.set_chill_or_heat import SetChillOrHeat from src.ui_state.set_menu.set_google_mins import SetGoogleSheetInterval from src.ui_state.set_menu.set_kd import SetKD +from src.ui_state.set_menu.set_led import SetLED 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_ph_calibration import PHCalibration @@ -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..efe9276 --- /dev/null +++ b/src/ui_state/set_menu/set_led.py @@ -0,0 +1,52 @@ +""" +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) + \ No newline at end of file From 304155dc14eec25912e6564c5d1cd7c08bc3bbf2 Mon Sep 17 00:00:00 2001 From: Lidya Langana Date: Wed, 19 Aug 2026 11:29:33 -0700 Subject: [PATCH 02/11] Added tests for the mock LED, and SetLED --- tests/devices/led_mock_test.py | 14 ++++++++++++ tests/ui_state/set_menu/set_led_test.py | 29 +++++++++++++++++++++++++ 2 files changed, 43 insertions(+) create mode 100644 tests/devices/led_mock_test.py create mode 100644 tests/ui_state/set_menu/set_led_test.py diff --git a/tests/devices/led_mock_test.py b/tests/devices/led_mock_test.py new file mode 100644 index 0000000..a2366fc --- /dev/null +++ b/tests/devices/led_mock_test.py @@ -0,0 +1,14 @@ +from src.devices.library import LED + +led = LED() + +print(led.is_on) # False + +led.on() +print(led.is_on) # True + +led.off() +print(led.is_on) # False + +led.toggle() +print(led.is_on) # True 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..0daaa02 --- /dev/null +++ b/tests/ui_state/set_menu/set_led_test.py @@ -0,0 +1,29 @@ +""" +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 + \ No newline at end of file From 1f4e439410e69ab396e84f5a05e7b453a6aca87f Mon Sep 17 00:00:00 2001 From: Lidya Langana Date: Wed, 19 Aug 2026 11:31:25 -0700 Subject: [PATCH 03/11] Add mock LED GUI display --- src/gui.py | 83 ++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 77 insertions(+), 6 deletions(-) diff --git a/src/gui.py b/src/gui.py index 1409117..a8a16da 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.state("zoomed") 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=30, sticky=tk.E) + + self.led_circle = self.led_canvas.create_oval( + 5, + 5, + 50, + 50, + 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=20, sticky=tk.W) + + # 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", + ) From f2004d8e5d00d96d50f924439d13ee927d3003ee Mon Sep 17 00:00:00 2001 From: Lidya Langana Date: Fri, 21 Aug 2026 12:28:45 -0700 Subject: [PATCH 04/11] change the screen --- src/gui.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui.py b/src/gui.py index a8a16da..6067cbc 100644 --- a/src/gui.py +++ b/src/gui.py @@ -34,7 +34,7 @@ def __init__(self, titrator): # Initialize the GUI Frame self.root = tk.Tk() - self.root.state("zoomed") + self.root.geometry("560x200") self.root.title("Tank Controller") self.root.configure(background="black") From e09fdea0b233a848232f2dc940f71e26fa647670 Mon Sep 17 00:00:00 2001 From: Lidya Langana Date: Fri, 21 Aug 2026 13:27:40 -0700 Subject: [PATCH 05/11] Add pytest unit tests for mock LED --- tests/devices/led_mock_test.py | 54 ++++++++++++++++++++++++++++------ 1 file changed, 45 insertions(+), 9 deletions(-) diff --git a/tests/devices/led_mock_test.py b/tests/devices/led_mock_test.py index a2366fc..485c764 100644 --- a/tests/devices/led_mock_test.py +++ b/tests/devices/led_mock_test.py @@ -1,14 +1,50 @@ -from src.devices.library import LED +""" +The file to test the Mock LED class. +""" -led = LED() +from src.devices.led_mock import LED -print(led.is_on) # False -led.on() -print(led.is_on) # True +def test_led_starts_off(): + """ + LED should be off when initialized. + """ + led = LED() -led.off() -print(led.is_on) # False + assert led.is_on is False -led.toggle() -print(led.is_on) # True + +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 From 9dd8105282b463886a3621a078a13bd0ac7e0a29 Mon Sep 17 00:00:00 2001 From: Lidya Langana Date: Fri, 28 Aug 2026 09:16:10 -0700 Subject: [PATCH 06/11] Move Board LED indicator to top-right of GUI --- src/gui.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/gui.py b/src/gui.py index 6067cbc..11b4cb6 100644 --- a/src/gui.py +++ b/src/gui.py @@ -249,13 +249,13 @@ def __init__(self, titrator): bg="darkgray", highlightthickness=0, ) - self.led_canvas.grid(row=0, column=0, padx=30, sticky=tk.E) + self.led_canvas.grid(row=0, column=0, padx=5, pady=10, sticky=tk.NE) self.led_circle = self.led_canvas.create_oval( - 5, - 5, - 50, - 50, + 4, + 4, + 30, + 30, fill="red", outline="black", width=3, @@ -268,7 +268,7 @@ def __init__(self, titrator): bg="darkgray", fg="black", ) - self.led_label.grid(row=0, column=1, padx=20, sticky=tk.W) + self.led_label.grid(row=0, column=1, padx=2, pady=10, sticky=tk.NW) # Start GUI update thread self.thread = threading.Thread( From 637f72a33f1136cf7ec025ec421fb75e1f2cf600 Mon Sep 17 00:00:00 2001 From: Lidya Langana Date: Fri, 28 Aug 2026 09:18:07 -0700 Subject: [PATCH 07/11] fix formatting --- src/devices/led_mock.py | 1 - src/ui_state/set_menu/set_led.py | 1 - tests/ui_state/set_menu/set_led_test.py | 1 - 3 files changed, 3 deletions(-) diff --git a/src/devices/led_mock.py b/src/devices/led_mock.py index c25f393..96c30af 100644 --- a/src/devices/led_mock.py +++ b/src/devices/led_mock.py @@ -21,4 +21,3 @@ def off(self): def toggle(self): """Toggle the LED between on and off.""" self.is_on = not self.is_on - \ No newline at end of file diff --git a/src/ui_state/set_menu/set_led.py b/src/ui_state/set_menu/set_led.py index efe9276..e00dccb 100644 --- a/src/ui_state/set_menu/set_led.py +++ b/src/ui_state/set_menu/set_led.py @@ -49,4 +49,3 @@ def handle_key(self, key): if key == Keypad.KEY_D: self._set_next_state(self.previous_state, True) - \ No newline at end of file diff --git a/tests/ui_state/set_menu/set_led_test.py b/tests/ui_state/set_menu/set_led_test.py index 0daaa02..1f5d65c 100644 --- a/tests/ui_state/set_menu/set_led_test.py +++ b/tests/ui_state/set_menu/set_led_test.py @@ -26,4 +26,3 @@ def test_led_off(): state.handle_key(Keypad.KEY_9) assert titrator.led.is_on is False - \ No newline at end of file From 73cd06d49421f5d6d4b4b512c12853dc987b8809 Mon Sep 17 00:00:00 2001 From: Lidya Langana Date: Fri, 28 Aug 2026 09:26:05 -0700 Subject: [PATCH 08/11] fix formatting by running format.bat/format.sh --- src/devices/library.py | 2 +- src/gui.py | 2 +- src/titrator.py | 2 +- src/ui_state/main_menu.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/devices/library.py b/src/devices/library.py index 2a7c41f..0602636 100644 --- a/src/devices/library.py +++ b/src/devices/library.py @@ -17,11 +17,11 @@ 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 from src.devices.spi_mock import SPI - from src.devices.led_mock import LED else: import adafruit_ads1x15.ads1115 as ADS import board diff --git a/src/gui.py b/src/gui.py index 11b4cb6..bbe4656 100644 --- a/src/gui.py +++ b/src/gui.py @@ -264,7 +264,7 @@ def __init__(self, titrator): self.led_label = tk.Label( right_frame, text="Board LED", - font=("Arial",12), + font=("Arial", 12), bg="darkgray", fg="black", ) diff --git a/src/titrator.py b/src/titrator.py index b67cf77..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, @@ -14,7 +15,6 @@ SyringePump, TemperatureControl, TemperatureProbe, - LED, ) from src.devices.ph_control import PHControl from src.devices.ph_probe_mock import PHProbe diff --git a/src/ui_state/main_menu.py b/src/ui_state/main_menu.py index 91959df..bac8601 100644 --- a/src/ui_state/main_menu.py +++ b/src/ui_state/main_menu.py @@ -9,9 +9,9 @@ from src.ui_state.set_menu.set_chill_or_heat import SetChillOrHeat from src.ui_state.set_menu.set_google_mins import SetGoogleSheetInterval from src.ui_state.set_menu.set_kd import SetKD -from src.ui_state.set_menu.set_led import SetLED 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 From 372f38cd316749c63b654335c5b4604d67e3ac65 Mon Sep 17 00:00:00 2001 From: Lidya Langana Date: Fri, 11 Sep 2026 07:51:39 -0700 Subject: [PATCH 09/11] Implement new heater/chiller state indicator and animation in GUI --- src/gui.py | 147 ++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 99 insertions(+), 48 deletions(-) diff --git a/src/gui.py b/src/gui.py index bbe4656..603c25b 100644 --- a/src/gui.py +++ b/src/gui.py @@ -123,21 +123,18 @@ def __init__(self, titrator): width=BUTTON_WIDTH, command=lambda: self.button_press("1"), ).grid(row=4, column=0, sticky=STICKY) - tk.Button( buttonframe, text="2", width=BUTTON_WIDTH, command=lambda: self.button_press("2"), ).grid(row=4, column=1, sticky=STICKY) - tk.Button( buttonframe, text="3", width=BUTTON_WIDTH, command=lambda: self.button_press("3"), ).grid(row=4, column=2, sticky=STICKY) - tk.Button( buttonframe, text="A", @@ -151,25 +148,22 @@ def __init__(self, titrator): width=BUTTON_WIDTH, command=lambda: self.button_press("4"), ).grid(row=5, column=0, sticky=STICKY) - tk.Button( buttonframe, text="5", width=BUTTON_WIDTH, command=lambda: self.button_press("5"), ).grid(row=5, column=1, sticky=STICKY) - tk.Button( buttonframe, text="6", width=BUTTON_WIDTH, command=lambda: self.button_press("6"), ).grid(row=5, column=2, sticky=STICKY) - tk.Button( buttonframe, text="B", - width=8, + width=BUTTON_WIDTH, command=lambda: self.button_press("B"), ).grid(row=5, column=3, sticky=STICKY) @@ -179,21 +173,18 @@ def __init__(self, titrator): width=BUTTON_WIDTH, command=lambda: self.button_press("7"), ).grid(row=6, column=0, sticky=STICKY) - tk.Button( buttonframe, text="8", width=BUTTON_WIDTH, command=lambda: self.button_press("8"), ).grid(row=6, column=1, sticky=STICKY) - tk.Button( buttonframe, text="9", width=BUTTON_WIDTH, command=lambda: self.button_press("9"), ).grid(row=6, column=2, sticky=STICKY) - tk.Button( buttonframe, text="C", @@ -207,21 +198,18 @@ def __init__(self, titrator): width=BUTTON_WIDTH, command=lambda: self.button_press("*"), ).grid(row=7, column=0, sticky=STICKY) - tk.Button( buttonframe, text="0", width=BUTTON_WIDTH, command=lambda: self.button_press("0"), ).grid(row=7, column=1, sticky=STICKY) - tk.Button( buttonframe, text="#", width=BUTTON_WIDTH, command=lambda: self.button_press("#"), ).grid(row=7, column=2, sticky=STICKY) - tk.Button( buttonframe, text="D", @@ -232,53 +220,102 @@ def __init__(self, titrator): buttonframe.grid(row=1, column=0, sticky=STICKY) # --------------------------------------------------------- - # RIGHT SIDE - BOARD LED + # RIGHT SIDE - BOARD LED AND HEATER/CHILLER # --------------------------------------------------------- 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) + # --------------------------------------------------------- + # BOARD LED + # --------------------------------------------------------- + + led_frame = tk.Frame(right_frame, bg="darkgray") + led_frame.grid(row=0, column=0, sticky=tk.N, padx=5, pady=10) self.led_canvas = tk.Canvas( - right_frame, - width=80, - height=80, - bg="darkgray", - highlightthickness=0, + led_frame, width=40, height=40, bg="darkgray", highlightthickness=0 ) - self.led_canvas.grid(row=0, column=0, padx=5, pady=10, sticky=tk.NE) + self.led_canvas.grid(row=0, column=0) self.led_circle = self.led_canvas.create_oval( - 4, - 4, - 30, - 30, - fill="red", - outline="black", - width=3, + 3, 3, 30, 30, fill="red", outline="black", width=3 ) self.led_label = tk.Label( - right_frame, - text="Board LED", - font=("Arial", 12), + led_frame, text="Board LED", font=("Arial", 12), bg="darkgray", fg="black" + ) + self.led_label.grid(row=0, column=1, padx=5, sticky=tk.W) + + # --------------------------------------------------------- + # HEATER / CHILLER SLIDING HIGHLIGHT BAR + # --------------------------------------------------------- + + device_frame = tk.Frame(right_frame, bg="darkgray") + device_frame.grid(row=1, column=0, sticky=tk.N, padx=5, pady=10) + + self.switch_canvas = tk.Canvas( + device_frame, + width=120, + height=40, bg="darkgray", - fg="black", + highlightthickness=0, ) - self.led_label.grid(row=0, column=1, padx=2, pady=10, sticky=tk.NW) + self.switch_canvas.grid(row=0, column=0) - # Start GUI update thread - self.thread = threading.Thread( - target=self.update_gui, - daemon=True, + # Background bar + self.switch_canvas.create_rectangle( + 5, 15, 115, 25, fill="#444444", outline="white", width=2 + ) + + # Sliding highlight bar + self.highlight = self.switch_canvas.create_rectangle( + 5, + 15, + 60, + 25, # default = CHILLER ON + fill="lightblue", + outline="white", + width=2, ) + + # Labels + self.switch_canvas.create_text( + 32, 32, text="CHILL", fill="white", font=("Arial", 9, "bold") + ) + self.switch_canvas.create_text( + 90, 32, text="HEAT", fill="white", font=("Arial", 9, "bold") + ) + + # Animation state + self.highlight_x = 5 + self.highlight_target = 5 + self.last_heater_state = None + + # Start GUI update thread + self.thread = threading.Thread(target=self.update_gui, daemon=True) self.thread.start() self.root.mainloop() + def animate_switch(self): + """Smooth sliding animation for heater/chiller highlight bar.""" + speed = 2 # pixels per frame + + if self.highlight_x < self.highlight_target: + self.highlight_x += speed + elif self.highlight_x > self.highlight_target: + self.highlight_x -= speed + + # Update highlight bar position + self.switch_canvas.coords( + self.highlight, self.highlight_x, 15, self.highlight_x + 55, 25 + ) + + # Continue animation until target reached + if abs(self.highlight_x - self.highlight_target) > 1: + self.root.after(10, self.animate_switch) + def button_press(self, key): """ The function to facilitate button presses @@ -287,7 +324,7 @@ def button_press(self, key): def update_gui(self): """ - The function to update the GUI LCD and LED + The function to update the GUI LCD, LED, and Heater/Chiller """ while True: time.sleep(0.001) @@ -309,13 +346,27 @@ def update_gui(self): anchor=self.titrator.lcd.get_style(4), ) + # Update Board LED if self.titrator.led.is_on: - self.led_canvas.itemconfig( - self.led_circle, - fill="yellow", - ) + self.led_canvas.itemconfig(self.led_circle, fill="yellow") else: - self.led_canvas.itemconfig( - self.led_circle, - fill="black", - ) + self.led_canvas.itemconfig(self.led_circle, fill="black") + + # --------------------------------------------------------- + # UPDATE HEATER / CHILLER SLIDING HIGHLIGHT BAR + # --------------------------------------------------------- + + heater_on = self.titrator.thermal_control.get_heat(True) + + # Only animate when state changes + if heater_on != self.last_heater_state: + if heater_on: + self.highlight_target = 60 + self.switch_canvas.itemconfig(self.highlight, fill="orange") + else: + self.highlight_target = 5 + self.switch_canvas.itemconfig(self.highlight, fill="lightblue") + + self.animate_switch() + + self.last_heater_state = heater_on From d3903b4ada7e154a4b1444843f3099dc41b1a959 Mon Sep 17 00:00:00 2001 From: Lidya Langana Date: Fri, 11 Sep 2026 09:18:01 -0700 Subject: [PATCH 10/11] Add Pico LCD + touch README and test files --- src/demos/README.md | 163 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 163 insertions(+) create mode 100644 src/demos/README.md diff --git a/src/demos/README.md b/src/demos/README.md new file mode 100644 index 0000000..dd549ae --- /dev/null +++ b/src/demos/README.md @@ -0,0 +1,163 @@ +# Raspberry Pi Pico + LCD + +MicroPython setup, wiring, drivers, and test scripts + +## 1. Install MicroPython on Raspberry Pi Pico + +1. Download the MicroPython UF2 for Pico: + [https://micropython.org/download/rp2-pico](https://micropython.org/download/rp2-pico) +2. Hold **BOOTSEL** while plugging in the Pico. +3. Drag the `.uf2` file onto the Pico drive. +4. Pico reboots into MicroPython. + +--- + +## 2. Using Raspberry Pi Pico in VS Code (MicroPython) + +### Steps + +1. Connect Pico via USB +2. Open VS Code +3. Press **Ctrl + Shift + P** +4. Select **Pico: Configure Project** +5. Choose the correct COM port +6. Open REPL +7. Upload your `.py` files to the Pico +8. Run scripts directly from VS Code or REPL + +--- + +## 3. Wiring (Pico → LCD + Touch) + +### Pin Mapping Table + +| **Pico Pin** | **LCD Pin** | **Signal** | +| ------------- | ----------- | ------------------------- | +| GP5 (Pin 5) | 5 | LCD_RS | +| GP6 (Pin 6) | 12 | SDA (Touch) | +| GP7 (Pin 7) | 10 | SCL (Touch) | +| GP12 (Pin 12) | 4 | LCD_RST | +| GP14 (Pin 14) | 7 | SCK (SPI) | +| GP15 (Pin 15) | 6 | MOSI (SPI) | +| GP16 (Pin 16) | 9 | MISO (SPI) | +| GND (Pin 18) | 2 | GND | +| GP20 (Pin 20) | 3 | LCD_CS | +| GP31 (Pin 31) | 13 | CTS_INT (Touch Interrupt) | +| 3.3V (Pin 36) | 11 | +3.3V | +| 5V (Pin 39) | 1 | +5V | + +--- + +## 4. Drivers + +### ILI9341 Display Driver + +Save as: **`ili9341.py`** + +**Source link:** + [https://github.com/rdagger/micropython-ili9341](https://github.com/rdagger/micropython-ili9341) + +--- + +### FT6336U Touch Driver + +Save as: **`uFT6336U.py`** + +**Source link:** + [https://github.com/fantasticdonkey/uFT6336U](https://github.com/fantasticdonkey/uFT6336U) + +--- + +## 5. Test Code + +### **Test 1 — Shapes Demo (LCD + Touch Init)** + +```python +from time import sleep +from ili9341 import Display, color565 +from machine import Pin, SPI, I2C +import uFT6336U + + +def test(): + spi = SPI(1, baudrate=40000000, sck=Pin(14), mosi=Pin(15), miso=Pin(16)) + display = Display(spi, dc=Pin(5), cs=Pin(20), rst=Pin(12)) + + i2c_bus = I2C(0, sda=Pin(6), scl=Pin(7), freq=400000) + touch = uFT6336U.FT6336U(i2c_bus) + + display.clear(color565(64, 0, 255)) + sleep(1) + display.clear() + + display.draw_hline(10, 319, 229, color565(255, 0, 255)) + sleep(1) + + display.draw_vline(10, 0, 319, color565(0, 255, 255)) + sleep(1) + + display.fill_hrect(23, 50, 30, 75, color565(255, 255, 255)) + sleep(1) + + display.draw_line(127, 0, 64, 127, color565(255, 255, 0)) + sleep(2) + + display.clear() + + coords = [[0, 63], [78, 80], [122, 92], [50, 50], [78, 15], [0, 63]] + display.draw_lines(coords, color565(0, 255, 255)) + sleep(1) + + display.clear() + display.fill_polygon(7, 120, 120, 100, color565(0, 255, 0)) + sleep(1) + + display.fill_rectangle(0, 0, 163, 163, color565(128, 128, 255)) + sleep(1) + + display.draw_rectangle(0, 64, 163, 163, color565(255, 0, 255)) + sleep(1) + + display.fill_circle(132, 132, 70, color565(0, 255, 0)) + sleep(1) + + display.draw_circle(132, 96, 70, color565(0, 0, 255)) + sleep(1) + + display.fill_ellipse(96, 96, 30, 16, color565(255, 0, 0)) + sleep(1) + + display.draw_ellipse(96, 256, 16, 30, color565(255, 255, 0)) + + sleep(5) + display.cleanup() + + +test() +``` + +--- + +### **Test 2 — Touch Polling** + +```python +from machine import I2C, Pin +import time +import uFT6336U + +i2c = I2C(0, scl=Pin(7), sda=Pin(6)) +t = uFT6336U.FT6336U(i2c) + +print("Touch test (polling)") +print("Touch the screen to see coordinates printed in REPL") + +while True: + np = t.get_points() + if np > 0: + print(t.get_p1_x(), t.get_p1_y()) + if np > 1: + print(t.get_p2_x(), t.get_p2_y()) + time.sleep(0.1) +``` + +--- From 36353a6979d234406016348555d676eb0d3e61c6 Mon Sep 17 00:00:00 2001 From: Lidya Langana Date: Fri, 11 Sep 2026 11:29:49 -0700 Subject: [PATCH 11/11] format --- demos/README.md | 10 +++++----- demos/touch_demo.py | 3 ++- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/demos/README.md b/demos/README.md index c1e3c99..c16408d 100644 --- a/demos/README.md +++ b/demos/README.md @@ -12,18 +12,18 @@ MicroPython setup, wiring, drivers, and test scripts --- -## 2. Using Raspberry Pi Pico in VS Code (MicroPython) +## 2. Using Raspberry Pi Pico in Visual Studio Code (MicroPython) ### Steps 1. Connect Pico via USB -2. Open VS Code +2. Open Visual Studio Code 3. Press **Ctrl + Shift + P** 4. Select **Pico: Configure Project** 5. Choose the correct COM port 6. Open REPL 7. Upload your `.py` files to the Pico -8. Run scripts directly from VS Code or REPL +8. Run scripts directly from Visual Studio Code or REPL --- @@ -71,8 +71,8 @@ Save as: **`uFT6336U.py`** ## 5. Test Code 1. Run **`demo_setup.bat`** from the `demos` folder to download the driver files. -2. Connect the Pico to VS Code and upload **`ili9341.py`** and **`uFT6336U.py`** to the Pico. +2. Connect the Pico to Visual Studio Code and upload **`ili9341.py`** and **`uFT6336U.py`** to the Pico. 3. To test the LCD, open **`shapes_demo.py`**, upload it to the Pico, and run the current file. The display should show colored lines, rectangles, polygons, circles, and ellipses. -4. To test the touchscreen, open **`touch_demo.py`**, upload it to the Pico, and run the current file. Touch coordinates should appear in the VS Code REPL. +4. To test the touchscreen, open **`touch_demo.py`**, upload it to the Pico, and run the current file. Touch coordinates should appear in the Visual Studio Code REPL. --- diff --git a/demos/touch_demo.py b/demos/touch_demo.py index 61e0d4d..f78ad4a 100644 --- a/demos/touch_demo.py +++ b/demos/touch_demo.py @@ -1,4 +1,5 @@ -'''uFT6336U demo (touch).''' +"""uFT6336U demo (touch).""" + import time import uFT6336U