Compare commits

...

2 Commits

Author SHA1 Message Date
Ninluc 25e635e207 MQTT hello is saved in database
Build, push image, and notify Watchtower / build-image (push) Successful in 42s
Build, push image, and notify Watchtower / notify (push) Successful in 11s
2026-08-06 16:55:24 +02:00
Ninluc fb41a2d07a Database logging and error handling 2026-08-06 16:55:03 +02:00
3 changed files with 144 additions and 72 deletions
+41 -4
View File
@@ -6,12 +6,40 @@ import asyncio
import requests import requests
from orchestrateur.sensors import gps from orchestrateur.sensors import gps
from shared import get_lora, get_mqtt_client, deviceTypes, config, payloads from shared import get_lora, get_mqtt_client, deviceTypes, config, payloads, db
from shared.logging import log from shared.logging import log
from shared.cookingState import CookingStates from shared.cookingState import CookingStates
from shared.lora_device import LoraCommands from shared.lora_device import LoraCommands
from sensors import ultrasonicRanger, temp_hum, button, camera from sensors import ultrasonicRanger, temp_hum, button, camera
# --- DB SETUP ---
DB_PATH = "orchestrateur/db.sqlite"
def init_db():
"""Ensures the connected_components table exists on startup."""
sql = """
CREATE TABLE IF NOT EXISTS connected_components (
id TEXT PRIMARY KEY,
type TEXT,
timestamp INTEGER
);
"""
db.execute(DB_PATH, sql)
print(f"[DB] Initialized database table at {DB_PATH}")
def save_connected_component(component_id: str, component_type: str):
"""Upserts component information into the database (blocking sync worker)."""
current_time = int(time.time())
sql = """
INSERT INTO connected_components (id, type, timestamp)
VALUES (?, ?, ?)
ON CONFLICT(id) DO UPDATE SET
type = excluded.type,
timestamp = excluded.timestamp;
"""
db.execute(DB_PATH, sql, (str(component_id), str(component_type), current_time))
print(f"[DB] Component saved/updated -> ID: {component_id}, Type: {component_type}, Timestamp: {current_time}")
# --- Read Unique Device ID --- # --- Read Unique Device ID ---
def get_device_id(): def get_device_id():
for path in ["device_id.txt", "/home/pi/SmartWave/orchestrateur/device_id.txt"]: for path in ["device_id.txt", "/home/pi/SmartWave/orchestrateur/device_id.txt"]:
@@ -287,11 +315,17 @@ async def process_messages_task():
if topic == hello_topic: if topic == hello_topic:
if data.get("id_orchestrator") != DEVICE_ID: if data.get("id_orchestrator") != DEVICE_ID:
mw_id = data.get("id_microwave") component_id = data.get("id_microwave")
print(f"[MQTT] Hello from {mw_id}. Sending ACK.") component_type = data.get("type", deviceTypes.DEVICE_TYPES["MICROWAVE"])
if component_id:
print(f"[MQTT] Hello received from '{component_id}' ({component_type}). Updating DB & sending ACK.")
# Offload DB insertion to async thread execution pool
await asyncio.to_thread(save_connected_component, component_id, component_type)
mqtt_client.publish( mqtt_client.publish(
config.MQTT_TOPIC_HELLO, config.MQTT_TOPIC_HELLO,
payloads.mqtt_hello_ack(DEVICE_ID, mw_id), payloads.mqtt_hello_ack(DEVICE_ID, component_id),
qos=config.MQTT_QOS qos=config.MQTT_QOS
) )
@@ -372,6 +406,9 @@ async def main():
global async_event_queue global async_event_queue
print("🚀 Orchestrateur Asyncio prêt. Lancement des tâches...") print("🚀 Orchestrateur Asyncio prêt. Lancement des tâches...")
# Initialize SQLite database table
init_db()
async_event_queue = asyncio.Queue() async_event_queue = asyncio.Queue()
await asyncio.gather( await asyncio.gather(
+38 -5
View File
@@ -5,6 +5,8 @@ same code can run on CPython (`sqlite3`) and MicroPython (`sqlite3` or
`usqlite`, depending on the port). `usqlite`, depending on the port).
""" """
from shared.logging import log
try: try:
import sqlite3 as _sqlite import sqlite3 as _sqlite
DRIVER_NAME = "sqlite3" DRIVER_NAME = "sqlite3"
@@ -13,20 +15,25 @@ except ImportError:
import usqlite as _sqlite import usqlite as _sqlite
DRIVER_NAME = "usqlite" DRIVER_NAME = "usqlite"
except ImportError as exc: except ImportError as exc:
log("[DB Error] No sqlite driver found. Expected sqlite3 or usqlite.")
raise ImportError("No sqlite driver found. Expected sqlite3 or usqlite.") from exc raise ImportError("No sqlite driver found. Expected sqlite3 or usqlite.") from exc
def _connect(database_path, **connect_kwargs): def _connect(database_path, **connect_kwargs):
try:
if connect_kwargs: if connect_kwargs:
try: try:
return _sqlite.connect(database_path, **connect_kwargs) return _sqlite.connect(database_path, **connect_kwargs)
except TypeError: except TypeError:
pass pass
return _sqlite.connect(database_path) return _sqlite.connect(database_path)
except Exception as e:
log(f"[DB Error] Driver connect failed for '{database_path}': {e}")
raise
class Database: class Database:
"""Lightweight connection wrapper with a consistent API.""" """Lightweight connection wrapper with logging and consistent API."""
def __init__(self, database_path, **connect_kwargs): def __init__(self, database_path, **connect_kwargs):
self._database_path = database_path self._database_path = database_path
@@ -40,29 +47,45 @@ class Database:
def close(self): def close(self):
if self._connection is not None: if self._connection is not None:
try:
self._connection.close() self._connection.close()
except Exception as e:
log(f"[DB Error] Failed to close database '{self._database_path}': {e}")
finally:
self._connection = None self._connection = None
def commit(self): def commit(self):
connection = self.open() connection = self.open()
if hasattr(connection, "commit"): if hasattr(connection, "commit"):
try:
connection.commit() connection.commit()
except Exception as e:
log(f"[DB Error] Commit failed on '{self._database_path}': {e}")
raise
def cursor(self): def cursor(self):
return self.open().cursor() return self.open().cursor()
def execute(self, sql, params=None): def execute(self, sql, params=None):
cursor = self.cursor() cursor = self.cursor()
try:
if params is None: if params is None:
cursor.execute(sql) cursor.execute(sql)
else: else:
cursor.execute(sql, params) cursor.execute(sql, params)
return cursor return cursor
except Exception as e:
log(f"[DB Error] Query failed on '{self._database_path}' | SQL: {sql} | Params: {params} | Error: {e}")
raise
def executemany(self, sql, params_list): def executemany(self, sql, params_list):
cursor = self.cursor() cursor = self.cursor()
try:
cursor.executemany(sql, params_list) cursor.executemany(sql, params_list)
return cursor return cursor
except Exception as e:
log(f"[DB Error] Executemany failed on '{self._database_path}' | SQL: {sql} | Error: {e}")
raise
def fetchone(self, sql, params=None): def fetchone(self, sql, params=None):
return self.execute(sql, params).fetchone() return self.execute(sql, params).fetchone()
@@ -73,16 +96,23 @@ class Database:
def executescript(self, script): def executescript(self, script):
connection = self.open() connection = self.open()
if hasattr(connection, "executescript"): if hasattr(connection, "executescript"):
try:
return connection.executescript(script) return connection.executescript(script)
except Exception as e:
log(f"[DB Error] Executescript failed on '{self._database_path}': {e}")
raise
log("[DB Error] executescript is not available on this sqlite backend")
raise NotImplementedError("executescript is not available on this sqlite backend") raise NotImplementedError("executescript is not available on this sqlite backend")
def __enter__(self): def __enter__(self):
self.open() self.open()
return self return self
def __exit__(self, exc_type, exc, traceback): def __exit__(self, exc_type, exc_val, exc_tb):
if exc_type is None: if exc_type is None:
self.commit() self.commit()
else:
log(f"[DB Error] Context exited with exception on '{self._database_path}': {exc_val}")
self.close() self.close()
@@ -91,12 +121,15 @@ def connect(database_path, **connect_kwargs):
def execute(database_path, sql, params=None, **connect_kwargs): def execute(database_path, sql, params=None, **connect_kwargs):
return connect(database_path, **connect_kwargs).execute(sql, params) with connect(database_path, **connect_kwargs) as db_inst:
return db_inst.execute(sql, params)
def fetchone(database_path, sql, params=None, **connect_kwargs): def fetchone(database_path, sql, params=None, **connect_kwargs):
return connect(database_path, **connect_kwargs).fetchone(sql, params) with connect(database_path, **connect_kwargs) as db_inst:
return db_inst.fetchone(sql, params)
def fetchall(database_path, sql, params=None, **connect_kwargs): def fetchall(database_path, sql, params=None, **connect_kwargs):
return connect(database_path, **connect_kwargs).fetchall(sql, params) with connect(database_path, **connect_kwargs) as db_inst:
return db_inst.fetchall(sql, params)
+3 -1
View File
@@ -1,4 +1,5 @@
from time import time from time import time
from shared.deviceTypes import DEVICE_TYPES
try: try:
@@ -16,7 +17,8 @@ def as_json(data):
def mqtt_hello(id_microwave): def mqtt_hello(id_microwave):
return as_json({ return as_json({
"id_microwave": id_microwave "id_microwave": id_microwave,
"type": DEVICE_TYPES["MICROWAVE"]
}) })
def mqtt_hello_ack(id_orchestrator, id_microwave): def mqtt_hello_ack(id_orchestrator, id_microwave):