This commit is contained in:
2026-08-11 18:16:49 +02:00
parent 1543dde603
commit 07051a7255
6 changed files with 127 additions and 9 deletions
+1 -2
View File
@@ -126,7 +126,6 @@ class MicrowaveScreen:
self.h_centered_text(text, 29, 0) self.h_centered_text(text, 29, 0)
def message(self, text, alert): def message(self, text, alert):
self.flick() # self.flick()
self._message(text, alert) self._message(text, alert)
self.show() self.show()
pass
+17 -2
View File
@@ -17,6 +17,7 @@ from shared.uart_comm import UARTCommand, UARTCommandType
from shared.sensors import RGBLED from shared.sensors import RGBLED
from shared.logging import log from shared.logging import log
from shared.lora_device import LoraCommands from shared.lora_device import LoraCommands
from shared.alerts import Alert, AlertType, AlertManager
# --- READ DEVICE ID --- # --- READ DEVICE ID ---
try: try:
@@ -37,6 +38,7 @@ current_temp = [None, None]
last_temp = [None, None] last_temp = [None, None]
temperature_asked = False temperature_asked = False
last_temp_request_time = 0 last_temp_request_time = 0
alert_manager = None
PING_PAYLOAD = { PING_PAYLOAD = {
@@ -44,6 +46,11 @@ PING_PAYLOAD = {
"type": deviceTypes.DEVICE_TYPES["MICROWAVE"] "type": deviceTypes.DEVICE_TYPES["MICROWAVE"]
} }
def on_new_alert(alert: Alert):
global microwave_screen
print(f"[Alert Manager] New alert received: {alert.to_dict()}")
microwave_screen.message(alert.message, True)
def init_hardware(): def init_hardware():
"""Initializes all hardware components.""" """Initializes all hardware components."""
global lora, uart_device, magnetron_led, microwave_screen global lora, uart_device, magnetron_led, microwave_screen
@@ -247,8 +254,13 @@ async def lora_process_task():
print("[LoRa Process] Toggling defrost mode via orchestrator command.") print("[LoRa Process] Toggling defrost mode via orchestrator command.")
defrost_mode = data["defrost_state"] defrost_mode = data["defrost_state"]
microwave_screen.update(cooking_state, defrost_mode) microwave_screen.update(cooking_state, defrost_mode)
elif data["action"] == LoraCommands.NEW_ALERT:
alert = Alert(data.get("alert_type"), data.get("message", "Unsafe area"))
alert.timestamp = data.get("timestamp", time.time())
print(f"[LoRa Process] New alert received via orchestrator: {alert.to_dict()}")
alert_manager.add_alert(alert)
await asyncio.sleep_ms(50) await asyncio.sleep_ms(100)
async def cooking_loop_task(): async def cooking_loop_task():
@@ -276,11 +288,14 @@ async def memory_cleanup_task():
# --- BOOTSTRAP --- # --- BOOTSTRAP ---
async def main(): async def main():
global cooking_state, defrost_mode, microwave_screen global cooking_state, defrost_mode, microwave_screen, alert_manager
print("[Main] Starting application...") print("[Main] Starting application...")
init_hardware() init_hardware()
alert_manager = AlertManager()
alert_manager.set_on_alert_callback(on_new_alert)
# Launch dedicated hardware thread for LoRa RX # Launch dedicated hardware thread for LoRa RX
try: try:
_thread.stack_size(16 * 1024) _thread.stack_size(16 * 1024)
+48 -4
View File
@@ -9,6 +9,7 @@ from shared import get_lora, get_mqtt_client, deviceTypes, config, payloads, db
from shared.logging import log from shared.logging import log
from shared.cookingState import CookingStates from shared.cookingState import CookingStates
from shared.lora_device import LoraCommands 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
from lib.systemd_logs import get_systemd_logs from lib.systemd_logs import get_systemd_logs
@@ -76,7 +77,6 @@ async_event_queue = None
ir_data_cache = {} # mw_id -> dict of IR readings ir_data_cache = {} # mw_id -> dict of IR readings
ir_data_events = {} # mw_id -> asyncio.Event() ir_data_events = {} # mw_id -> asyncio.Event()
# --- HARDWARE & MQTT SETUP --- # --- HARDWARE & MQTT SETUP ---
lora = get_lora() lora = get_lora()
lora.configure() lora.configure()
@@ -188,6 +188,28 @@ def button_callback():
button.set_callback(button_callback) button.set_callback(button_callback)
button.start_button_monitoring_thread() button.start_button_monitoring_thread()
# --- Alert manager ---
alert_manager = None
def on_new_alert(alert):
# Sends to cloud server
alert_dict = alert.to_dict()
alert_dict["orchestrator_id"] = DEVICE_ID
response = requests.post(
"https://smartwave.matthiasg.dev/alert",
json=alert_dict,
)
response.raise_for_status()
res_json = response.json()
print(f"[Alert Handling] Alert sent to cloud: {alert_dict}, Response: {res_json}")
# Send the alert to the lora device
lora.send_reliable(payloads.lora_new_alert(alert))
# TODO : envoi de l'alerte aux écrans externes
# --- HARDWARE CONTROLLERS --- # --- HARDWARE CONTROLLERS ---
def _stop_hardware(microwave_id: str): def _stop_hardware(microwave_id: str):
print(f"[{microwave_id}] /!\\ Emergency stop issued to hardware.") print(f"[{microwave_id}] /!\\ Emergency stop issued to hardware.")
@@ -268,7 +290,7 @@ async def handle_new_dish(microwave_id, detected_height):
async def request_cloud_cooking_plan(microwave_id, sensors_data): async def request_cloud_cooking_plan(microwave_id, sensors_data):
"""Sends all data to the cloud with up to 3 retries (330s timeout for AI generation).""" """Sends all data to the cloud with up to 3 retries (330s timeout for AI generation)."""
global cloud_alert global cloud_alert, alert_manager
microwave_states[microwave_id] = MicrowaveState.WAITING_FOR_CLOUD microwave_states[microwave_id] = MicrowaveState.WAITING_FOR_CLOUD
URL = "https://smartwave.matthiasg.dev/cooking-params" URL = "https://smartwave.matthiasg.dev/cooking-params"
@@ -306,8 +328,22 @@ async def request_cloud_cooking_plan(microwave_id, sensors_data):
response.raise_for_status() response.raise_for_status()
# Robust JSON extraction: handles direct dict or nested {"cook_plan": {...}} # JSON extraction
res_json = response.json() res_json = response.json()
# Error handling
if "error" in res_json:
print(f"[{microwave_id}] Cloud API returned error: {res_json['error']}")
microwave_states[microwave_id] = MicrowaveState.DONE
if not res_json['is_safe']: # The cooking area is not safe, raise an alert
alert = Alert(AlertType.COOKING_SAFETY, res_json['warning_message'])
alert_manager.add_alert(alert)
else:
alert = Alert(AlertType.ERROR, f"Cloud API error for microwave {microwave_id}: {res_json['error']}")
return
plan = res_json.get("cook_plan", res_json) plan = res_json.get("cook_plan", res_json)
c_time = plan.get("cook_time_seconds") c_time = plan.get("cook_time_seconds")
@@ -316,7 +352,7 @@ async def request_cloud_cooking_plan(microwave_id, sensors_data):
if c_time is None or c_power is None or c_temp is None: 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] = MicrowaveState.DONE microwave_states[microwave_id] = None
return return
print(f"[{microwave_id}] Cloud Plan Received! Starting microwave: {c_time}s @ {c_power}W") print(f"[{microwave_id}] Cloud Plan Received! Starting microwave: {c_time}s @ {c_power}W")
@@ -335,6 +371,9 @@ async def request_cloud_cooking_plan(microwave_id, sensors_data):
return return
except Exception as e: except Exception as e:
# Print stacktrace
import traceback
traceback.print_exc()
print(f"[{microwave_id}] Cloud API Error (Attempt {attempt}/{max_retries}): {e}") print(f"[{microwave_id}] Cloud API Error (Attempt {attempt}/{max_retries}): {e}")
if attempt < max_retries: if attempt < max_retries:
@@ -540,6 +579,11 @@ async def main():
global async_event_queue global async_event_queue
print("Orchestrateur prêt. Lancement des tâches...") print("Orchestrateur prêt. Lancement des tâches...")
# Initialize the alert manager and set the callback
global alert_manager
alert_manager = AlertManager()
alert_manager.set_on_alert_callback(on_new_alert)
# Initialize SQLite database table # Initialize SQLite database table
init_db() init_db()
+1
View File
@@ -6,6 +6,7 @@ import shared.config as config
import shared.payloads as payloads import shared.payloads as payloads
import shared.cookingState as cookingState import shared.cookingState as cookingState
import shared.safeQueue as safeQueue import shared.safeQueue as safeQueue
import shared.alerts as alerts
try: try:
import shared.lora_device as lora_device import shared.lora_device as lora_device
except ImportError: except ImportError:
+47
View File
@@ -0,0 +1,47 @@
import time
class AlertManager:
def __init__(self):
self.alerts = []
self.on_alert_callback = None
def set_on_alert_callback(self, callback):
self.on_alert_callback = callback
def add_alert(self, alert):
self.alerts.append(alert)
if self.on_alert_callback:
self.on_alert_callback(alert)
def get_alerts(self):
return self.alerts
def clear_alerts(self):
self.alerts.clear()
class Alert:
def __init__(self, alertType: "AlertType", message: str):
self.alertType = alertType
self.message = message
self.timestamp = time.time()
def to_dict(self):
return {
"type": self.alertType,
"message": self.message,
"timestamp": self.timestamp
}
def to_json(self):
import json
return json.dumps(self.to_dict())
def from_json(json_string: str) -> "Alert":
import json
data = json.loads(json_string)
alert = Alert(data["type"], data["message"])
alert.timestamp = data["timestamp"]
return alert
class AlertType:
COOKING_SAFETY = "COOKING_SAFETY"
+13 -1
View File
@@ -1,5 +1,11 @@
from time import time from time import time
from shared.deviceTypes import DEVICE_TYPES from shared.deviceTypes import DEVICE_TYPES
from shared.alerts import Alert, AlertType
import shared.config as config
try:
from shared.lora_device import LoraCommands
except ImportError:
pass # No need
try: try:
@@ -65,4 +71,10 @@ def telemetry_payload(device_id, microwave_states, button_state, cloud_alert, gp
"ambient_humidity": ambient_humidity, "ambient_humidity": ambient_humidity,
"connected_components": connected_components, "connected_components": connected_components,
"logs": logs "logs": logs
}) })
def lora_new_alert(alert: Alert):
return {
"action": LoraCommands.NEW_ALERT,
"alert_type": alert.alertType
}