diff --git a/ecran_externe/display_api.h b/ecran_externe/display_api.h index 2c2a884..16fd18e 100644 --- a/ecran_externe/display_api.h +++ b/ecran_externe/display_api.h @@ -4,16 +4,33 @@ #include "esphome/components/json/json_util.h" #include #include +#include -// Callback signature: void(device_id, cloud_alert, defrost_mode) -using DisplayUpdateCallback = std::function; +struct MicrowaveData { + std::string id; + std::string name; + int state; + bool paused; + int remaining_time; + int progress; +}; +// Global states parsed from the API +static std::vector g_microwaves; +static bool g_cloud_alert = false; +static std::string g_update_time = "--:--"; +static bool g_received_update = false; + +// Callback signature for refreshing UI +using DisplayUpdateCallback = std::function; static DisplayUpdateCallback g_display_callback = nullptr; class DisplayApiHandler : public esphome::web_server_idf::AsyncWebHandler { public: bool canHandle(esphome::web_server_idf::AsyncWebServerRequest *request) const override { - return request->url_to() == "/api/display"; + // Provide the 513-byte buffer expected by ESPHome's new memory-safe API + char url_buf[513]; + return request->url_to(url_buf) == "/api/display"; } void handleRequest(esphome::web_server_idf::AsyncWebServerRequest *request) override { @@ -24,16 +41,31 @@ class DisplayApiHandler : public esphome::web_server_idf::AsyncWebHandler { std::string body((char *)data, len); esphome::json::parse_json(body, [](JsonObject root) -> bool { - std::string device_id = root["device_id"] | "unknown"; - bool cloud_alert = root["cloud_alert"] | false; - bool defrost_mode = root["defrost_mode"] | false; + g_cloud_alert = root["cloud_alert"] | false; + g_update_time = root["update_time"] | "--:--"; + + g_microwaves.clear(); + JsonArray mw_array = root["microwaves"]; + + for (JsonObject mw : mw_array) { + MicrowaveData md; + md.id = mw["id"] | "unknown"; + md.name = mw["name"] | "Microwave"; + md.state = mw["state"] | 4; + md.paused = mw["paused"] | false; + md.remaining_time = mw["remaining_time"] | 0; + md.progress = mw["progress"] | 0; + + g_microwaves.push_back(md); + } + + g_received_update = true; - ESP_LOGI("custom_api", "Received update for %s (Alert: %d, Defrost: %d)", - device_id.c_str(), cloud_alert, defrost_mode); + ESP_LOGI("custom_api", "Received update. Cloud Alert: %d, Microwaves: %d", + g_cloud_alert, g_microwaves.size()); - // Execute the callback passed from YAML if (g_display_callback != nullptr) { - g_display_callback(device_id, cloud_alert, defrost_mode); + g_display_callback(); } return true; diff --git a/ecran_externe/epaper_thread_node.yaml b/ecran_externe/epaper_thread_node.yaml index e08f9c2..5045a31 100644 --- a/ecran_externe/epaper_thread_node.yaml +++ b/ecran_externe/epaper_thread_node.yaml @@ -10,30 +10,11 @@ esphome: priority: -100.0 then: - lambda: |- - setup_display_api([](const std::string &dev_id, bool alert, bool defrost) { - // Update global state - id(cloud_alert) = alert; - id(defrost_mode) = defrost; - - // Set status message text - std::string msg = "Microwave " + dev_id; - id(status_message).publish_state(msg.c_str()); - + setup_display_api([]() { // Force immediate e-Paper refresh! id(my_display).update(); }); -# ------------------------------------------------------------------- -# State Globals -# ------------------------------------------------------------------- -globals: - - id: cloud_alert - type: bool - initial_value: 'false' - - id: defrost_mode - type: bool - initial_value: 'false' - web_server: port: 5000 @@ -102,22 +83,13 @@ openthread: # Template Text Sensor # ------------------------------------------------------------------- text_sensor: - - platform: template - name: "Display Status Message" - id: status_message - platform: openthread_info - ip_address: - name: "Thread IP" - id: thread_ip role: name: "Thread Role" id: thread_role channel: name: "Thread Channel" id: thread_channel - rloc16: - name: "Thread RLOC16" - id: thread_rloc16 # ------------------------------------------------------------------- # Hardware SPI Pin Mapping (ESP32-H2) @@ -139,12 +111,18 @@ font: - file: type: gfonts family: Overpass - weight: 400 + weight: 600 id: font_body size: 14 + - file: + type: gfonts + family: Overpass + weight: 500 + id: font_small + size: 12 # ------------------------------------------------------------------- -# Waveshare 2.13-inch e-Paper Display (V4 panel = 2.13inv3 in ESPHome) +# Waveshare 2.13-inch e-Paper Display (250x122 standard resolution) # ------------------------------------------------------------------- display: - platform: waveshare_epaper @@ -157,35 +135,75 @@ display: update_interval: 30min id: my_display lambda: |- - if (id(status_message).has_state()) { - // Header - it.print(5, 3, id(font_header), id(status_message).state.c_str()); - it.line(0, 26, it.get_width(), 26); - - // Mode Status - if (id(defrost_mode)) { - it.print(5, 32, id(font_header), "MODE: DEFROST"); - } else { - it.print(5, 32, id(font_header), "MODE: NORMAL"); - } - - // Cloud Alert Status Box - if (id(cloud_alert)) { - it.filled_rectangle(5, 60, 240, 24, COLOR_OFF); // Inverse background if supported, or frame box - it.print(10, 62, id(font_body), "CLOUD ALERT ACTIVE"); - } else { - it.print(10, 62, id(font_body), "System Normal"); - } - - // Footer: Thread Info - it.line(0, 95, it.get_width(), 95); - it.printf(5, 100, id(font_body), "Role: %s | Ch: %s", - id(thread_role).state.c_str(), - id(thread_channel).state.c_str()); - } else { + // --- STARTUP SCREEN --- + if (!g_received_update) { int center_x = it.get_width() / 2; int center_y = it.get_height() / 2; - it.printf(center_x, center_y - 10, id(font_header), TextAlign::CENTER, "SmartWave"); - it.printf(center_x, center_y + 15, id(font_body), TextAlign::CENTER, "Waiting for Pi updates..."); - } \ No newline at end of file + it.printf(center_x, center_y + 15, id(font_body), TextAlign::CENTER, "Waiting for updates..."); + return; + } + + // --- MICROWAVE LAYOUT --- + int y_offset = 0; + for (const auto& mw : g_microwaves) { + // Name + it.print(0, y_offset, id(font_header), mw.name.c_str()); + + // Decode State Text + std::string state_str; + switch(mw.state) { + case 0: state_str = mw.paused ? "PAUSED" : "COOKING"; break; + case 1: state_str = "STIRRING REQ."; break; + case 2: state_str = "DONE"; break; + case 3: state_str = "ALERT"; break; + case 4: state_str = "IDLE"; break; + default: state_str = "UNKNOWN"; break; + } + + // Print Status (Top Right Align) + it.printf(it.get_width(), y_offset + 5, id(font_body), TextAlign::TOP_RIGHT, "%s", state_str.c_str()); + + // Process active/cooking visualisations + if (mw.state == 0) { + int bar_y = y_offset + 28; + int bar_w = 175; + int bar_h = 16; + + // Draw progress bar outline & fill + it.rectangle(0, bar_y, bar_w, bar_h); + if (mw.progress > 0) { + int fill_w = (mw.progress * bar_w) / 100; + it.filled_rectangle(0, bar_y, fill_w, bar_h); + } + + // Draw time remaining (or paused state) + if (mw.paused) { + it.printf(185, bar_y + 1, id(font_body), "PAUSED"); + } else { + int mins = mw.remaining_time / 60; + int secs = mw.remaining_time % 60; + it.printf(185, bar_y + 1, id(font_body), "%02d:%02d", mins, secs); + } + } + else if (mw.state == 1) { // Stirring Required + it.filled_rectangle(0, y_offset + 28, 250, 20); + it.printf(125, y_offset + 28, id(font_body), COLOR_OFF, TextAlign::TOP_CENTER, "ACTION REQUIRED: STIR"); + } + + // Move cursor down (if we have more than 1 microwave on future larger screens) + y_offset += 60; + } + + // --- SYSTEM FOOTER --- + // Line separator right above footer + it.line(0, 102, it.get_width(), 102); + + // Bottom Left: Cloud Reachability & Last Update Time + std::string cloud_status = g_cloud_alert ? "Cloud: OK" : "Cloud: ERR"; + it.printf(0, 105, id(font_small), "%s | %s", cloud_status.c_str(), g_update_time.c_str()); + + // Bottom Right: OpenThread Diagnostics + std::string role = id(thread_role).state; + it.printf(it.get_width(), 105, id(font_small), TextAlign::TOP_RIGHT, + "Th: %s (Ch %s)", role.c_str(), id(thread_channel).state.c_str()); \ No newline at end of file diff --git a/orchestrateur/external_display_manager.py b/orchestrateur/external_display_manager.py index 38eb5a8..3e38745 100644 --- a/orchestrateur/external_display_manager.py +++ b/orchestrateur/external_display_manager.py @@ -1,7 +1,7 @@ import asyncio import time import requests -import ipaddress +import datetime from typing import Dict from zeroconf import ServiceStateChange from zeroconf.asyncio import AsyncZeroconf, AsyncServiceBrowser, AsyncServiceInfo @@ -55,20 +55,45 @@ class DisplayManager: print(f"[DisplayManager] ❌ Screen disconnected: {name}") self.screens.pop(name, None) - async def broadcast_state(self, device_id: str, microwave_states: dict, button_state: bool, cloud_alert: bool): + async def broadcast_state(self, microwave_states: dict, cloud_alert: bool): """Broadcasts the system state JSON to all discovered screens concurrently.""" if not self.screens: return + now_time = time.time() + update_time_str = datetime.datetime.now().strftime("%H:%M") + + microwaves_list = [] + for mw_id, state_info in microwave_states.items(): + # Extract state properties safely + state_val = state_info.cooking_state + paused = state_info.paused + rem_time = state_info.cooking_estimated_remaining_time + start_t = state_info.cooking_start_time + name = state_info.friendly_name + + # Calculate progress percentage (0 to 100) + progress = 0 + if start_t > 0 and rem_time > 0: + elapsed = now_time - start_t + total = elapsed + rem_time + if total > 0: + progress = int((elapsed / total) * 100) + progress = max(0, min(100, progress)) + + microwaves_list.append({ + "id": mw_id, + "name": name, + "state": state_val, + "paused": paused, + "remaining_time": rem_time, + "progress": progress + }) + payload = { - "device_id": device_id, - "timestamp": int(time.time()), "cloud_alert": cloud_alert, - "defrost_mode": button_state, - "microwaves": [ - {"id": mw_id, "state": state} - for mw_id, state in microwave_states.items() - ] + "update_time": update_time_str, + "microwaves": microwaves_list } # Dispatch POST requests in parallel without blocking the main loop diff --git a/orchestrateur/main.py b/orchestrateur/main.py index 900156d..dd04ac8 100644 --- a/orchestrateur/main.py +++ b/orchestrateur/main.py @@ -4,6 +4,7 @@ import time import asyncio import requests +from orchestrateur.microwave_state import MicrowaveState from orchestrateur.sensors import gps from shared import get_lora, get_mqtt_client, deviceTypes, config, payloads, db from shared.logging import log @@ -12,7 +13,10 @@ 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 -from external_display_manager import DisplayManager # <-- Import display manager +from external_display_manager import DisplayManager + +# --- CONFIGURATION CONSTANTS --- +DISPLAY_DEBOUNCE_SECONDS = 3 # Seconds to wait after first change before broadcasting # --- DB SETUP --- DB_PATH = "orchestrateur/db.sqlite" @@ -60,16 +64,27 @@ def get_device_id(): DEVICE_ID = get_device_id() -# --- STATE MACHINE DEFINITIONS --- -class MicrowaveState: - IDLE = "IDLE" # Microwave is empty - ANALYZING = "ANALYZING" # Reading sensors & waiting for IR - WAITING_FOR_CLOUD = "WAITING_FOR_CLOUD" # Waiting for API parameters - COOKING = "COOKING" # Microwave is active - DONE = "DONE" # Finished/Stopped, waiting for dish removal +# --- DISPLAY QUEUE & NOTIFICATION HELPERS --- +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() + loop.call_soon_threadsafe(display_update_queue.put_nowait, time.time()) + except RuntimeError: + pass + +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() # Global state trackers -microwave_states = {"2": MicrowaveState.IDLE} +microwave_states = {"2": MicrowaveState("2", on_change_callback=notify_display_update)} button_state = False cloud_alert = False # Global status flag for screen / UI display async_event_queue = None @@ -178,25 +193,40 @@ async def cloud_mqtt_listener_task(): }) await asyncio.sleep(0.1) -async def display_broadcast_task(): - """Monitors state changes and broadcasts updates to all connected screens.""" - last_state = None +async def display_broadcast_worker_task(): + """ + Debounced display broadcast task. + Waits until DISPLAY_DEBOUNCE_SECONDS have passed since the first queued update + before sending state to all connected screens. + """ + print("[Display Worker] Started debounced display broadcast worker.") while True: - current_state = (dict(microwave_states), button_state, cloud_alert) - if current_state != last_state: - last_state = current_state - await display_manager.broadcast_state( - device_id=DEVICE_ID, - microwave_states=microwave_states, - button_state=button_state, - cloud_alert=cloud_alert - ) - await asyncio.sleep(3.0) + # Block until the FIRST update reminder arrives in the queue + first_event_time = await display_update_queue.get() + + # Compute wait time relative to the first event's timestamp + elapsed = time.time() - first_event_time + remaining_wait = DISPLAY_DEBOUNCE_SECONDS - elapsed + if remaining_wait > 0: + await asyncio.sleep(remaining_wait) + + # Clear any subsequent events that arrived during the wait period + while not display_update_queue.empty(): + try: + display_update_queue.get_nowait() + except asyncio.QueueEmpty: + break + + # Broadcast state ONCE + await display_manager.broadcast_state( + microwave_states=microwave_states, + cloud_alert=cloud_alert + ) def button_callback(): """Button physical interrupt callback.""" global button_state - if microwave_states.get("2") == MicrowaveState.COOKING or microwave_states.get("2") == MicrowaveState.DONE: + if microwave_states.get("2").cooking_state == CookingStates.COOKING or microwave_states.get("2").cooking_state == CookingStates.DONE or microwave_states.get("2").cooking_state == CookingStates.STIRRING_REQUIRED: print("[Button] Toggling pause/resume for microwave '2'.") lora.send_reliable({"id": DEVICE_ID, "microwave_id": "2", "action": LoraCommands.TOGGLE_PAUSE}) else: @@ -227,7 +257,7 @@ def on_new_alert(alert): # Send the alert to the lora device lora.send_reliable(payloads.lora_new_alert(alert)) - # TODO : envoi de l'alerte aux écrans externes + microwave_states["2"].set_cooking_state(CookingStates.ALERT) # --- HARDWARE CONTROLLERS --- def _stop_hardware(microwave_id: str): @@ -264,7 +294,7 @@ def read_local_sensors(microwave_id, initial_dish_height): async def handle_new_dish(microwave_id, detected_height): """Triggered when a new dish is placed inside.""" - microwave_states[microwave_id] = MicrowaveState.ANALYZING + microwave_states[microwave_id].set_state(MicrowaveState.ANALYZING) print(f"\n[{microwave_id}] 🍽️ Dish detected at {detected_height:.1f} cm! Requesting IR from microwave...") # 1. Setup synchronization event and clear previous cache for this microwave @@ -286,7 +316,7 @@ async def handle_new_dish(microwave_id, detected_height): sensors_data = await sensor_task # Check if dish was removed while reading sensors - if microwave_states.get(microwave_id) != MicrowaveState.ANALYZING: + if microwave_states.get(microwave_id).state != MicrowaveState.ANALYZING: print(f"[{microwave_id}] Dish removed during sensor read. Aborting.") ir_data_events.pop(microwave_id, None) return @@ -310,7 +340,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, alert_manager - microwave_states[microwave_id] = MicrowaveState.WAITING_FOR_CLOUD + microwave_states[microwave_id].set_state(MicrowaveState.WAITING_FOR_CLOUD) URL = "https://smartwave.matthiasg.dev/cooking-params" # Format image @@ -326,7 +356,7 @@ async def request_cloud_cooking_plan(microwave_id, sensors_data): HTTP_TIMEOUT = (10, 330) for attempt in range(1, max_retries + 1): - if microwave_states.get(microwave_id) != MicrowaveState.WAITING_FOR_CLOUD: + if microwave_states.get(microwave_id).state != MicrowaveState.WAITING_FOR_CLOUD: print(f"[{microwave_id}] Dish removed or state changed. Aborting API request.") return @@ -341,7 +371,7 @@ async def request_cloud_cooking_plan(microwave_id, sensors_data): ) # Abort if state changed while waiting for cloud AI response - if microwave_states.get(microwave_id) != MicrowaveState.WAITING_FOR_CLOUD: + if microwave_states.get(microwave_id).state != MicrowaveState.WAITING_FOR_CLOUD: print(f"[{microwave_id}] Dish removed during API request. Discarding API plan.") return @@ -353,7 +383,7 @@ async def request_cloud_cooking_plan(microwave_id, sensors_data): # Error handling if "error" in res_json: print(f"[{microwave_id}] Cloud API returned error: {res_json['error']}") - microwave_states[microwave_id] = MicrowaveState.DONE + microwave_states[microwave_id].set_state(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']) @@ -371,12 +401,12 @@ 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] = None + 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] = MicrowaveState.COOKING + microwave_states[microwave_id].set_state(MicrowaveState.COOKING) if config.DEBUG: c_time = 20 # Set to 20s for debug @@ -399,7 +429,7 @@ async def request_cloud_cooking_plan(microwave_id, sensors_data): print(f"[{microwave_id}] Retrying in {retry_delay_seconds} seconds...") # Interruptible wait loop in case state changes mid-wait for _ in range(retry_delay_seconds): - if microwave_states.get(microwave_id) != MicrowaveState.WAITING_FOR_CLOUD: + if microwave_states.get(microwave_id).state != MicrowaveState.WAITING_FOR_CLOUD: print(f"[{microwave_id}] State changed during retry wait. Aborting retries.") return await asyncio.sleep(1) @@ -407,7 +437,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 - microwave_states[microwave_id] = MicrowaveState.DONE + microwave_states[microwave_id].set_state(MicrowaveState.DONE) # --- MAIN LOGIC TASKS --- async def process_messages_task(): @@ -427,11 +457,13 @@ async def process_messages_task(): n_state = data["data"].get("new_cooking_state") print(f"[LoRa] Microwave {mw_id} state changed to: {n_state}") + microwave_states[mw_id].set_cooking_state(n_state) + 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) buzzer.buzzer_siren(beatsNb=beats) - if n_state == CookingStates.IDLE and microwave_states.get(mw_id) == MicrowaveState.COOKING: - microwave_states[mw_id] = MicrowaveState.DONE + 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.") elif source == "MQTT": @@ -450,6 +482,13 @@ async def process_messages_task(): # Offload DB insertion to async thread execution pool await asyncio.to_thread(save_connected_component, component_id, component_type) + # Pass change callback to newly connected microwave + microwave_states[component_id] = MicrowaveState( + component_id, + on_change_callback=notify_display_update + ) + notify_display_update() + mqtt_client.publish( config.MQTT_TOPIC_HELLO, payloads.mqtt_hello_ack(DEVICE_ID, component_id), @@ -497,7 +536,7 @@ async def handle_telemetry_request(endpoint: str): # 3. Build the telemetry payload (returns a JSON string) telemetry_payload = payloads.telemetry_payload( device_id=DEVICE_ID, - microwave_states=microwave_states, + microwave_states=microwave_states.to_dict(), button_state=button_state, cloud_alert=cloud_alert, gps_data=gps.get_gps_data(), @@ -556,7 +595,7 @@ async def monitor_dish_height_task(): while True: dist = await get_filtered_dish_height() - current_state = microwave_states.get(mw_id, MicrowaveState.IDLE) + current_state = microwave_states.get(mw_id, MicrowaveState.IDLE).state if dist is not None: # Hysteresis Thresholds: @@ -582,8 +621,8 @@ async def monitor_dish_height_task(): elif consecutive_absent >= REQUIRED_STABLE_READS and current_state != MicrowaveState.IDLE: 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 + microwave_states[mw_id].set_state(MicrowaveState.IDLE) + set_cloud_alert(False) if current_state == MicrowaveState.COOKING: _stop_hardware(mw_id) @@ -595,7 +634,7 @@ async def monitor_dish_height_task(): # --- BOOTSTRAP --- async def main(): - global async_event_queue + global async_event_queue, display_update_queue print("Orchestrateur prêt. Lancement des tâches...") # Initialize the alert manager and set the callback @@ -610,6 +649,7 @@ async def main(): await display_manager.start() async_event_queue = asyncio.Queue() + display_update_queue = asyncio.Queue() await asyncio.gather( lora_listener_task(), @@ -617,7 +657,7 @@ async def main(): cloud_mqtt_listener_task(), process_messages_task(), monitor_dish_height_task(), - display_broadcast_task() + display_broadcast_worker_task() ) if __name__ == "__main__": diff --git a/orchestrateur/microwave_state.py b/orchestrateur/microwave_state.py new file mode 100644 index 0000000..991256c --- /dev/null +++ b/orchestrateur/microwave_state.py @@ -0,0 +1,85 @@ +from shared.cookingState import CookingStates +import shared.config as config + +class MicrowaveState: + state = None + paused = False + cooking_state = None + cooking_estimated_remaining_time = 0 + cooking_start_time = 0 + + def __init__(self, id, on_change_callback=None): + self.friendly_name = f"Microwave-{id}" + self.state = MicrowaveState.IDLE + self.cooking_state = CookingStates.IDLE + self.on_change_callback = on_change_callback + + def _notify_change(self): + """Invokes callback if registered on state attribute changes.""" + if self.on_change_callback: + try: + self.on_change_callback() + except Exception as e: + print(f"[MicrowaveState] Error in change callback: {e}") + + def set_state(self, new_state): + if config.DEBUG: + assert new_state in ( + MicrowaveState.IDLE, + MicrowaveState.ANALYZING, + MicrowaveState.WAITING_FOR_CLOUD, + MicrowaveState.COOKING, + MicrowaveState.DONE + ), f"Invalid state: {new_state}" + + if self.state != new_state: + self.state = new_state + self._notify_change() + + def set_paused(self, is_paused): + if self.paused != is_paused: + self.paused = is_paused + self._notify_change() + + def toggle_pause(self): + self.paused = not self.paused + self._notify_change() + + def set_cooking_state(self, new_cooking_state): + if config.DEBUG: + assert new_cooking_state in ( + CookingStates.IDLE, + CookingStates.COOKING, + CookingStates.DONE, + CookingStates.STIRRING_REQUIRED, + CookingStates.ALERT + ), "Invalid cooking state: " + str(new_cooking_state) + + if self.cooking_state != new_cooking_state: + self.cooking_state = new_cooking_state + self._notify_change() + + def set_cooking_estimated_remaining_time(self, time_seconds): + if self.cooking_estimated_remaining_time != time_seconds: + self.cooking_estimated_remaining_time = time_seconds + self._notify_change() + + def set_cooking_start_time(self, start_time): + if self.cooking_start_time != start_time: + self.cooking_start_time = start_time + self._notify_change() + + def to_dict(self): + return { + "friendly_name": self.friendly_name, + "state": self.state, + "paused": self.paused, + "cooking_estimated_remaining_time": self.cooking_estimated_remaining_time, + "cooking_start_time": self.cooking_start_time + } + + IDLE = "IDLE" # Microwave is empty + ANALYZING = "ANALYZING" # Reading sensors & waiting for IR + WAITING_FOR_CLOUD = "WAITING_FOR_CLOUD" # Waiting for API parameters + COOKING = "COOKING" # Microwave is active + DONE = "DONE" # Finished/Stopped, waiting for dish removal \ No newline at end of file diff --git a/shared/config.py b/shared/config.py index 094cec1..de492d2 100644 --- a/shared/config.py +++ b/shared/config.py @@ -1,4 +1,6 @@ +# Debugging and others DEBUG=True +DEBUG_DANGEROUS_AREA=False # LoRa LORA_HEARTBEAT_INTERVAL = 30