You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: docs/01_user_guide/mqtt_api.adoc
+57-16Lines changed: 57 additions & 16 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -65,6 +65,47 @@ Eine erfolgreiche Response auf `signalduino/v1/responses` hat folgende Struktur:
65
65
}
66
66
----
67
67
68
+
[[_cli_tool]]
69
+
== CLI Tool zur Steuerung (`tools/sd_mqtt_cli.py`)
70
+
71
+
Das Skript `tools/sd_mqtt_cli.py` dient als einfaches Python-Kommandozeilen-Tool, um Befehle an das PySignalduino MQTT-Gateway zu senden und die Antworten zu empfangen.
72
+
73
+
=== Installation und Ausführung
74
+
75
+
Das Tool benötigt die `paho-mqtt` Abhängigkeit, die in der `requirements-dev.txt` enthalten ist.
76
+
77
+
[source,bash]
78
+
----
79
+
pip install paho-mqtt
80
+
python3 tools/sd_mqtt_cli.py --help
81
+
----
82
+
83
+
=== Verfügbare Kommandos
84
+
85
+
|===
86
+
| Kommando | Beschreibung | Beispiel
87
+
88
+
| `reset`
89
+
| Führt einen Factory Reset durch (`set/factory_reset`).
90
+
| `python3 tools/sd_mqtt_cli.py reset`
91
+
92
+
| `get all-settings`
93
+
| Fragt alle wichtigen CC1101-Einstellungen in einer aggregierten Nachricht ab.
94
+
| `python3 tools/sd_mqtt_cli.py get all-settings`
95
+
96
+
| `get hardware-status --parameter <param>`
97
+
| Fragt einen spezifischen CC1101-Parameter ab. Parameter: `frequency`, `bandwidth`, `rampl`, `sensitivity`, `datarate`.
98
+
| `python3 tools/sd_mqtt_cli.py get hardware-status --parameter frequency`
99
+
100
+
| `get system-status --parameter <param>`
101
+
| **NEU:** Fragt einen spezifischen System-Parameter ab. Parameter: `version`, `freeram`, `uptime`.
102
+
| `python3 tools/sd_mqtt_cli.py get system-status --parameter freeram`
103
+
104
+
| `poll`
105
+
| **NEU:** Fragt nacheinander alle verfügbaren System- und CC1101-Parameter ab. Nützlich zur Diagnose des aktuellen Gerätezustands.
106
+
| `python3 tools/sd_mqtt_cli.py poll`
107
+
|===
108
+
68
109
[[_get_commands]]
69
110
== GET Commands (Status und Konfiguration abrufen)
70
111
@@ -79,51 +120,51 @@ GET-Befehle benötigen eine leere Payload (`{}`) oder nur eine `req_id`.
| Liest den Wert eines einzelnen CC1101-Registers (Adresse 0x00). Der Befehl nimmt keinen Wert in der Payload entgegen und liest standardmäßig Register 0x00.
= ADR-004: Strukturiertes Parsing serieller Antworten für MQTT GET-Befehle
2
+
:revdate: 2026-01-06
3
+
:author: Roo
4
+
5
+
== 1. Kontext
6
+
7
+
Die MQTT-Befehle `get/cc1101/*` (z.B. `get/cc1101/config`) und `get/config/decoder` schlagen mit Timeouts fehl, obwohl die serielle Kommunikation mit der SIGNALDuino-Firmware die Antworten empfängt. Die Ursache liegt darin, dass der `MqttCommandDispatcher` eine strukturierte JSON-Payload (ein Python-Dictionary) als `data`-Feld in der MQTT-Antwort erwartet. Die zugrundeliegenden `SignalduinoCommands` Methoden geben jedoch in diesen Fällen den *rohen* String der seriellen Firmware-Antwort zurück.
8
+
9
+
Der `MqttCommandDispatcher` kann diese String-Antworten nicht direkt in das JSON-Antwortformat umwandeln, was zu einem Abbruch der Verarbeitung und damit zum Timeout führt.
Zusätzlich müssen alle `get` Befehle, die einen rohen String zurückgeben, angepasst werden, um die Konsistenz des MQTT-API zu gewährleisten.
16
+
17
+
== 2. Entscheidung
18
+
19
+
Wir werden die `SignalduinoCommands` Methoden, die serielle GET-Befehle ausführen, so modifizieren, dass sie die rohe Firmware-Antwort parsen und ein konsistentes Python-Dictionary (`Dict[str, Any]`) zurückgeben. Dieses Dictionary wird dann vom `MqttCommandDispatcher` als JSON-Payload im `data`-Feld der MQTT-Antwort verwendet.
20
+
21
+
Dies stellt sicher, dass alle erfolgreichen `GET` Anfragen über MQTT eine strukturierte und maschinenlesbare JSON-Antwort erhalten und die Timeouts vermieden werden.
22
+
23
+
=== Detaillierte Logik-Anpassungen
24
+
25
+
1. **`get_config` (CG):**
26
+
* Wird eine private Hilfsfunktion `_parse_decoder_config(response: str) -> Dict[str, int]` in [`signalduino/commands.py`](signalduino/commands.py) implementiert.
27
+
* Diese Funktion parst den `key=value;` String in ein Dictionary (z.B. `{'MS': 1, 'MU': 1, 'MC': 1, 'Mred': 1}`).
28
+
* Der Rückgabetyp von `get_config` wird von `str` auf `Dict[str, int]` geändert.
29
+
30
+
2. **`get_ccconf` (C0DnF):**
31
+
* Diese Methode gibt einen String wie `C0Dn11=<Hex-Wert>` zurück.
32
+
* Die Methode wird angepasst, um die rohe String-Antwort in ein Dictionary zu kapseln, z.B. `{'cc1101_config_string': response_string}`.
33
+
* Der Rückgabetyp von `get_ccconf` wird von `str` auf `Dict[str, str]` geändert.
34
+
35
+
3. **Weitere einfache GET-Befehle:**
36
+
* Methoden wie `get_version`, `get_free_ram`, `get_uptime` geben bereits einen geparsten Wert zurück (String oder Int), der korrekt gekapselt wird. Diese Methoden bleiben unverändert, da sie bereits einen strukturierten Wert zurückgeben, der indirekt im `data`-Feld des MQTT-Payloads landet.
37
+
38
+
== 3. Konsequenzen
39
+
40
+
=== Positive
41
+
* **Behebung der Timeouts:** Die MQTT GET-Befehle für Konfigurationen werden korrekt beantwortet und die Timeouts behoben.
42
+
* **API-Konsistenz:** Alle MQTT `GET` Antworten liefern nun eine konsistente, JSON-serialisierbare Struktur.
43
+
* **Wartbarkeit:** Der Code wird robuster, da das Parsing der seriellen Antwort in der `commands.py`-Schicht zentralisiert ist.
44
+
45
+
=== Negative
46
+
* **Refactoring:** Es müssen kleinere Refactorings in [`signalduino/commands.py`](signalduino/commands.py) durchgeführt werden, um die Rückgabetypen der Methoden anzupassen.
47
+
* **Tests/Dokumentation:** Die zugehörigen Unittests in [`tests/test_mqtt_commands.py`](tests/test_mqtt_commands.py) und die MQTT API Dokumentation in [`docs/01_user_guide/mqtt_api.adoc`](docs/01_user_guide/mqtt_api.adoc) müssen aktualisiert werden.
48
+
49
+
== 4. Alternativen
50
+
51
+
1. **Alternative 1: Parsing im `MqttCommandDispatcher`:** Die Rohergebnisse als `str` beibehalten und das Parsen spezifischer Befehlsantworten direkt im `MqttCommandDispatcher` durchführen.
52
+
* *Nachteil:* Vermischt die Zuständigkeiten. Der Dispatcher sollte nur das Routing und die Validierung übernehmen, während die `SignalduinoCommands` die Logik für die Kommunikation und das Parsen der Firmware-spezifischen Antworten enthalten sollten.
53
+
* *Abgelehnt* wegen schlechter Architektur und Verstoß gegen das Single Responsibility Principle.
54
+
55
+
2. **Alternative 2: Globaler, einfacher String-Wrapper im Dispatcher:** Jede String-Antwort global in ein einfaches Dictionary wie `{'response': <String>}` verpacken.
56
+
* *Nachteil:* Führt zu einer inkonsistenten API, da einige Befehle (wie `get/config/decoder`) semantisch reiche, parsbare Daten liefern, die als roher String versteckt wären.
57
+
* *Abgelehnt* zugunsten einer semantisch korrekten, strukturierten Antwort.
Aktuell weichen die JSON-Antwortstrukturen für die Abfrage einzelner CC1101-Parameter via MQTT (z.B. Topic `get/cc1101/bandwidth`) von der Struktur der Gesamt-Abfrage (Topic `get/cc1101/settings`) ab.
Diese Inkonsistenz erschwert die automatisierte Verarbeitung der Antworten, da Clients je nach Abfragetyp unterschiedliche JSON-Pfade parsen müssen. Ziel ist eine konsistente Struktur, bei der die JSON-Knotennamen für die einzelnen Parameter in beiden Abfragetypen identisch sind.
14
+
15
+
[[entscheidung]]
16
+
== Entscheidung
17
+
18
+
Die JSON-Antwortstruktur für alle CC1101-Parameter-Abfragen wird vereinheitlicht. Die Schlüsselnamen der einzelnen Parameter in der JSON-Antwort werden in beiden Abfragetypen (Einzelparameter und Gesamt-Settings) identisch verwendet. Es wird entschieden, die Schlüssel der Einzelparameter ohne umschließendes Wrapper-Objekt zu verwenden.
19
+
20
+
* **Antwort auf `get/cc1101/parameter` (z.B. `get/cc1101/bandwidth`):**
21
+
```json
22
+
{"bandwidth": "X kHz"}
23
+
```
24
+
* **Antwort auf `get/cc1101/settings`:**
25
+
```json
26
+
{
27
+
"bandwidth": "X kHz",
28
+
"rampl": "Y dbm",
29
+
"sensitivity": "Z",
30
+
"datarate": "A kbps"
31
+
}
32
+
```
33
+
Die `settings`-Antwort ist somit eine direkte Aggregation der Einzelparameter-Antworten.
34
+
35
+
[[konsequenzen]]
36
+
== Konsequenzen
37
+
38
+
=== Positive Konsequenzen
39
+
* **Konsistenz:** Vereinfacht das Parsen für MQTT-Clients, da die logischen Parameternamen (z.B. `bandwidth`) immer als JSON-Schlüssel auf der obersten Ebene der jeweiligen Antwort verwendet werden.
40
+
* **Wartbarkeit:** Reduziert die Komplexität in der Implementierung, da die Logik zur Generierung der Parameterdaten wiederverwendet werden kann.
41
+
42
+
=== Negative Konsequenzen
43
+
* **Breaking Change:** Bestehende Clients, die sich auf eine Wrapper-Struktur wie `{"cc1101": {...}}` bei der Gesamt-Abfrage (`get/cc1101/settings`) verlassen, müssen angepasst werden.
44
+
* **Migration:** Die Server-Logik für die MQTT-Antworten in der PySignalduino-Implementierung muss entsprechend geändert werden.
45
+
46
+
[[alternativen]]
47
+
== Alternativen
48
+
* **Alternative A: Wrapper in Einzelabfragen beibehalten:** Man könnte die Einzelabfrage um den CC1101-Wrapper erweitern (z.B. `get/cc1101/bandwidth` -> `{"cc1101": {"bandwidth": "X kHz"}}`). Dies wurde abgelehnt, da es unnötige Verschachtelung für Einzelwerte einführt und die Lesbarkeit des Payloads verschlechtert.
49
+
* **Alternative B: Einzelabfragen als reiner Wert:** Die Antwort könnte nur den reinen Wert zurückgeben (z.B. `get/cc1101/bandwidth` -> `"X kHz"`). Dies wurde abgelehnt, da es das JSON-Format verlässt und der Parametername im Payload verloren ginge, was die Eindeutigkeit erschwert.
0 commit comments