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)
def message(self, text, alert):
self.flick()
# self.flick()
self._message(text, alert)
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.logging import log
from shared.lora_device import LoraCommands
from shared.alerts import Alert, AlertType, AlertManager
# --- READ DEVICE ID ---
try:
@@ -37,6 +38,7 @@ current_temp = [None, None]
last_temp = [None, None]
temperature_asked = False
last_temp_request_time = 0
alert_manager = None
PING_PAYLOAD = {
@@ -44,6 +46,11 @@ PING_PAYLOAD = {
"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():
"""Initializes all hardware components."""
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.")
defrost_mode = data["defrost_state"]
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():
@@ -276,11 +288,14 @@ async def memory_cleanup_task():
# --- BOOTSTRAP ---
async def main():
global cooking_state, defrost_mode, microwave_screen
global cooking_state, defrost_mode, microwave_screen, alert_manager
print("[Main] Starting application...")
init_hardware()
alert_manager = AlertManager()
alert_manager.set_on_alert_callback(on_new_alert)
# Launch dedicated hardware thread for LoRa RX
try:
_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.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 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_events = {} # mw_id -> asyncio.Event()
# --- HARDWARE & MQTT SETUP ---
lora = get_lora()
lora.configure()
@@ -188,6 +188,28 @@ def button_callback():
button.set_callback(button_callback)
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 ---
def _stop_hardware(microwave_id: str):
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):
"""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
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()
# Robust JSON extraction: handles direct dict or nested {"cook_plan": {...}}
# JSON extraction
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)
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:
print(f"[{microwave_id}] ❌ Invalid plan received: {res_json}")
microwave_states[microwave_id] = MicrowaveState.DONE
microwave_states[microwave_id] = None
return
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
except Exception as e:
# Print stacktrace
import traceback
traceback.print_exc()
print(f"[{microwave_id}] Cloud API Error (Attempt {attempt}/{max_retries}): {e}")
if attempt < max_retries:
@@ -540,6 +579,11 @@ async def main():
global async_event_queue
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
init_db()
+1
View File
@@ -6,6 +6,7 @@ import shared.config as config
import shared.payloads as payloads
import shared.cookingState as cookingState
import shared.safeQueue as safeQueue
import shared.alerts as alerts
try:
import shared.lora_device as lora_device
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 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:
@@ -65,4 +71,10 @@ def telemetry_payload(device_id, microwave_states, button_state, cloud_alert, gp
"ambient_humidity": ambient_humidity,
"connected_components": connected_components,
"logs": logs
})
})
def lora_new_alert(alert: Alert):
return {
"action": LoraCommands.NEW_ALERT,
"alert_type": alert.alertType
}