From fd00c9a89d86e48a568521b56c1bace0061892a0 Mon Sep 17 00:00:00 2001 From: Matthias Guillitte Date: Sun, 16 Aug 2026 14:23:18 +0200 Subject: [PATCH] Sending updates to external screen + RGB LCD --- ecran_externe/display_api.h | 2 +- micro_ondes/esp_lora/main.py | 62 ++++++++-- orchestrateur/compose.yaml | 2 +- orchestrateur/external_display_manager.py | 13 +- orchestrateur/main.py | 62 ++++++++-- orchestrateur/microwave_state.py | 2 +- orchestrateur/rgb_lcd_manager.py | 138 ++++++++++++++++++++++ orchestrateur/sensors/__init__.py | 3 +- orchestrateur/sensors/rgb_lcd.py | 102 ++++++++++++++++ shared/__init__.py | 4 +- shared/lora_device.py | 1 + 11 files changed, 367 insertions(+), 24 deletions(-) create mode 100644 orchestrateur/rgb_lcd_manager.py create mode 100644 orchestrateur/sensors/rgb_lcd.py diff --git a/ecran_externe/display_api.h b/ecran_externe/display_api.h index 16fd18e..0f0bde4 100644 --- a/ecran_externe/display_api.h +++ b/ecran_externe/display_api.h @@ -17,7 +17,7 @@ struct MicrowaveData { // Global states parsed from the API static std::vector g_microwaves; -static bool g_cloud_alert = false; +static bool g_cloud_alert = true; static std::string g_update_time = "--:--"; static bool g_received_update = false; diff --git a/micro_ondes/esp_lora/main.py b/micro_ondes/esp_lora/main.py index 9780a70..03205fe 100644 --- a/micro_ondes/esp_lora/main.py +++ b/micro_ondes/esp_lora/main.py @@ -28,6 +28,7 @@ except Exception: # --- GLOBAL VARIABLES --- cooking_state = None +cooking_start_time = None # Track start timestamp data_queue = SafeQueue() lora = None uart_device = None @@ -46,6 +47,31 @@ PING_PAYLOAD = { "type": deviceTypes.DEVICE_TYPES["MICROWAVE"] } + +def send_cooking_update(state=None, start_time=None, estimated_remaining_time=None, paused=None): + """Sends a flexible COOKING_UPDATE payload containing only populated fields.""" + if not lora: + return + + payload = { + "id": DEVICE_ID, + "action": LoraCommands.COOKING_UPDATE + } + + if state is not None: + payload["cooking_state"] = state + if estimated_remaining_time is not None: + payload["estimated_remaining_time"] = estimated_remaining_time + if paused is not None: + payload["paused"] = paused + + # Only transmit if at least one field beyond id and action was provided + if len(payload) > 2: + log(f"[LoRa] Sending COOKING_UPDATE payload: {payload}") + + lora.send(payload) + + def on_new_alert(alert: Alert): global microwave_screen print(f"[Alert Manager] New alert received: {alert.to_dict()}") @@ -138,9 +164,7 @@ def cooking_state_temperature_provider(): # 3. Handle fresh incoming reading if not temp_is_invalid(current_temp): temps = [float(current_temp[0]), float(current_temp[1])] - last_temp = [temps[0], temps[1]] # Keep a safe reference copy - - # Reset current_temp buffer to consume the value + last_temp = [temps[0], temps[1]] current_temp = [None, None] return temps @@ -152,6 +176,7 @@ def cooking_state_temperature_provider(): return (0.0, 0.0) def cooking_state_on_state_change(state): + global cooking_start_time print(f"[CookingState] State changed to: {state.state}") if state.paused or state.state == cookingState.CookingStates.DONE or state.state == cookingState.CookingStates.IDLE: @@ -162,8 +187,14 @@ def cooking_state_on_state_change(state): # Send state updates to WiFi board and Orchestrator if uart_device: uart_device.send_as_command(UARTCommand(UARTCommandType.COOKING_STATE_UPDATE, {"state": state.state})) - if lora: - lora.send_reliable({"id": DEVICE_ID, "new_cooking_state": state.state}) + + # Send LoRa update with start time and remaining time if beginning cooking process + send_cooking_update( + state=state.state, + start_time=cooking_start_time if state.state == cookingState.CookingStates.COOKING else None, + estimated_remaining_time=state.get_remaining_time() if hasattr(state, "get_remaining_time") else None, + paused=state.paused + ) microwave_screen.update(cooking_state, defrost_mode) @@ -176,14 +207,20 @@ def cooking_state_on_pause(state): # If the cooking is unpaused and was in STIRRING_REQUIRED or ALERT state, we set the state back to COOKING. if not state.paused and (state.state == cookingState.CookingStates.STIRRING_REQUIRED or state.state == cookingState.CookingStates.ALERT): state.set_state(cookingState.CookingStates.COOKING) - # TODO: send_reliable lora message to orchestrator about pause/resume state + + # Send update on pause/resume toggle + send_cooking_update( + state=state.state, + paused=state.paused, + estimated_remaining_time=state.get_remaining_time() + ) # --- ASYNC TASKS --- async def uart_polling_task(): """Polls UART for incoming messages from the WiFi board.""" - global cooking_state, temperature_asked + global cooking_state, cooking_start_time, temperature_asked while True: if uart_device and uart_device.any(): @@ -196,6 +233,9 @@ async def uart_polling_task(): uart_device.send_as_command(UARTCommand(UARTCommandType.TEMPERATURE_REQUEST, {})) + # Track start time timestamp + cooking_start_time = time.time() + cooking_state = cookingState.CookingState( cook_time=params["cook_time"], power_level=params["power_level"], @@ -271,10 +311,16 @@ async def cooking_loop_task(): cooking_state.update_tick() log_counter += 1 - # Print log output every 5 seconds (10 ticks x 500ms) + # Print log output and send periodic updates every 5 seconds (10 ticks x 500ms) if log_counter % 10 == 0: print(f"[Cooking Task] State: {cooking_state.state}, Temp: {cooking_state.current_dish_temp}, " f"Paused: {cooking_state.paused}, Remaining: {cooking_state.get_remaining_time():.2f}s") + + # Send periodic time remaining update while actively cooking + if not cooking_state.paused and cooking_state.state == cookingState.CookingStates.COOKING: + send_cooking_update( + estimated_remaining_time=cooking_state.get_remaining_time() + ) await asyncio.sleep_ms(500) diff --git a/orchestrateur/compose.yaml b/orchestrateur/compose.yaml index bfe526f..fca01a9 100644 --- a/orchestrateur/compose.yaml +++ b/orchestrateur/compose.yaml @@ -9,7 +9,7 @@ services: - NET_ADMIN environment: - OT_RCP_DEVICE=spinel+hdlc+uart:///dev/ttyUSB1?uart-baudrate=460800 - - OT_INFRA_IF=wlan0 + - OT_INFRA_IF=wlx6815790f3204 - OT_THREAD_IF=wpan0 - OT_LOG_LEVEL=6 - FIREWALL=0 diff --git a/orchestrateur/external_display_manager.py b/orchestrateur/external_display_manager.py index 3e38745..2598ed9 100644 --- a/orchestrateur/external_display_manager.py +++ b/orchestrateur/external_display_manager.py @@ -14,6 +14,7 @@ class DisplayManager: self.screens: Dict[str, str] = {} # Map: service_name -> URL self.aiozc: AsyncZeroconf | None = None self.browser: AsyncServiceBrowser | None = None + self._on_screens_change_callback = None # Callback for screen changes async def start(self): """Starts dynamic mDNS discovery for e-Paper screens.""" @@ -48,12 +49,20 @@ class DisplayManager: url = f"http://{ip_str}:{port}{self.endpoint_path}" self.screens[name] = url + if self._on_screens_change_callback: + self._on_screens_change_callback(self.screens) print(f"[DisplayManager] 📺 Screen registered: {name} -> {url}") elif state_change == ServiceStateChange.Removed: if name in self.screens: print(f"[DisplayManager] ❌ Screen disconnected: {name}") self.screens.pop(name, None) + if self._on_screens_change_callback: + self._on_screens_change_callback(self.screens) + + def set_on_screens_change_callback(self, callback): + """Set a callback function to be called when screens are added or removed.""" + self._on_screens_change_callback = callback async def broadcast_state(self, microwave_states: dict, cloud_alert: bool): """Broadcasts the system state JSON to all discovered screens concurrently.""" @@ -113,9 +122,9 @@ class DisplayManager: timeout=3.0 ) response.raise_for_status() - print(f"[DisplayManager] ✅ Updated {name}") + print(f"[DisplayManager] Updated {name}") except Exception as e: - print(f"[DisplayManager] ⚠️ Error pushing state to {name}: {e}") + print(f"[DisplayManager] Error pushing state to {name}: {e}") async def stop(self): """Clean up mDNS browser and Zeroconf instance.""" diff --git a/orchestrateur/main.py b/orchestrateur/main.py index dd04ac8..1340454 100644 --- a/orchestrateur/main.py +++ b/orchestrateur/main.py @@ -11,9 +11,13 @@ from shared.logging import log from shared.cookingState import CookingStates from shared.lora_device import LoraCommands from shared.alerts import AlertManager, Alert, AlertType -from sensors import ultrasonicRanger, temp_hum, button, camera, buzzer +from sensors import ultrasonicRanger, temp_hum, button, camera, buzzer, rgb_lcd from lib.systemd_logs import get_systemd_logs from external_display_manager import DisplayManager +from rgb_lcd_manager import RGBLCDManager + +rgb_lcd.setRGB(255, 255, 255, brightness=0.8) +rgb_lcd.setText("SmartWave\nOrchestrateur") # --- CONFIGURATION CONSTANTS --- DISPLAY_DEBOUNCE_SECONDS = 3 # Seconds to wait after first change before broadcasting @@ -69,6 +73,7 @@ display_update_queue = None def notify_display_update(): """Safely adds an update event timestamp to the display update queue.""" + if display_update_queue is not None: try: loop = asyncio.get_running_loop() @@ -79,18 +84,21 @@ def notify_display_update(): def set_cloud_alert(state: bool): """Setter for cloud_alert that triggers a display update when changed.""" global cloud_alert + if cloud_alert != state: cloud_alert = state notify_display_update() + update_lcd_cloud_alert() # Global state trackers microwave_states = {"2": MicrowaveState("2", on_change_callback=notify_display_update)} button_state = False -cloud_alert = False # Global status flag for screen / UI display +cloud_alert = None # Global status flag for screen / UI display async_event_queue = None # Display Manager instance display_manager = DisplayManager(endpoint_path="/api/display") +rgb_lcd_manager = RGBLCDManager(rgb_lcd, brightness=0.8, interval=5.0) # Async synchronization trackers for MQTT IR sensors responses ir_data_cache = {} # mw_id -> dict of IR readings @@ -112,6 +120,10 @@ mqtt_client.connect() mqtt_client.subscribe(config.MQTT_TOPIC_SENSOR, qos=config.MQTT_QOS) mqtt_client.subscribe(config.MQTT_TOPIC_HELLO, qos=config.MQTT_QOS) +# DHT sensor warmup +time.sleep(2) # Allow GrovePi MCU and DHT sensor circuits to settle post-service start +temp_hum.get_temperature_and_humidity() # Dummy read to flush initial NaN state + if hasattr(mqtt_client._client, "loop_start"): mqtt_client._client.loop_start() print("[MQTT Local] Paho background loop started.") @@ -192,6 +204,23 @@ async def cloud_mqtt_listener_task(): "data": payload }) await asyncio.sleep(0.1) +# === LCD DISPLAY === +def on_screens_change(screens): + """Callback for when screens are added or removed.""" + log(f"[DisplayManager] Screens changed. Current screens: {list(screens.keys())}") + rgb_lcd_manager.set_external_screen_count(len(screens)) +display_manager.set_on_screens_change_callback(on_screens_change) + +def update_lcd_microwave_count(): + """Updates the LCD with the current number of connected microwaves.""" + global microwave_states + count = len(microwave_states) + rgb_lcd_manager.set_microwave_count(count) + +def update_lcd_cloud_alert(): + """Updates the LCD with the current cloud connectivity status.""" + global cloud_alert + rgb_lcd_manager.set_cloud_alert(cloud_alert) async def display_broadcast_worker_task(): """ @@ -199,6 +228,8 @@ async def display_broadcast_worker_task(): Waits until DISPLAY_DEBOUNCE_SECONDS have passed since the first queued update before sending state to all connected screens. """ + global display_update_queue, microwave_states, cloud_alert + print("[Display Worker] Started debounced display broadcast worker.") while True: # Block until the FIRST update reminder arrives in the queue @@ -331,7 +362,7 @@ async def handle_new_dish(microwave_id, detected_height): # 6. Dispatch cloud request task asyncio.create_task(request_cloud_cooking_plan(microwave_id, sensors_data)) except asyncio.TimeoutError: - print(f"[{microwave_id}] ⚠️ Timeout waiting for MQTT IR data from ESP32.") + print(f"[{microwave_id}] Timeout waiting for MQTT IR data from ESP32.") sensors_data["ir_initial_temp"] = None sensors_data["ir_ambient_temp"] = None finally: @@ -376,6 +407,8 @@ async def request_cloud_cooking_plan(microwave_id, sensors_data): return response.raise_for_status() + + set_cloud_alert(False) # Clear any previous cloud alert # JSON extraction res_json = response.json() @@ -400,12 +433,11 @@ async def request_cloud_cooking_plan(microwave_id, sensors_data): c_temp = plan.get("target_temp") if c_time is None or c_power is None or c_temp is None: - print(f"[{microwave_id}] ❌ Invalid plan received: {res_json}") + print(f"[{microwave_id}] Invalid plan received: {res_json}") microwave_states[microwave_id].set_state(MicrowaveState.IDLE) return print(f"[{microwave_id}] Cloud Plan Received! Starting microwave: {c_time}s @ {c_power}W") - cloud_alert = False # Reset alert flag on success microwave_states[microwave_id].set_state(MicrowaveState.COOKING) if config.DEBUG: @@ -417,6 +449,8 @@ async def request_cloud_cooking_plan(microwave_id, sensors_data): payloads.mqtt_cooking_config(microwave_id, c_time, c_power, c_temp), qos=config.MQTT_QOS ) + + microwave_states[microwave_id].set_cooking_start_time(time.time()) return except Exception as e: @@ -436,7 +470,7 @@ async def request_cloud_cooking_plan(microwave_id, sensors_data): # Executed only if all 3 retries failed print(f"[{microwave_id}] All cloud retries failed. Setting global alert flag.") - cloud_alert = True + set_cloud_alert(True) microwave_states[microwave_id].set_state(MicrowaveState.DONE) # --- MAIN LOGIC TASKS --- @@ -458,6 +492,7 @@ async def process_messages_task(): print(f"[LoRa] Microwave {mw_id} state changed to: {n_state}") microwave_states[mw_id].set_cooking_state(n_state) + update_lcd_microwave_count() # Update the LCD with new microwave count if n_state in (CookingStates.DONE, CookingStates.STIRRING_REQUIRED, CookingStates.ALERT): beats = 5 if n_state == CookingStates.ALERT else (1 if n_state == CookingStates.STIRRING_REQUIRED else 3) @@ -465,7 +500,17 @@ async def process_messages_task(): if n_state == CookingStates.IDLE and microwave_states.get(mw_id).state == MicrowaveState.COOKING: microwave_states[mw_id].set_state(MicrowaveState.DONE) print(f"[{mw_id}] Cooking finished. Waiting for user to remove dish.") - + if "action" in data.get("data", {}): + action = data["data"]["action"] + if action == LoraCommands.COOKING_UPDATE: + mw_id = data["data"].get("id") + if mw_id in microwave_states: + state_info = data["data"] + microwave_states[mw_id].set_cooking_state(state_info.get("cooking_state", microwave_states[mw_id].cooking_state)) + microwave_states[mw_id].set_cooking_estimated_remaining_time(state_info.get("estimated_remaining_time", microwave_states[mw_id].cooking_estimated_remaining_time)) + microwave_states[mw_id].set_paused(state_info.get("paused", microwave_states[mw_id].paused)) + else: + print(f"[LoRa] Unhandled action received: {action} with data: {data['data']}") elif source == "MQTT": topic = msg["topic"] @@ -487,7 +532,8 @@ async def process_messages_task(): component_id, on_change_callback=notify_display_update ) - notify_display_update() + notify_display_update() # Update the external screens + update_lcd_microwave_count() # Update the LCD with new microwave count mqtt_client.publish( config.MQTT_TOPIC_HELLO, diff --git a/orchestrateur/microwave_state.py b/orchestrateur/microwave_state.py index 991256c..9014d51 100644 --- a/orchestrateur/microwave_state.py +++ b/orchestrateur/microwave_state.py @@ -34,7 +34,7 @@ class MicrowaveState: if self.state != new_state: self.state = new_state - self._notify_change() + # self._notify_change() def set_paused(self, is_paused): if self.paused != is_paused: diff --git a/orchestrateur/rgb_lcd_manager.py b/orchestrateur/rgb_lcd_manager.py new file mode 100644 index 0000000..c48b871 --- /dev/null +++ b/orchestrateur/rgb_lcd_manager.py @@ -0,0 +1,138 @@ +""" +Manages the RGB LCD display with the current state of the microwaves and cloud connectivity. + +The display switches content every x seconds between microwaves, external screens, +and cloud connectivity state using a background thread. +""" + +import threading +import time + +SCREEN_SWITCH_INTERVAL = 5 # seconds +DEFAULT_BRIGHTNESS = 0.3 # Constant brightness (range 0.0 to 1.0) + + +class RGBLCDManager: + def __init__(self, rgb_lcd, interval=SCREEN_SWITCH_INTERVAL, brightness=DEFAULT_BRIGHTNESS, auto_start=True): + self.rgb_lcd = rgb_lcd + self.interval = interval + self.brightness = max(0.0, min(1.0, float(brightness))) + + # Internal state store + self._microwave_count = "N/A" + self._external_screen_count = "N/A" + self._cloud_alert = None + + # Screen rotation sequence + self.screens = [ + self.display_microwave_state, + self.display_external_screens, + self.display_cloud_connectivity, + ] + self.current_screen_idx = 0 + + # Threading controls + self._thread = None + self._running = False + self._stop_event = threading.Event() + + if auto_start: + self.start() + + # --- SETTER METHODS --- + + def set_brightness(self, brightness): + """Dynamically adjust backlight brightness (0.0 to 1.0).""" + self.brightness = max(0.0, min(1.0, float(brightness))) + + def set_microwave_count(self, count): + """Set the number of connected microwaves.""" + self._microwave_count = count + + def set_external_screen_count(self, count): + """Set the number of connected external screens.""" + self._external_screen_count = count + + def set_cloud_alert(self, status: bool): + """Set cloud connectivity status.""" + self._cloud_alert = status + + def set_data(self, microwave_count=None, external_screen_count=None, cloud_alert=None): + """Convenience method to update all state variables at once.""" + if microwave_count is not None: + self.set_microwave_count(microwave_count) + if external_screen_count is not None: + self.set_external_screen_count(external_screen_count) + if cloud_alert is not None: + self.set_cloud_alert(cloud_alert) + + # --- DISPLAY RENDERERS --- + + def display_microwave_state(self): + """Display the number of connected microwaves.""" + text = f"Microwaves:\nConnected: {self._microwave_count}" + self.rgb_lcd.setText(text) + self.rgb_lcd.setRGB(255, 255, 255, brightness=self.brightness) + + def display_external_screens(self): + """Display the number of connected external screens.""" + text = f"Screens:\nConnected: {self._external_screen_count}" + self.rgb_lcd.setText(text) + self.rgb_lcd.setRGB(255, 255, 255, brightness=self.brightness) + + def display_cloud_connectivity(self): + """Display cloud connectivity status with brightness-scaled backlight color.""" + status_str = None + + # Base RGB colors + if self._cloud_alert is True: + rgb = (255, 0, 0) # Red + status_str = "Disconnected" + elif self._cloud_alert is False: + rgb = (0, 255, 0) # Green + status_str = "Connected" + else: + rgb = (255, 165, 0) # Orange + status_str = "N/A" + + self.rgb_lcd.setRGB(rgb[0], rgb[1], rgb[2], brightness=self.brightness) + self.rgb_lcd.setText(f"Cloud Status:\n{status_str}") + + # --- BACKGROUND THREAD LOGIC --- + + def _display_loop(self): + """Background loop that switches screens at regular intervals.""" + while not self._stop_event.is_set(): + try: + self.screens[self.current_screen_idx]() + self.current_screen_idx = (self.current_screen_idx + 1) % len(self.screens) + except Exception as e: + print(f"[RGBLCDManager] Error updating LCD: {e}") + + sleep_ticks = int(self.interval * 10) + for _ in range(sleep_ticks): + if self._stop_event.is_set(): + break + time.sleep(0.1) + + def start(self): + """Start the display background thread.""" + if not self._running: + self._running = True + self._stop_event.clear() + self._thread = threading.Thread(target=self._display_loop, daemon=True) + self._thread.start() + + def stop(self): + """Stop the display loop and turn off the LCD backlight.""" + if self._running: + self._stop_event.set() + if self._thread and self._thread.is_alive(): + self._thread.join(timeout=2.0) + self._running = False + + try: + self.rgb_lcd.setText("Bye !") + self.rgb_lcd.setRGB(0, 0, 0) + except Exception as e: + print(f"[RGBLCDManager] Error clearing LCD: {e}") \ No newline at end of file diff --git a/orchestrateur/sensors/__init__.py b/orchestrateur/sensors/__init__.py index dd0a856..4f3405b 100644 --- a/orchestrateur/sensors/__init__.py +++ b/orchestrateur/sensors/__init__.py @@ -5,4 +5,5 @@ import sensors.temp_hum as temp_hum import sensors.button as button import sensors.gps as gps import sensors.camera as camera -import sensors.buzzer as buzzer \ No newline at end of file +import sensors.buzzer as buzzer +import sensors.rgb_lcd as rgb_lcd \ No newline at end of file diff --git a/orchestrateur/sensors/rgb_lcd.py b/orchestrateur/sensors/rgb_lcd.py new file mode 100644 index 0000000..3524245 --- /dev/null +++ b/orchestrateur/sensors/rgb_lcd.py @@ -0,0 +1,102 @@ +import time +import sys +from sensors.lock import grove_lock + +if sys.platform == 'uwp': + import winrt_smbus as smbus + bus = smbus.SMBus(1) +else: + import smbus + import RPi.GPIO as GPIO + rev = GPIO.RPI_REVISION + if rev == 2 or rev == 3: + bus = smbus.SMBus(1) + else: + bus = smbus.SMBus(0) + +# Device I2C addresses +DISPLAY_RGB_ADDR = 0x62 +DISPLAY_TEXT_ADDR = 0x3e + + +def setRGB(r, g, b, brightness=1.0): + """Set backlight to (R,G,B) with optional brightness level (0.0 to 1.0).""" + brightness = max(0.0, min(1.0, float(brightness))) + + r_scaled = int(max(0, min(255, r * brightness))) + g_scaled = int(max(0, min(255, g * brightness))) + b_scaled = int(max(0, min(255, b * brightness))) + + with grove_lock: + try: + bus.write_byte_data(DISPLAY_RGB_ADDR, 0, 0) + bus.write_byte_data(DISPLAY_RGB_ADDR, 1, 0) + bus.write_byte_data(DISPLAY_RGB_ADDR, 0x08, 0xaa) + bus.write_byte_data(DISPLAY_RGB_ADDR, 4, r_scaled) + bus.write_byte_data(DISPLAY_RGB_ADDR, 3, g_scaled) + bus.write_byte_data(DISPLAY_RGB_ADDR, 2, b_scaled) + except OSError: + pass + + +def textCommand(cmd): + """Send command to display (internal use).""" + bus.write_byte_data(DISPLAY_TEXT_ADDR, 0x80, cmd) + + +def setText(text): + """Set display text (\n for second line or auto wrap).""" + with grove_lock: + try: + textCommand(0x01) # Clear display + time.sleep(0.05) + textCommand(0x08 | 0x04) # Display on, no cursor + textCommand(0x28) # 2 lines + time.sleep(0.05) + + count = 0 + row = 0 + for c in text: + if c == '\n' or count == 16: + count = 0 + row += 1 + if row == 2: + break + textCommand(0xc0) + if c == '\n': + continue + count += 1 + bus.write_byte_data(DISPLAY_TEXT_ADDR, 0x40, ord(c)) + time.sleep(0.001) # Small pacing delay to prevent LCD buffer overflow + except OSError: + pass + + +def setText_norefresh(text): + """Update display text without full screen erase.""" + with grove_lock: + try: + textCommand(0x02) # Return home + time.sleep(0.05) + textCommand(0x08 | 0x04) # Display on, no cursor + textCommand(0x28) # 2 lines + time.sleep(0.05) + + count = 0 + row = 0 + while len(text) < 32: # Clear rest of screen space + text += ' ' + for c in text: + if c == '\n' or count == 16: + count = 0 + row += 1 + if row == 2: + break + textCommand(0xc0) + if c == '\n': + continue + count += 1 + bus.write_byte_data(DISPLAY_TEXT_ADDR, 0x40, ord(c)) + time.sleep(0.001) # Small pacing delay to prevent LCD buffer overflow + except OSError: + pass \ No newline at end of file diff --git a/shared/__init__.py b/shared/__init__.py index 7875fa0..9432715 100644 --- a/shared/__init__.py +++ b/shared/__init__.py @@ -9,11 +9,11 @@ import shared.safeQueue as safeQueue import shared.alerts as alerts try: import shared.lora_device as lora_device -except ImportError: +except (ImportError, SyntaxError): pass # No need try: import shared.uart_comm as uart_comm -except ImportError: +except (ImportError, SyntaxError): pass # No need as we are on the RPI import shared.sensors diff --git a/shared/lora_device.py b/shared/lora_device.py index 0b3cb09..1002783 100644 --- a/shared/lora_device.py +++ b/shared/lora_device.py @@ -439,6 +439,7 @@ def get_lora_device(port_or_pins=None): class LoraCommands: PING = "ping" COOKING_STATE_UPDATE = "cooking_state_update" + COOKING_UPDATE = "cooking_update" TOGGLE_PAUSE = "toggle_pause" TOGGLE_DEFROST = "toggle_defrost" NEW_ALERT = "new_alert" \ No newline at end of file