Better LoRa

This commit is contained in:
2026-08-16 16:28:58 +02:00
parent 9b0ecf944a
commit 01eccf47c6
3 changed files with 141 additions and 123 deletions
+30 -32
View File
@@ -1,8 +1,6 @@
import gc
import sys
import time
import _thread
from lib.microwaveScreen import MicrowaveScreen
import uasyncio as asyncio
from machine import Pin, SoftI2C
import ssd1306
@@ -19,6 +17,7 @@ from shared.logging import log
from shared.lora_device import LoraCommands
from shared.alerts import Alert, AlertType, AlertManager
from shared.microwave_state import MicrowaveState
from lib.microwaveScreen import MicrowaveScreen
# --- READ DEVICE ID ---
try:
@@ -112,10 +111,9 @@ def init_hardware():
print(f"[Main] ESP32 initialized with ID: '{DEVICE_ID}' (Type: {deviceTypes.DEVICE_TYPES['MICROWAVE']})")
# --- DEDICATED LORA HARDWARE THREAD ---
def lora_hardware_thread():
"""Runs in a separate OS thread to keep the LoRa radio in continuous RX mode."""
# --- ASYNC LORA TASK (REPLACES _THREAD) ---
async def lora_rx_and_heartbeat_task():
"""Replaces the hardware OS thread with a non-blocking async task to avoid SPI collisions."""
last_heartbeat_time = 0
while True:
@@ -124,19 +122,22 @@ def lora_hardware_thread():
# 1. Send periodic heartbeat
if now - last_heartbeat_time >= config.LORA_HEARTBEAT_INTERVAL:
last_heartbeat_time = now
log("\n[LoRa Thread] Sending Heartbeat...")
log("\n[LoRa Task] Sending Heartbeat...")
if lora:
lora.send(PING_PAYLOAD)
# CRITICAL: Force the radio chip out of Standby mode and back into RX mode
if hasattr(lora, 'receive'):
lora.receive()
# 2. Blocking 300ms RX listen window (keeps radio actively listening)
# 2. Listen for incoming packets (Must be longer than LoRa Time-on-Air)
if lora:
paquet = lora.receive_reliable(timeout_ms=300)
paquet = lora.receive_reliable(timeout_ms=350)
if paquet is not None:
log(f"[LoRa Thread] New Packet Received: {paquet}")
log(f"[LoRa Task] New Packet Received: {paquet}")
data_queue.put(paquet)
time.sleep_ms(10)
# Give control back to event loop
await asyncio.sleep_ms(10)
# --- COOKING STATE CALLBACKS ---
def cooking_state_temperature_provider():
@@ -228,7 +229,7 @@ def update_screen():
async def uart_polling_task():
"""Polls UART for incoming messages from the WiFi board."""
global cooking_state, cooking_start_time, temperature_asked
global cooking_state, cooking_start_time, temperature_asked, microwave_state
while True:
if uart_device and uart_device.any():
@@ -256,6 +257,7 @@ async def uart_polling_task():
await asyncio.sleep_ms(200)
cooking_state_on_state_change(cooking_state)
microwave_state = MicrowaveState.COOKING
elif command.command_type == UARTCommandType.TEMPERATURE_RESPONSE:
try:
# Check if payload is already a dict or needs JSON decoding
@@ -282,40 +284,43 @@ async def lora_process_task():
if paquet and not paquet.get("raw"):
data = paquet.get("data", {})
# RECIPIENT FILTERING: Drop packets not addressed to this ESP32 or ALL
target = data.get("target_id") or data.get("recipient")
if target and target not in [DEVICE_ID, "ALL", "BROADCAST"]:
log(f"[LoRa Process] Ignoring packet intended for {target}")
continue
if "action" in data:
if data["action"] == LoraCommands.TOGGLE_PAUSE:
if cooking_state is not None:
if cooking_state.state == cookingState.CookingStates.DONE:
print("[LoRa Process] Cooking is done. Resetting microwave for the next session.")
print("[LoRa Process] Cooking is done. Resetting microwave for next session.")
cooking_state.set_state(cookingState.CookingStates.IDLE)
await asyncio.sleep_ms(20)
cooking_state = None
else:
cooking_state.toggle_pause()
if cooking_state.paused:
print("[LoRa Process] Cooking paused via orchestrator command.")
else:
print("[LoRa Process] Cooking resumed via orchestrator command.")
print(f"[LoRa Process] Cooking paused state toggled to {cooking_state.paused}")
else:
log("[LoRa Process] No active cooking state to toggle pause/resume.")
elif data["action"] == LoraCommands.TOGGLE_DEFROST:
print("[LoRa Process] Toggling defrost mode via orchestrator command.")
defrost_mode = data["defrost_state"]
defrost_mode = data.get("defrost_state", False)
update_screen()
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)
elif data["action"] == LoraCommands.MICROVAVE_STATE_UPDATE:
new_state = data.get("new_microwave_state")
if new_state:
print(f"[LoRa Process] Microwave state update received: {new_state}")
microwave_state = new_state
update_screen()
else:
print("[LoRa Process] Received microwave state update with no state specified.")
await asyncio.sleep_ms(100)
@@ -351,23 +356,16 @@ async def memory_cleanup_task():
# --- BOOTSTRAP ---
async def main():
global cooking_state, defrost_mode, microwave_screen, alert_manager
global 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)
except Exception:
pass
_thread.start_new_thread(lora_hardware_thread, ())
print("[Main] LoRa hardware background thread started.")
# Launch background async tasks
# Launch all tasks within the same single-threaded uasyncio event loop
asyncio.create_task(lora_rx_and_heartbeat_task())
asyncio.create_task(uart_polling_task())
asyncio.create_task(lora_process_task())
asyncio.create_task(cooking_loop_task())