Sending updates to external screen + RGB LCD

This commit is contained in:
2026-08-16 14:23:18 +02:00
parent 2276c1742a
commit fd00c9a89d
11 changed files with 367 additions and 24 deletions
+54 -8
View File
@@ -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,