From 6fa74c6b933206138e5f399878f1437a02798453 Mon Sep 17 00:00:00 2001 From: Matthias Guillitte Date: Wed, 5 Aug 2026 21:34:15 +0200 Subject: [PATCH] Retries with cloud Still need to to be able to reset the cooking_state so hat the user can reinsert a dish --- orchestrateur/main.py | 86 +++++++++++++++++++++++++++++-------------- 1 file changed, 58 insertions(+), 28 deletions(-) diff --git a/orchestrateur/main.py b/orchestrateur/main.py index 8556634..6653d47 100644 --- a/orchestrateur/main.py +++ b/orchestrateur/main.py @@ -35,7 +35,9 @@ class MicrowaveState: # Global state trackers microwave_states = {"2": MicrowaveState.IDLE} button_state = False +cloud_alert = False # Global status flag for screen / UI display async_event_queue = None + # Async synchronization trackers for MQTT IR sensors responses ir_data_cache = {} # mw_id -> dict of IR readings ir_data_events = {} # mw_id -> asyncio.Event() @@ -109,7 +111,7 @@ button.start_button_monitoring_thread() # --- HARDWARE CONTROLLERS --- def _stop_hardware(microwave_id: str): - print(f"[{microwave_id}] /!\ Emergency stop issued to hardware.") + print(f"[{microwave_id}] /!\\ Emergency stop issued to hardware.") # TODO: Add LoRa STOP command here # --- ASYNC COOKING LOGIC --- @@ -187,7 +189,8 @@ async def handle_new_dish(microwave_id, detected_height): asyncio.create_task(request_cloud_cooking_plan(microwave_id, sensors_data)) async def request_cloud_cooking_plan(microwave_id, sensors_data): - """Sends all data to the cloud and starts the microwave if successful.""" + """Sends all data to the cloud with up to 3 retries (30s interval).""" + global cloud_alert microwave_states[microwave_id] = MicrowaveState.WAITING_FOR_CLOUD URL = "https://smartwave.matthiasg.dev/cooking-params" @@ -196,36 +199,60 @@ async def request_cloud_cooking_plan(microwave_id, sensors_data): sensors_data["camera_image"] = base64.b64encode(sensors_data["camera_image"]).decode("utf-8") print(f"[{microwave_id}] Requesting cooking plan from cloud app...") - try: - response = await asyncio.to_thread(requests.post, URL, json=sensors_data, timeout=30) - - # Abort if state changed (e.g. user removed dish while waiting for wifi) - if microwave_states[microwave_id] != MicrowaveState.WAITING_FOR_CLOUD: - print(f"[{microwave_id}] Dish removed during API request. Discarding API plan.") + + max_retries = 3 + retry_delay_seconds = 30 + + for attempt in range(1, max_retries + 1): + if microwave_states.get(microwave_id) != MicrowaveState.WAITING_FOR_CLOUD: + print(f"[{microwave_id}] Dish removed or state changed. Aborting API request.") return - response.raise_for_status() - plan = response.json().get("cook_plan", {}) - c_time = plan.get("cook_time_seconds") - c_power = plan.get("effective_power_watts") - 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: {response.json()}") - microwave_states[microwave_id] = MicrowaveState.DONE # Fail safe + try: + print(f"[{microwave_id}] Connection attempt {attempt}/{max_retries}...") + response = await asyncio.to_thread(requests.post, URL, json=sensors_data, timeout=30) + # Abort if state changed (e.g. user removed dish while waiting for wifi) + if microwave_states.get(microwave_id) != MicrowaveState.WAITING_FOR_CLOUD: + print(f"[{microwave_id}] Dish removed during API request. Discarding API plan.") + return + + response.raise_for_status() + plan = response.json().get("cook_plan", {}) + c_time = plan.get("cook_time_seconds") + c_power = plan.get("effective_power_watts") + 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: {response.json()}") + microwave_states[microwave_id] = MicrowaveState.DONE + 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] = MicrowaveState.COOKING + mqtt_client.publish( + config.MQTT_TOPIC_COOKING, + payloads.mqtt_cooking_config(microwave_id, c_time, c_power, c_temp), + qos=config.MQTT_QOS + ) return - print(f"[{microwave_id}] Cloud Plan Received! Starting microwave: {c_time}s @ {c_power}W") - microwave_states[microwave_id] = MicrowaveState.COOKING - mqtt_client.publish( - config.MQTT_TOPIC_COOKING, - payloads.mqtt_cooking_config(microwave_id, c_time, c_power, c_temp), - qos=config.MQTT_QOS - ) - - except Exception as e: - print(f"[{microwave_id}] Cloud API Error: {e}") - microwave_states[microwave_id] = MicrowaveState.DONE + except Exception as e: + print(f"[{microwave_id}] Cloud API Error (Attempt {attempt}/{max_retries}): {e}") + + if attempt < max_retries: + print(f"[{microwave_id}] Retrying in {retry_delay_seconds} seconds...") + # Interruptible wait loop in case user removes the dish mid-wait + for _ in range(retry_delay_seconds): + if microwave_states.get(microwave_id) != MicrowaveState.WAITING_FOR_CLOUD: + print(f"[{microwave_id}] State changed during retry wait. Aborting retries.") + return + await asyncio.sleep(1) + + # Executed only if all 3 retries failed + print(f"[{microwave_id}] All 3 cloud retries failed. Setting global alert flag.") + cloud_alert = True + microwave_states[microwave_id] = MicrowaveState.DONE # --- MAIN LOGIC TASKS --- async def process_messages_task(): @@ -292,6 +319,7 @@ async def get_filtered_dish_height(samples=3, delay=0.04): async def monitor_dish_height_task(): """Monitors presence of dish with hysteresis and debouncing.""" + global cloud_alert mw_id = "2" consecutive_present = 0 consecutive_absent = 0 @@ -326,6 +354,8 @@ async def monitor_dish_height_task(): consecutive_absent = 0 print(f"\n[{mw_id}] Dish Removed! Resetting state to IDLE.") microwave_states[mw_id] = MicrowaveState.IDLE + cloud_alert = False # Reset error alert on dish removal + if current_state == MicrowaveState.COOKING: _stop_hardware(mw_id) # Remove from IR cache and events