Wait, is this peak ?
This commit is contained in:
@@ -1,9 +1,13 @@
|
||||
import _thread
|
||||
from machine import Pin
|
||||
from machine import Pin, SoftI2C
|
||||
from shared.safeQueue import SafeQueue
|
||||
from shared import get_lora, get_uart, deviceTypes, config, cookingState
|
||||
from shared.uart_comm import UARTCommand, UARTCommandType
|
||||
from shared.sensors import RGBLED
|
||||
from shared.logging import log
|
||||
from shared.lora_device import LoraCommands
|
||||
import framebuf
|
||||
import ssd1306
|
||||
import time
|
||||
|
||||
# --- Configuration Matérielle ---
|
||||
@@ -21,44 +25,56 @@ except Exception:
|
||||
# --- Initialisation LoRa ---
|
||||
lora = get_lora()
|
||||
lora.configure(freq=868.1, sf=7)
|
||||
data_queue = SafeQueue()
|
||||
|
||||
# --- Création des lEDs RGB ---
|
||||
magnetron_led = RGBLED(red_pin=48, green_pin=47, blue_pin=33)
|
||||
magnetron_led.color = RGBLED.WHITE_YELLOW
|
||||
magnetron_led.off()
|
||||
|
||||
# --- Création de l'écran OLED ---
|
||||
scl_pin = Pin(18, Pin.OUT, pull=Pin.PULL_UP)
|
||||
sda_pin = Pin(17, Pin.OUT, pull=Pin.PULL_UP)
|
||||
display_i2c = SoftI2C(scl=scl_pin, sda=sda_pin, freq=100000)
|
||||
display = ssd1306.SSD1306_I2C(128, 64, display_i2c, addr=0x3C)
|
||||
display.text("Booting...", 1, 2, 1)
|
||||
display.show()
|
||||
|
||||
print(f"ESP32 initialisé avec l'ID : '{DEVICE_ID}' (Type : {deviceTypes.DEVICE_TYPES['MICROWAVE']})")
|
||||
|
||||
PING_PAYLOAD = {
|
||||
"id": DEVICE_ID,
|
||||
"type": deviceTypes.DEVICE_TYPES["MICROWAVE"]
|
||||
}
|
||||
|
||||
def heartbeat_loop():
|
||||
last_heartbeat_time = 0
|
||||
while True:
|
||||
print(f"\nESP32 : Envoi du Heartbeat...")
|
||||
# Envoi périodique
|
||||
ping_payload = {
|
||||
"id": DEVICE_ID,
|
||||
"type": deviceTypes.DEVICE_TYPES["MICROWAVE"]
|
||||
}
|
||||
lora.send(ping_payload)
|
||||
now = time.time()
|
||||
|
||||
# Le receive_packet est maintenant protégé par le lock dans lora_device
|
||||
# Si le main thread utilise la radio, ce thread attendra son tour
|
||||
paquet = lora.receive_packet(timeout_ms=2000)
|
||||
# 1. Send periodic heartbeat
|
||||
if now - last_heartbeat_time >= config.LORA_HEARTBEAT_INTERVAL:
|
||||
last_heartbeat_time = now
|
||||
print("\nESP32 : Envoi du Heartbeat...")
|
||||
lora.send(PING_PAYLOAD)
|
||||
|
||||
if paquet and not paquet["raw"]:
|
||||
donnees = paquet["data"]
|
||||
# Vérification si le paquet reçu est bien la réponse attendue de l'orchestrateur
|
||||
if donnees.get("type") == deviceTypes.DEVICE_TYPES["ORCHESTRATOR"]:
|
||||
print(f"ESP32 : Réponse reçue de l'orchestrateur '{donnees.get('id')}' ! [Statut: ALIVE]")
|
||||
else:
|
||||
print(f"ESP32 : Paquet reçu d'un type inattendu : {donnees.get('type')}")
|
||||
else:
|
||||
print("ESP32 : Pas de réponse de l'orchestrateur (Le RPI est-il éteint ?)")
|
||||
# 2. Increase listen window to 300ms so radio stays active in RX mode
|
||||
paquet = lora.receive_reliable(timeout_ms=300)
|
||||
|
||||
time.sleep(config.HEARTBEAT_INTERVAL)
|
||||
if paquet is not None:
|
||||
log(f"[LoRa Thread] New Packet Received: {paquet}")
|
||||
data_queue.put(paquet)
|
||||
|
||||
time.sleep_ms(10)
|
||||
|
||||
# UART
|
||||
uart_device = get_uart(uart_id=1, tx_pin=46, rx_pin=45)
|
||||
|
||||
# Lancer la boucle de heartbeat dans un thread séparé
|
||||
try:
|
||||
_thread.stack_size(16 * 1024)
|
||||
except Exception:
|
||||
pass
|
||||
_thread.start_new_thread(heartbeat_loop, ())
|
||||
|
||||
# Cooking parameters
|
||||
@@ -71,8 +87,13 @@ def cooking_state_on_state_change(state):
|
||||
|
||||
# Send to the Wifi board the current state
|
||||
uart_device.send_as_command(UARTCommand(UARTCommandType.COOKING_STATE_UPDATE, {"state": state.state}))
|
||||
# Send to the orchestrator the current state
|
||||
lora.send_reliable({"id": DEVICE_ID, "new_cooking_state": state.state})
|
||||
|
||||
if state.paused or state.state == cookingState.CookingStates.DONE:
|
||||
display.text(cookingState.CookingStates.get_state_name(state.state), 1, 2, 1)
|
||||
display.show()
|
||||
|
||||
if state.paused or state.state == cookingState.CookingStates.DONE or state.state == cookingState.CookingStates.IDLE:
|
||||
magnetron_led.off()
|
||||
else:
|
||||
magnetron_led.on()
|
||||
@@ -83,8 +104,7 @@ def cooking_state_on_state_change(state):
|
||||
if state.state == cookingState.CookingStates.STIRRING_REQUIRED:
|
||||
pass
|
||||
if state.state == cookingState.CookingStates.DONE:
|
||||
global cooking_state
|
||||
cooking_state = None
|
||||
pass
|
||||
if state.state == cookingState.CookingStates.ALERT:
|
||||
pass
|
||||
|
||||
@@ -92,6 +112,13 @@ def cooking_state_on_refresh(state):
|
||||
# TODO Show screen information
|
||||
pass
|
||||
|
||||
def cooking_state_on_pause(state):
|
||||
# If the cooking is unpaused and was in STIRRING_REQUIRED or ALERT state, we set the state back to COOKING.
|
||||
if not state.paused and (state.state == cookingState.CookingStates.STIRRING_REQUIRED or state.state == cookingState.CookingStates.ALERT):
|
||||
state.set_state(cookingState.CookingStates.COOKING)
|
||||
|
||||
# TODO send_reliable lora message to orchestrator about pause/resume state
|
||||
|
||||
|
||||
# --- MAIN APPLICATION THREAD ---
|
||||
print("[Main] Main execution path active.")
|
||||
@@ -113,17 +140,40 @@ while True:
|
||||
cooking_state.set_temperature_provider(cooking_state_temperature_provider)
|
||||
cooking_state.set_state_change_callback(cooking_state_on_state_change)
|
||||
cooking_state.set_refresh_callback(cooking_state_on_refresh)
|
||||
cooking_state.set_pause_callback(cooking_state_on_pause)
|
||||
time.sleep_ms(20) # Before sending back right away
|
||||
cooking_state_on_state_change(cooking_state)
|
||||
|
||||
else:
|
||||
print(f"[Main] Unknown command type received: {command.command_type}")
|
||||
# 2. Listen for incoming LoRa packets from the orchestrator
|
||||
while not data_queue.empty():
|
||||
paquet = data_queue.get()
|
||||
if paquet and not paquet["raw"]:
|
||||
data = paquet["data"]
|
||||
# Commands
|
||||
if "action" in data:
|
||||
if data["action"] == LoraCommands.TOGGLE_PAUSE:
|
||||
if cooking_state != None:
|
||||
if (cooking_state.state == cookingState.CookingStates.DONE):
|
||||
print("[Main] Cooking is done. We reset the microwave for the next cooking session.")
|
||||
cooking_state.set_state(cookingState.CookingStates.IDLE)
|
||||
time.sleep_ms(20) # Before sending back right away
|
||||
cooking_state = None
|
||||
else:
|
||||
cooking_state.toggle_pause()
|
||||
if cooking_state.paused:
|
||||
print("[Main] Cooking paused via orchestrator command.")
|
||||
else:
|
||||
print("[Main] Cooking resumed via orchestrator command.")
|
||||
else:
|
||||
log("[Main] No active cooking state to toggle pause/resume.")
|
||||
|
||||
# uart_device.send(f"Hello from esp-32 lora ID {DEVICE_ID}")
|
||||
|
||||
# Cooking State Update
|
||||
if cooking_state:
|
||||
if cooking_state != None:
|
||||
cooking_state.update_tick()
|
||||
print(f"[Main] Cooking state : State : {cooking_state.state}, Temperature: {cooking_state.current_dish_temp}, Paused: {cooking_state.paused}, Remaining Time: {cooking_state.get_remaining_time():.2f}s, Estimated Remaining Time: {cooking_state.get_remaining_time_estimation():.2f}s")
|
||||
|
||||
time.sleep_ms(200)
|
||||
time.sleep_ms(500)
|
||||
@@ -14,10 +14,10 @@ while True:
|
||||
mesures = {"id": "ESP32_Salon", "temp": 22.4, "hum": 55.2}
|
||||
|
||||
# Envoi direct (le pilote s'occupe de mettre le groupe \x02)
|
||||
lora.send(b'\x02' + lora.send_json_bytes_helper if False else bytes([2]) + lora.send_helper if False else b'\x02' + __import__('ujson').dumps(mesures).encode('utf-8'))
|
||||
lora.send_reliable(b'\x02' + lora.send_json_bytes_helper if False else bytes([2]) + lora.send_helper if False else b'\x02' + __import__('ujson').dumps(mesures).encode('utf-8'))
|
||||
|
||||
# Réception propre
|
||||
paquet = lora.receive_packet(3000)
|
||||
paquet = lora.receive_reliable(3000)
|
||||
if paquet:
|
||||
# paquet est un dict : {"group": 2, "data": {...}, "raw": False}
|
||||
print(f"ESP32 : Message reçu du groupe {paquet['group']}")
|
||||
|
||||
@@ -19,7 +19,7 @@ except Exception:
|
||||
orchestrator_id = None
|
||||
cooking_state = None
|
||||
mqtt_connected = False
|
||||
unsubscribed_hello = False
|
||||
should_unsubscribe_hello = False
|
||||
|
||||
# --- ASYNC SIGNALS & QUEUES ---
|
||||
# Event to signal when orchestrator requests sensor data (prevents MQTT lock deadlock)
|
||||
@@ -95,25 +95,29 @@ def on_received_cooking_state_update(state, is_error=False, is_terminated=False)
|
||||
|
||||
def on_cooking_state_change(state):
|
||||
"""Callback executed whenever local cooking state transitions."""
|
||||
BLINK_INTERVAL_MS = 500
|
||||
|
||||
if status_led and cookingState:
|
||||
if state == cookingState.CookingStates.IDLE:
|
||||
status_led.set_color(0, 0, 0) # Off
|
||||
elif state == cookingState.CookingStates.PREHEATING:
|
||||
status_led.set_color(255, 165, 0) # Orange
|
||||
status_led.color = status_led.OFF
|
||||
status_led.blink_off()
|
||||
elif state == cookingState.CookingStates.COOKING:
|
||||
status_led.set_color(255, 0, 0) # Red
|
||||
status_led.color = status_led.YELLOW
|
||||
status_led.blink_off()
|
||||
elif state == cookingState.CookingStates.STIRRING_REQUIRED:
|
||||
status_led.color = status_led.ORANGE
|
||||
status_led.blink_on(BLINK_INTERVAL_MS)
|
||||
elif state == cookingState.CookingStates.ALERT:
|
||||
status_led.color = status_led.RED
|
||||
status_led.blink_on(BLINK_INTERVAL_MS)
|
||||
elif state == cookingState.CookingStates.DONE:
|
||||
status_led.set_color(0, 255, 0) # Green
|
||||
elif state in (
|
||||
cookingState.CookingStates.ERROR,
|
||||
cookingState.CookingStates.ABORTED,
|
||||
):
|
||||
status_led.set_color(255, 0, 255) # Magenta/Purple
|
||||
status_led.color = status_led.GREEN
|
||||
status_led.blink_off()
|
||||
|
||||
|
||||
def on_mqtt_message(message):
|
||||
"""Sync callback: Lightweight! Only updates variables or triggers async signals."""
|
||||
global orchestrator_id, cooking_state, unsubscribed_hello
|
||||
global orchestrator_id, cooking_state, should_unsubscribe_hello
|
||||
print("[MQTT] Received message on topic:", message.get("topic"))
|
||||
|
||||
payload_data = None
|
||||
@@ -132,13 +136,7 @@ def on_mqtt_message(message):
|
||||
):
|
||||
orchestrator_id = payload_data.get("id_orchestrator")
|
||||
print("[MQTT] Hello response received from orchestrator:", orchestrator_id)
|
||||
if not unsubscribed_hello:
|
||||
unsubscribed_hello = True
|
||||
try:
|
||||
mqtt_client.unsubscribe(config.MQTT_TOPIC_HELLO)
|
||||
print("[MQTT] Successfully unsubscribed from topic:", config.MQTT_TOPIC_HELLO)
|
||||
except Exception as e:
|
||||
print("[MQTT] Unsubscribe error:", e)
|
||||
should_unsubscribe_hello = True
|
||||
|
||||
# 2. Cooking Parameters / Sensor Request
|
||||
elif (
|
||||
@@ -275,8 +273,7 @@ async def mqtt_poll_task():
|
||||
mqtt_client.poll()
|
||||
now = time.time()
|
||||
if now - last_ping >= 15:
|
||||
if mqtt_client._client:
|
||||
mqtt_client._client.ping()
|
||||
mqtt_client.ping()
|
||||
last_ping = now
|
||||
except OSError as e:
|
||||
print("[MQTT Task] Socket error encountered during poll/ping:", e)
|
||||
@@ -287,9 +284,17 @@ async def mqtt_poll_task():
|
||||
|
||||
|
||||
async def orchestrator_hello_task():
|
||||
global mqtt_connected
|
||||
global mqtt_connected, should_unsubscribe_hello
|
||||
while True:
|
||||
if orchestrator_id is not None:
|
||||
if should_unsubscribe_hello:
|
||||
try:
|
||||
mqtt_client.unsubscribe(config.MQTT_TOPIC_HELLO)
|
||||
should_unsubscribe_hello = False
|
||||
print("[MQTT] Successfully unsubscribed from hello topic.")
|
||||
except Exception as e:
|
||||
print("[MQTT] Unsubscribe error:", e)
|
||||
|
||||
# Hello successfully acknowledged! Stop looping this task.
|
||||
print("[Hello Task] Orchestrator acknowledged. Stopping hello task.")
|
||||
break
|
||||
|
||||
Reference in New Issue
Block a user