OTA-Update per MQTT

Neues Kommando <PREFIX>/cmd/ota loest ota.update() aus: laedt die
Projektdateien per HTTP, erst als temporaere .ota-Dateien, und benennt
sie nur bei vollem Erfolg um. Schlaegt ein Download fehl, bleiben die
bisherigen Dateien unveraendert -- die Anzeige laeuft mit der alten
Version weiter (Grundsatz wie beim Rest von MQTT: eine fehlgeschlagene
OTA darf das Geraet nicht unbrauchbar machen).

Am Gerät bestätigt, inkl. echtem Reboot: leere/ungueltige Payload,
unbekanntes Kommando und nicht erreichbarer Server werden abgefangen,
die Poll-Schleife laeuft unbeeinflusst weiter. Ein echtes Update mit
allen Projektdateien kam sauber ueber main.py-Autostart wieder hoch.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017zTSXLK4w32PoCmFgSzp68
This commit is contained in:
2026-09-04 17:34:58 +02:00
parent 72da766af0
commit 0e4bb025ef
9 changed files with 534 additions and 9 deletions
+34 -3
View File
@@ -6,6 +6,9 @@
<PREFIX>/set/hysterese 150
<PREFIX>/set/mount_host 192.168.1.115 Adresse der Montierung; leer =
Vorgabe aus config.MOUNT_HOST
<PREFIX>/cmd/ota http://192.168.1.50:8000 loest ota.update() aus; leere
Nachricht = Vorgabe aus
config.OTA_URL (siehe ota.py)
<PREFIX>/status/ra 18h36m56s ausgehend
<PREFIX>/status/dec +38°47'01"
@@ -58,7 +61,7 @@ class MqttBridge:
def __init__(self, broker=None, port=1883, user="", password="",
prefix="grossanzeige", client_id="grossanzeige",
client=None, log=None, on_change=None):
client=None, log=None, on_change=None, on_ota=None):
self._broker = broker
self._port = port
self._user = user
@@ -68,6 +71,7 @@ class MqttBridge:
self._client = client
self._log = log or (lambda *a: None)
self._on_change = on_change # wird nach jeder Aenderung gerufen
self._on_ota = on_ota # wird bei cmd/ota gerufen (Payload = URL)
self.connected = False
self._retry = RETRY_START
@@ -78,6 +82,9 @@ class MqttBridge:
def _set_topic(self, name=None):
return "%s/set/%s" % (self._prefix, "+" if name is None else name)
def _cmd_topic(self, name=None):
return "%s/cmd/%s" % (self._prefix, "+" if name is None else name)
def _status_topic(self, name):
return "%s/status/%s" % (self._prefix, name)
@@ -106,6 +113,7 @@ class MqttBridge:
retain=True)
self._client.connect()
self._client.subscribe(self._set_topic())
self._client.subscribe(self._cmd_topic())
self._client.publish(self._status_topic("online"), b"1", retain=True)
self.connected = True
self._retry = RETRY_START
@@ -143,12 +151,17 @@ class MqttBridge:
def _on_message(self, topic, payload):
"""Callback von umqtt. Faengt alles ab -- hier darf nichts durchschlagen."""
try:
name = topic.decode().rsplit("/", 1)[-1]
teile = topic.decode().split("/")
gruppe, name = teile[-2], teile[-1]
roh = payload.decode().strip()
except Exception:
self._log("MQTT: unlesbare Nachricht verworfen")
return
if gruppe == "cmd":
self._on_command(name, roh)
return
if name not in SETZBAR:
self._log("MQTT: unbekannte Einstellung %r verworfen" % name)
return
@@ -179,6 +192,23 @@ class MqttBridge:
except Exception as e:
self._log("MQTT: on_change fehlgeschlagen (%s)" % e)
def _on_command(self, name, roh):
"""cmd/<name> auswerten. Bislang nur "ota" -- Payload ist die URL."""
if name != "ota":
self._log("MQTT: unbekanntes Kommando %r verworfen" % name)
return
if self._on_ota is None:
self._log("MQTT: OTA angefordert, aber kein Handler eingerichtet")
return
self._log("MQTT: OTA angefordert%s" % (" (%s)" % roh if roh else ""))
try:
self._on_ota(roh)
except Exception as e:
# OTA scheitert oft erst mitten im Download (Netz, Server nicht
# erreichbar) -- das darf die Poll-Schleife nicht mitreissen,
# dieselbe Regel wie bei on_change.
self._log("MQTT: OTA fehlgeschlagen (%s)" % e)
def check(self):
"""Anstehende Nachrichten verarbeiten. Blockiert nicht."""
if not self.ensure():
@@ -235,7 +265,7 @@ class MqttBridge:
self._client = None
def connect_from_config(log=None, on_change=None):
def connect_from_config(log=None, on_change=None, on_ota=None):
"""Bruecke aus mqtt_config.py bauen und verbinden.
Liefert None, wenn keine Konfiguration da ist -- dann laeuft die Anzeige
@@ -257,6 +287,7 @@ def connect_from_config(log=None, on_change=None):
client_id=getattr(mqtt_config, "CLIENT_ID", "grossanzeige"),
log=log,
on_change=on_change,
on_ota=on_ota,
)
bridge.connect() # scheitert es, versucht check() es spaeter erneut
return bridge