Discovery: alle Geraete finden und stabil ablegen

Ein Lauf mit allen Modulen brachte 54 Geraete und 441 Messwerte - und fuenf
Fehler ans Licht, die vorher niemand sehen konnte, weil clear_tables die
Tabellen bei jedem Lauf geleert hat.

Vier davon sind Schluessel- und Upsert-Fehler derselben Familie:

 * command_parameters war ueber (command_id, url) eindeutig. WLED-Parameter
   haben keine URL - ihr Name ist der Platzhalter in der Kommando-Vorlage -,
   und eine NULL kollidiert in MySQL nie. ON DUPLICATE KEY UPDATE griff
   also nicht, und jeder Lauf legte dieselben Parameter erneut an: aus 18
   wurden nach drei Laeufen 54. Schluessel jetzt (command_id,
   parameter_name).

 * actor_states war ueber (actor_id, url) eindeutig. Umgekehrtes Problem:
   Messwerte, die sich ein Topic teilen, ueberschrieben einander. Der
   go-eCharger schickt sechzehn Werte als JSON-Feld auf einem Topic - von
   denen kam genau einer in der Datenbank an. Schluessel jetzt (actor_id,
   state_name), das bringt 50 verlorene Messwerte zurueck.

 * Kommandos, Messwerte und Parameter aktualisierten ihre URL beim
   Wiederholungslauf nicht - sie stand nicht im UPDATE-Teil. Eine Korrektur
   in einem Modul kam damit nie in einer bestehenden Datenbank an.

 * Der Typ eines Kommando-Parameters wurde als Text in eine int-Spalte
   geschrieben. MariaDB macht daraus stillschweigend 0, und 0 ist "bool" -
   deshalb bot der Editor fuer die WLED-Helligkeit (0..255) ein Ja/Nein an.

Der fuenfte: die Shelly-Gen1-Relais hatten weder eine Kommando-URL noch eine
URL am Zustand. Ohne die weiss niemand, was zu schicken und wo nachzusehen
ist. Gen1 schaltet ueber ?turn=on|off|toggle und meldet sich in "ison".

Damit entfaellt auch die Uebersetzungstabelle im HTTPTransport: die
Zuordnung gehoert ins Geraetemodell, nicht in den Runner.

Zwei Luecken auf der Runner-Seite, die derselbe Lauf gezeigt hat:

 * WLED liess sich gar nicht ansteuern - es gab keinen Transport dafuer.
   Der neue nutzt die JSON-Vorlage aus command_url, fuellt die Platzhalter
   und schickt sie am Stueck an /json/state.

 * Tahoma wurde am Schema "io://" erkannt. Das Schema beschreibt aber die
   Funkart: dieselbe Box liefert rts:// fuer die Dachfenster und
   internal:// fuer die Alarmanlage. Drei von 22 Geraeten fielen durch.
   Erkannt wird jetzt an der Box-Kennung in der URL.

Nachgemessen: zwei Laeufe hintereinander aendern keine einzige Zeile mehr,
die Geraete-IDs bleiben ueber Laeufe stabil (Voraussetzung fuer die
Fremdschluessel der Automatiken), alle 54 Geraete finden genau einen
Transport, und 440 der 441 Messwerte sind tatsaechlich lesbar.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-31 14:57:29 +02:00
co-authored by Claude Opus 5
parent 0cb1fc635e
commit 631baa7333
6 changed files with 180 additions and 46 deletions
+10 -10
View File
@@ -365,13 +365,13 @@ class DatabaseManager:
INSERT INTO actor_commands (actor_id, command_name, command_url)
VALUES (%s, %s, %s)
ON DUPLICATE KEY UPDATE
command_name = VALUES(command_name)
command_url = VALUES(command_url)
"""
cursor.execute(cmd_query, (actor_id, command_name, command_url))
query = """
SELECT ID FROM actor_commands where actor_id = %s and command_url = %s
SELECT ID FROM actor_commands where actor_id = %s and command_name = %s
"""
cursor.execute(query, (actor_id, command_url))
cursor.execute(query, (actor_id, command_name))
command_id = cursor.fetchone()[0]
# Parameter einfügen
@@ -381,14 +381,14 @@ class DatabaseManager:
(command_id, parameter_name, parameter_type, min_value, max_value, possible_values, url)
VALUES (%s, %s, %s, %s, %s, %s, %s)
ON DUPLICATE KEY UPDATE
parameter_name = VALUES(parameter_name),
parameter_type = VALUES(parameter_type),
min_value = VALUES(min_value),
max_value = VALUES(max_value),
possible_values = VALUES(possible_values)
possible_values = VALUES(possible_values),
url = VALUES(url)
"""
param_name = param.get('name', '')
param_type = param.get('type', '')
param_type = self.PARAMETER_TYPES_MAPPING.get(param.get('type', ''), 4)
min_val = param.get('min')
max_val = param.get('max')
possible_vals = json.dumps(param.get('values')) if 'values' in param else ""
@@ -404,11 +404,11 @@ class DatabaseManager:
(actor_id, state_name, state_type, current_value, unit, url, possible_values)
VALUES (%s, %s, %s, %s, %s, %s, %s)
ON DUPLICATE KEY UPDATE
state_name = VALUES(state_name),
state_type = VALUES(state_type),
current_value = VALUES(current_value),
possible_values = VALUES(possible_values),
unit = VALUES(unit)
unit = VALUES(unit),
url = VALUES(url)
"""
state_name = state.get('name', '')
state_type = self.PARAMETER_TYPES_MAPPING.get(state.get('type', 0), 4)
@@ -474,10 +474,10 @@ class DatabaseManager:
(actor_id, state_name, state_type, current_value, unit, url)
VALUES (%s, %s, %s, %s, %s, %s)
ON DUPLICATE KEY UPDATE
state_name = VALUES(state_name),
state_type = VALUES(state_type),
current_value = VALUES(current_value),
unit = VALUES(unit)
unit = VALUES(unit),
url = VALUES(url)
"""
state_name = state.get('name', '')
state_type = self.PARAMETER_TYPES_MAPPING.get(state.get('type', 0), 4)
@@ -267,14 +267,15 @@ class ShellyModule(BaseModule):
'name': f"{device_name}_Switch_{i}",
'url': f"http://{ip}/rpc/Switch.Set?id={i}",
'commands': [
{'command': 'turn_on', 'parameters': []},
{'command': 'turn_off', 'parameters': []},
{'command': 'turn_on', 'url': 'on=true', 'parameters': []},
{'command': 'turn_off', 'url': 'on=false', 'parameters': []},
{'command': 'toggle', 'parameters': []}
],
'states': [
{
'name': 'output',
'type': 'boolean',
'url': 'output',
'current_value': switch_data.get('output', False)
}
]
@@ -444,14 +445,15 @@ class ShellyModule(BaseModule):
'name': f"{device_name}_Relay_{i}",
'url': f"http://{ip}/relay/{i}",
'commands': [
{'command': 'turn_on', 'parameters': []},
{'command': 'turn_off', 'parameters': []},
{'command': 'toggle', 'parameters': []}
{'command': 'turn_on', 'url': 'turn=on', 'parameters': []},
{'command': 'turn_off', 'url': 'turn=off', 'parameters': []},
{'command': 'toggle', 'url': 'turn=toggle', 'parameters': []}
],
'states': [
{
'name': 'ison',
'type': 'boolean',
'url': 'ison',
'current_value': relay.get('ison', False)
}
]