From 0f17e9dce62ee193e95119ebd5d89889bd9b197c Mon Sep 17 00:00:00 2001 From: Matthias Guillitte Date: Mon, 27 Jul 2026 17:14:05 +0200 Subject: [PATCH] Better cook parameter estimation + Defrost mode + Removed esp-wifi debugs + Orchestrator and microwave exchange --- cloud/APIs/edamam.py | 2 + cloud/app.py | 47 ++++--- cloud/microwaveCookPlanner.py | 6 +- cloud/microwaveThermalEngine.py | 67 +++++++--- cloud/requirements.txt | 3 +- micro_ondes/esp_wifi/boot.py | 2 +- micro_ondes/esp_wifi/main.py | 31 +++-- orchestrateur/main.py | 226 ++++++++++++++++++++++++++++++-- shared/payloads.py | 15 +++ 9 files changed, 335 insertions(+), 64 deletions(-) diff --git a/cloud/APIs/edamam.py b/cloud/APIs/edamam.py index 4df7280..68e6709 100644 --- a/cloud/APIs/edamam.py +++ b/cloud/APIs/edamam.py @@ -1,4 +1,5 @@ import os +import time import requests from typing import Dict, Any, Optional import json @@ -17,6 +18,7 @@ class EdamamAPI: def analyze_dish_image(self, image_file_path: str): if SAVE_EDAMAM_API_TOKEN: + time.sleep(3) return json.loads(""" { "combined": { diff --git a/cloud/app.py b/cloud/app.py index 1debd3f..03602aa 100644 --- a/cloud/app.py +++ b/cloud/app.py @@ -52,44 +52,57 @@ def cooking_params(): if not data: return jsonify({"error": "Invalid or missing JSON payload"}), 400 + # Extract user or device parameters (with fallback defaults) + height_cm = float(data.get("dish_height", 4.0)) + initial_temp_c = float(data.get("ir_initial_temp", 20.0)) # e.g., 4.0 for fridge, -18.0 for freezer + microwave_wattage = int(data.get("microwave_wattage", 900)) # e.g., 900W + defrost_mode = bool(data.get("defrost_mode", False)) # True for defrost, False for cook/reheat + print("Received cooking parameters request:", data) + print("Parsed parameters - Height (cm):", height_cm, "Initial Temp (C):", initial_temp_c, "Microwave Wattage:", microwave_wattage, "Defrost Mode:", defrost_mode) + # 1. Handle the Camera Image camera_image_b64 = data.get("camera_image") + filepath = None + if camera_image_b64: - # Generate a unique filename using UUID to avoid overwriting filename = f"dish_{uuid.uuid4().hex}.jpg" filepath = os.path.join(CAMERA_IMAGE_DIR, filename) try: - # Decode the base64 string and save it as a binary file with open(filepath, "wb") as f: f.write(base64.b64decode(camera_image_b64)) - # Replace the giant base64 string in the dictionary with the local file path - # so we don't bloat the MongoDB document data["camera_image"] = filepath except Exception as e: return jsonify({"error": f"Failed to save camera image: {str(e)}"}), 500 + else: + return jsonify({"error": "Missing required field 'camera_image'"}), 400 - # 2. Save to MongoDB + # 2. Run the Cook Planning Engine + try: + cook_plan = microwave_cook_planner.generate_plan( + image_path=filepath, + height_cm=height_cm, + initial_temp_c=initial_temp_c, + microwave_wattage=microwave_wattage, + defrost_mode=defrost_mode + ) + except Exception as e: + return jsonify({"error": f"Failed to compute cooking plan: {str(e)}"}), 500 + + # 3. Attach cooking parameters to database record + data["analysis_results"] = cook_plan + + # 4. Save to MongoDB try: - # Insert the dictionary directly into Mongo (it will retain your exact JSON keys) cooking_collection.insert_one(data) - - # Remove the Mongo-injected '_id' object before returning the response data.pop("_id", None) - except Exception as e: return jsonify({"error": f"Database error: {str(e)}"}), 500 - # 3. Returns with the cooking parameters - cooking_plan = microwave_cook_planner.generate_plan( - image_path=data.get("camera_image"), - height_cm=data.get("height_cm", 4.0), - initial_temp_c=data.get("initial_temp_c", 20.0), - microwave_wattage=data.get("microwave_wattage", 900) - ) - return jsonify(cooking_plan), 201 + # 5. Return complete output + return jsonify(cook_plan), 201 @app.route("/device-network", methods=["POST"]) def device_network(): diff --git a/cloud/microwaveCookPlanner.py b/cloud/microwaveCookPlanner.py index 9593004..c3933a1 100644 --- a/cloud/microwaveCookPlanner.py +++ b/cloud/microwaveCookPlanner.py @@ -50,7 +50,8 @@ class MicrowaveCookPlanner: image_path: str, height_cm: float, initial_temp_c: float, - microwave_wattage: int = 900 + microwave_wattage: int = 900, + defrost_mode: bool = False ) -> Dict[str, Any]: """Main pipeline call to parse an image and return cooking parameters.""" @@ -85,7 +86,8 @@ class MicrowaveCookPlanner: # 5. Cook Plan Calculation cook_plan = self.engine.calculate_cook_plan( state=thermal_state, - microwave_wattage=microwave_wattage + microwave_wattage=microwave_wattage, + defrost_mode=defrost_mode ) # Return consolidated output diff --git a/cloud/microwaveThermalEngine.py b/cloud/microwaveThermalEngine.py index f65efa1..a1794c9 100644 --- a/cloud/microwaveThermalEngine.py +++ b/cloud/microwaveThermalEngine.py @@ -4,23 +4,25 @@ from typing import Dict, Any, Optional @dataclass class DishThermalState: food_name: str - macronutrients: Dict[str, float] # grams of water, fat, protein, carbs + macronutrients: Dict[str, float] estimated_mass_g: float initial_temp_c: float volume_cm3: Optional[float] = None - + + class MicrowaveThermalEngine: """Calculates cook parameters based on physical properties""" DEFAULT_EFFICIENCY = 0.70 # ~70% magnetron efficiency - TARGET_TEMP_C = 74.0 # Safe food temperature + COOK_TARGET_TEMP_C = 74.0 # Safe food temp for cooking/reheating + DEFROST_TARGET_TEMP_C = 4.0 # Chilled state target for defrosting + LATENT_HEAT_ICE_J_G = 334.0 # Joules required to melt 1g of ice to water @staticmethod def estimate_specific_heat(macros: Dict[str, float], total_weight_g: float) -> float: """Estimates Cp in J/(g*C) based on macro composition""" if total_weight_g <= 0: - return 3.5 # Fallback average for mixed meals - + return 3.5 w_water = macros.get("water_g", total_weight_g * 0.7) / total_weight_g w_protein = macros.get("protein_g", 0.0) / total_weight_g w_fat = macros.get("fat_g", 0.0) / total_weight_g @@ -28,25 +30,54 @@ class MicrowaveThermalEngine: return (4.184 * w_water) + (1.71 * w_protein) + (1.67 * w_fat) + (1.42 * w_carbs) - def calculate_cook_plan(self, state: DishThermalState, microwave_wattage: int) -> Dict[str, Any]: + def calculate_cook_plan( + self, state: DishThermalState, microwave_wattage: int, defrost_mode: bool + ) -> Dict[str, Any]: cp = self.estimate_specific_heat(state.macronutrients, state.estimated_mass_g) - delta_t = max(0.0, self.TARGET_TEMP_C - state.initial_temp_c) - - # Q = m * c_p * delta_t + label = state.food_name.lower() + + # Set target temperature based on selected mode + target_temp = self.DEFROST_TARGET_TEMP_C if defrost_mode else self.COOK_TARGET_TEMP_C + delta_t = max(0.0, target_temp - state.initial_temp_c) + + # 1. Base thermal energy: Q_sensible = m * c_p * delta_t required_joules = state.estimated_mass_g * cp * delta_t - effective_power_watts = microwave_wattage * self.DEFAULT_EFFICIENCY - - total_seconds = required_joules / effective_power_watts if effective_power_watts > 0 else 0 - - # Determine duty cycle / power level recommendations - power_level = 100 - if state.initial_temp_c < 0: # Frozen food requires defrost cycle to prevent edge-cooking - power_level = 50 - total_seconds *= 1.4 + + # 2. Account for Phase Change (Ice -> Water) if food starts below 0°C + if state.initial_temp_c < 0: + water_g = state.macronutrients.get("water_g", state.estimated_mass_g * 0.7) + latent_energy_joules = water_g * self.LATENT_HEAT_ICE_J_G + required_joules += latent_energy_joules + + # 3. Determine power level and duty cycle based on mode + if defrost_mode: + # Defrost mode strictly runs low power (30%) to allow heat conduction + power_level = 30 if "bread" in label or "baked" in label else 40 + time_factor = 1.1 # Slight padding for thermal conductivity losses + else: + # Cook / Reheat Mode logic + if state.initial_temp_c < 0: + # Cooking from frozen needs lower power to defrost first, then cook + power_level = 50 + time_factor = 1.35 + elif state.estimated_mass_g > 350 and not any(w in label for w in ["soup", "beverage", "water", "tea"]): + power_level = 70 + time_factor = 1.2 + elif any(w in label for w in ["cheese", "cream", "sauce", "butter", "egg"]): + power_level = 60 + time_factor = 1.25 + else: + power_level = 100 + time_factor = 1.0 + + # Effective power delivered to food + effective_power_watts = microwave_wattage * self.DEFAULT_EFFICIENCY * (power_level / 100.0) + total_seconds = (required_joules / effective_power_watts * time_factor) if effective_power_watts > 0 else 0 return { "cook_time_seconds": round(total_seconds), "recommended_power_level_pct": power_level, + "target_temp": target_temp, "estimated_specific_heat": round(cp, 2), "energy_joules": round(required_joules) } \ No newline at end of file diff --git a/cloud/requirements.txt b/cloud/requirements.txt index 68993f1..0ada20d 100644 --- a/cloud/requirements.txt +++ b/cloud/requirements.txt @@ -1,4 +1,5 @@ Flask==3.0.2 pymongo==4.6.1 gunicorn==21.2.0 -opencv-python-headless \ No newline at end of file +opencv-python-headless +requests==2.32.3 \ No newline at end of file diff --git a/micro_ondes/esp_wifi/boot.py b/micro_ondes/esp_wifi/boot.py index 818a88c..8742daf 100644 --- a/micro_ondes/esp_wifi/boot.py +++ b/micro_ondes/esp_wifi/boot.py @@ -16,4 +16,4 @@ def do_connect(ssid, pwd): print('network config:', sta_if.ifconfig()) # Attempt to connect to WiFi network -# do_connect("Smartwave-1", 'Smartwave-prot-1') +do_connect("Smartwave-1", 'Smartwave-prot-1') diff --git a/micro_ondes/esp_wifi/main.py b/micro_ondes/esp_wifi/main.py index 99bc169..98371ba 100644 --- a/micro_ondes/esp_wifi/main.py +++ b/micro_ondes/esp_wifi/main.py @@ -55,6 +55,21 @@ def on_mqtt_message(message): # Unsubscribe from the hello topic since we got a response mqtt_client.unsubscribe(config.MQTT_TOPIC_HELLO) print("[MQTT Thread] Unsubscribed from topic:", config.MQTT_TOPIC_HELLO) + + # Handle cooking messages + elif message['topic'] == config.MQTT_TOPIC_COOKING and payload_data and payload_data["id_microwave"] == DEVICE_ID: + # Cooking sensors init request + if not "cook_time_seconds" in payload_data: + print("[MQTT Thread] Cooking sensors init received from the orchestrator") + obj_temp = temperature_sensor.read_object_temp() + amb_temp = temperature_sensor.read_ambient_temp() + queue_publish(config.MQTT_TOPIC_SENSOR, payloads.mqtt_sensor_data(DEVICE_ID, obj_temp, amb_temp)) + # Received cooking parameters from the orchestrator + else: + print("[MQTT Thread] Cooking parameters received from the orchestrator:", payload_data) + # Here you would handle the cooking parameters, e.g., start a cooking process + # For now, we just print them + print("[MQTT Thread] Message processing complete.") mqtt_client.set_callback(on_mqtt_message) @@ -86,7 +101,6 @@ def mqtt_background_thread(): mqtt_client.publish(topic, payload, qos=config.MQTT_QOS) # 2. Check for incoming messages (non-blocking poll) - # Shortened timeout to keep the queue responsive events = poller.poll(200) if events: mqtt_client.wait() @@ -145,18 +159,18 @@ def uart_background_thread(): time.sleep(5) # --- Launch background worker --- -# _thread.start_new_thread(mqtt_background_thread, ()) -# _thread.start_new_thread(uart_background_thread, ()) +_thread.start_new_thread(mqtt_background_thread, ()) +_thread.start_new_thread(uart_background_thread, ()) # --- MAIN APPLICATION THREAD (Core 0) --- print("[Main] Main execution path active.") time.sleep(2) # Give the thread a moment to initial connect mqtt_hello_sent_timestamp = -config.MQTT_HELLO_INTERVAL -# mqtt_client.subscribe(config.MQTT_TOPIC_HELLO, qos=config.MQTT_QOS) +mqtt_client.subscribe(config.MQTT_TOPIC_HELLO, qos=config.MQTT_QOS) # Temperature sensor setup -temperature_sensor_i2c = I2C(scl=Pin(25, Pin.IN, Pin.PULL_UP), sda=Pin(26, Pin.IN, Pin.PULL_UP), freq=100000) +temperature_sensor_i2c = I2C(0, scl=Pin(25, Pin.IN, Pin.PULL_UP), sda=Pin(26, Pin.IN, Pin.PULL_UP), freq=100000) # Scan to verify the sensor is connected and detected print("Scanning I2C bus...") devices = temperature_sensor_i2c.scan() @@ -174,13 +188,6 @@ while True: mqtt_hello_sent_timestamp = time.time() pass - # Sensors - print(f"[Main] Reading temperature from sensor...") - obj_temp = temperature_sensor.read_object_temp() - amb_temp = temperature_sensor.read_ambient_temp() - if obj_temp is not None and amb_temp is not None: - print(f"Object: {obj_temp:.2f}°C | Ambient: {amb_temp:.2f}°C") - # 2. Example: Send data to the Heltec board every 5 seconds # uart_device.send("Status Check: WiFi Active") time.sleep(1) \ No newline at end of file diff --git a/orchestrateur/main.py b/orchestrateur/main.py index 36b29a7..dcabff6 100644 --- a/orchestrateur/main.py +++ b/orchestrateur/main.py @@ -1,8 +1,11 @@ +import base64 import json import threading import queue import time import traceback + +import requests from orchestrateur.sensors import gps from shared import get_lora, get_mqtt_client, deviceTypes, config, payloads from shared.logging import log @@ -21,6 +24,9 @@ except Exception: # Thread-safe queue for application messages data_queue = queue.Queue() +cooking_queue = {} +active_cooks = {} +active_cooks_lock = threading.Lock() lora = get_lora() lora.configure() @@ -54,8 +60,9 @@ mqtt_client = get_mqtt_client( ) 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) print(f"Subscribed to topic: {config.MQTT_TOPIC_SENSOR}") +mqtt_client.subscribe(config.MQTT_TOPIC_HELLO, qos=config.MQTT_QOS) +print(f"Subscribed to topic: {config.MQTT_TOPIC_HELLO}") # --- THE CRUCIAL PAHO FIX --- # Start Paho's internal background thread. This handles all network packets, @@ -95,13 +102,179 @@ def button_callback(): button.set_callback(button_callback) # Launch background monitoring workers -# threading.Thread(target=lora_listener, daemon=True).start() -# threading.Thread(target=mqtt_listener, daemon=True).start() +threading.Thread(target=lora_listener, daemon=True).start() +threading.Thread(target=mqtt_listener, daemon=True).start() # Launch button monitoring thread button.start_button_monitoring_thread() print("Orchestrateur prêt. Le main loop est libre.") +def _tryReadSensorsWithRetries(func, exception=True, max_retries=3, delay=1): + """ + Tries to read the sensor max_retries times until the return value of func is not None. + It will then return the value of func. If it fails max_retries times, it will fail if exception is True, otherwise it will return None. + """ + + for attempt in range(max_retries): + result = func() + if result is not None: + return result + else: + log(f"Attempt {attempt + 1} failed. Retrying in {delay} seconds...") + time.sleep(delay) + + if exception: + raise Exception(f"Failed to read sensor after {max_retries} attempts.") + else: + return None + +def read_sensors_for_cooking(microwave_id): + """Read all sensors and return a dictionary of their values, including the microwave ID.""" + log("\nLecture des capteurs...") + sensor_data = {} + sensor_data["microwave_id"] = microwave_id + + # === Notify the microwave of needed sensor readings === + mqtt_client.publish(config.MQTT_TOPIC_COOKING, payloads.mqtt_cooking_init(microwave_id), qos=config.MQTT_QOS) + + # Read Ultrasonic Ranger + sensor_data["ultrasonic_distance"] = _tryReadSensorsWithRetries(ultrasonicRanger.get_dish_height) + log(f"\nLecture du capteur Ultrason : {sensor_data['ultrasonic_distance']}") + + # Read Temperature and Humidity + temperature, humidity = temp_hum.get_temperature_and_humidity() + if temperature is not None and humidity is not None: + log(f"\nLecture du capteur Temp/Hum : {temperature}, {humidity}") + sensor_data["temperature"] = temperature + sensor_data["humidity"] = humidity + + # Camera + def _getPicture(): + picture_bytes = None + try: + picture_bytes = camera.get_picture() + return picture_bytes + except Exception as e: + log(f"Error reading camera data: {e}") + return None + sensor_data["camera_image"] = _tryReadSensorsWithRetries(_getPicture, exception=True) + log(f"\nPhoto de la Caméra : {len(sensor_data['camera_image'])} bytes") + + # Read Button State (last because he can still change state while reading other sensors) + sensor_data["defrost_mode"] = button_state + + cooking_queue[microwave_id] = sensor_data + + +def _stop_hardware(microwave_id: str): + """ + Hardware driver stop — halts magnetron/turntable immediately. + """ + log(f"[{microwave_id}] 🛑 Emergency stop issued to hardware.") + # TODO: Add physical hardware stop command here + # e.g., gpio_controller.stop() + + +def _send_params_to_microwave(microwave_id: str, cook_time_seconds: int, power_level_pct: int, target_temp: float, cancel_event: threading.Event): + """ + Triggers physical microwave execution. + """ + if cancel_event.is_set(): + return + + log(f"[{microwave_id}] ⚡ Starting microwave cooking : {cook_time_seconds}s @ {power_level_pct}% power, target temp {target_temp}°C.") + # TODO: Connect to microwave hardware driver here + # e.g., gpio_controller.start(time=cook_time_seconds, power=power_level_pct) + + +def _cooking_worker(microwave_id: str, sensors_data: dict, cancel_event: threading.Event): + """Worker function executing cloud API calls and hardware triggers.""" + URL = "https://smartwave.matthiasg.dev/cooking-params" + + try: + # Check cancellation before network call + if cancel_event.is_set(): + print(f"[{microwave_id}] Job canceled before starting API call.") + return + + log(f"[{microwave_id}] Sending sensor data to cloud API...") + + # Ensure camera_image is encoded to Base64 string if it's currently raw bytes + if isinstance(sensors_data.get("camera_image"), bytes): + sensors_data["camera_image"] = base64.b64encode(sensors_data["camera_image"]).decode("utf-8") + + # 1. HTTP Request (15-second timeout) + response = requests.post(URL, json=sensors_data, timeout=15) + + # Check cancellation right after network call returns + if cancel_event.is_set(): + print(f"[{microwave_id}] Job was canceled while waiting for cloud response. Discarding result.") + return + + response.raise_for_status() + + # 2. Extract Response Parameters + response_json = response.json() + cook_plan = response_json.get("cook_plan", {}) + + cook_time = cook_plan.get("cook_time_seconds") + power_level = cook_plan.get("recommended_power_level_pct") + target_temp = cook_plan.get("target_temp") + dish_name = response_json.get("dish_name", "Unknown Dish") + + if cook_time is None or power_level is None or target_temp is None: + print(f"[{microwave_id}] Cloud returned incomplete plan: {response_json}") + return + + # Check cancellation before starting physical microwave + if cancel_event.is_set(): + print(f"[{microwave_id}] Job was canceled before starting hardware execution.") + return + + print(f"[{microwave_id}] Received plan for '{dish_name}': {cook_time}s @ {power_level}% power, target temp {target_temp}°C.") + + # 3. Start Hardware Execution + _send_params_to_microwave(microwave_id, cook_time, power_level, target_temp, cancel_event) + + except requests.exceptions.Timeout: + print(f"[{microwave_id}] Request timed out waiting for cloud response.") + except requests.exceptions.RequestException as e: + print(f"[{microwave_id}] HTTP error reaching cloud API: {e}") + except Exception as e: + print(f"[{microwave_id}] Unexpected error in worker thread: {e}") + traceback.print_exc() + finally: + # Clean up registry entry if this worker was the active one + with active_cooks_lock: + if active_cooks.get(microwave_id) == cancel_event: + del active_cooks[microwave_id] + + +def start_cooking_for_microwave(microwave_id: str, sensors_data: dict): + """ + Sends sensors data to the cloud and starts cooking in a separate thread. + If a worker is already running for the given microwave_id, it cancels + the previous process and stops the hardware before starting the new one. + """ + with active_cooks_lock: + # 1. If an active job exists for this microwave, cancel it + if microwave_id in active_cooks: + print(f"[{microwave_id}] Existing cooking job detected! Canceling old worker...") + active_cooks[microwave_id].set() # Signal existing thread to abort + _stop_hardware(microwave_id) # Stop hardware immediately + + # 2. Register a new cancellation event for this microwave + cancel_event = threading.Event() + active_cooks[microwave_id] = cancel_event + + # 3. Start the new background worker thread + thread = threading.Thread( + target=_cooking_worker, + args=(microwave_id, sensors_data, cancel_event), + daemon=True + ) + thread.start() + # Sensor reading def read_sensors(): """Read all sensors and return a dictionary of their values.""" @@ -138,14 +311,14 @@ def read_sensors(): log(f"Error reading camera data: {e}") # Read Button State (last because he can still change state while reading other sensors) - sensor_data["button_state"] = button_state + sensor_data["defrost_state"] = button_state return sensor_data # --- MAIN EXECUTION LOOP --- while True: try: - # Check for non-heartbeat data + # === TREAT MESSAGE QUEUE === try: msg = data_queue.get(block=False) @@ -154,6 +327,7 @@ while True: if msg["source"] == "LoRa": print(f"\n[Main Loop] LoRa : Données traitées : {msg['data']}") elif msg["source"] == "MQTT": + # MQTT HELLO if (msg["topic"] == config.MQTT_TOPIC_HELLO.decode('utf-8')): if ("id_orchestrator" in msg["data"] and msg["data"]["id_orchestrator"] == DEVICE_ID): # Do not answer to messages coming from me @@ -164,19 +338,45 @@ while True: mqtt_client.publish(config.MQTT_TOPIC_HELLO, payloads.mqtt_hello_ack(DEVICE_ID, microwave_id), qos=config.MQTT_QOS) print(f"[Main Loop] MQTT : Réponse Hello envoyée à {microwave_id}.") # TODO : Save in database - - + # MQTT SENSOR DATA + elif (msg["topic"] == config.MQTT_TOPIC_SENSOR.decode('utf-8')): + print(f"\n[Main Loop] MQTT : Données capteurs reçues du micro-ondes : {msg['data']}") + microwave_id = msg["data"].get("id_microwave") + if not microwave_id: + print("[Main Loop] MQTT : Données capteurs reçues sans ID micro-ondes. Ignoré.") + continue + # Get the already existing cooking data for this microwave + sensors_data = cooking_queue.get(microwave_id) + if sensors_data is None: + print(f"[Main Loop] MQTT : Données capteurs reçues pour {microwave_id} mais aucune donnée de cuisson en cours. Ignoré.") + continue + # Merge the received sensor data into the existing cooking data + sensors_data["ir_initial_temp"] = msg["data"].get("dish_temp") + sensors_data["ir_ambient_temp"] = msg["data"].get("ambient_temp") + start_cooking_for_microwave(microwave_id, sensors_data) + # Remove the cooking data from the queue since it's now being processed + del cooking_queue[microwave_id] + print(f"\n[Main Loop] MQTT : Données traitées : {msg['data']}") except queue.Empty: pass + # === CHECK FOR DISH INSERTED === + # Read the dish height from the ultrasonic sensor. If it's below a certain threshold, we assume a dish has been inserted. + dish_height = ultrasonicRanger.get_dish_height() + if dish_height is not None and dish_height > 2.0: # Threshold in cm for detecting a dish + print(f"\n[Main Loop] Dish detected at height: {dish_height} cm. Initiating sensor read...") + # Read all sensors and store the data in the cooking queue for this microwave + read_sensors_for_cooking("2") + print(f"[Main Loop] Sensor data collected and queued for cooking.") + # DEBUG : Read sensors - sensor_values = read_sensors() - if sensor_values: - sensor_values_print = sensor_values.copy() - if "camera_image" in sensor_values_print: - sensor_values_print["camera_image"] = f"<{len(sensor_values_print['camera_image'])} bytes>" - print(f"\nCapteurs Données lues : {sensor_values_print}") + # sensor_values = read_sensors() + # if sensor_values: + # sensor_values_print = sensor_values.copy() + # if "camera_image" in sensor_values_print: + # sensor_values_print["camera_image"] = f"<{len(sensor_values_print['camera_image'])} bytes>" + # print(f"\nCapteurs Données lues : {sensor_values_print}") time.sleep(3) diff --git a/shared/payloads.py b/shared/payloads.py index 19b350c..72ef79d 100644 --- a/shared/payloads.py +++ b/shared/payloads.py @@ -1,3 +1,6 @@ +from time import time + + try: import ujson as json except ImportError: @@ -20,4 +23,16 @@ def mqtt_hello_ack(id_orchestrator, id_microwave): return as_json({ "id_microwave": id_microwave, "id_orchestrator": id_orchestrator + }) + +def mqtt_cooking_init(id_microwave): + return as_json({ + "id_microwave": id_microwave + }) + +def mqtt_sensor_data(id_microwave, dish_temp, ambient_temp): + return as_json({ + "id_microwave": id_microwave, + "dish_temp": dish_temp, + "ambient_temp": ambient_temp }) \ No newline at end of file