Skip to content

Commit c3c36c4

Browse files
author
sidey79
committed
feat: implement mqtt commands for factory reset and settings retrieval
Refactor MQTT handling to use MqttCommandDispatcher. Add CLI tool for MQTT commands. Add tests and architecture documentation.
1 parent 36708f2 commit c3c36c4

16 files changed

+1231
-137
lines changed

.roo/mcp.json

Lines changed: 1 addition & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1 @@
1-
{
2-
"mcpServers": {
3-
"filesystem": {
4-
"command": "npx",
5-
"args": [
6-
"-y",
7-
"@modelcontextprotocol/server-filesystem",
8-
"/workspaces/PySignalduino"
9-
]
10-
},
11-
"git": {
12-
"command": "uvx",
13-
"args": [
14-
"mcp-server-git",
15-
"--repository",
16-
"/workspaces/PySignalduino"
17-
]
18-
}
19-
}
20-
}
1+
{"mcpServers":{"filesystem":{"command":"npx","args":["-y","@modelcontextprotocol/server-filesystem","/workspaces/PySignalduino"],"alwaysAllow":["edit_file","read_text_file","search_files","read_multiple_files"]},"git":{"command":"uvx","args":["mcp-server-git","--repository","/workspaces/PySignalduino"],"alwaysAllow":["git_diff_unstaged","git_checkout"]}}}

.vscode/settings.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@
5252
"host": "mqtt",
5353
"port": 1883,
5454
"clientId": "vsmqtt_client_db93",
55-
"savedSubscriptions": ['signalduino/']
55+
"savedSubscriptions": ['signalduino/v1/responses','signalduino/v1/commands','signalduino/v1/errors']
5656
}
5757
]
5858
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
= ADR 002: Verwendung des MqttCommandDispatcher für die MQTT-Befehlsbehandlung
2+
:doctype: article
3+
:encoding: utf-8
4+
:lang: de
5+
:status: Accepted
6+
:decided-at: 2026-01-04
7+
:decided-by: Roo (Architekt)
8+
9+
[[kontext]]
10+
== Kontext
11+
12+
Die MQTT-Befehlsbehandlung in `signalduino/mqtt.py` erfolgt derzeit über eine hartcodierte `if/elif`-Kette in der Methode `_handle_command` (Zeile 108). Diese Struktur ist schwer wartbar und skaliert schlecht, sobald neue Befehle hinzugefügt werden müssen, wie es für Factory Reset und das Abrufen von Hardware-Einstellungen erforderlich ist.
13+
14+
Der Code enthält bereits einen generischen, schema-validierenden Befehls-Dispatcher, den `MqttCommandDispatcher` (definiert in `signalduino/commands.py`). Dieser Dispatcher ist so konzipiert, dass er Befehlspfade gegen eine zentrale `COMMAND_MAP` prüft, Payloads validiert und die Ausführung an die entsprechenden Methoden im `SignalduinoController` delegiert.
15+
16+
Die zentrale Verwaltung der Befehle und deren Validierung ist eine bewährte Methode, um die Robustheit und Erweiterbarkeit der Schnittstelle zu gewährleisten.
17+
18+
[[entscheidung]]
19+
== Entscheidung
20+
21+
Die hartcodierte `if/elif`-Logik in `signalduino/mqtt.py` wird durch die Verwendung des `MqttCommandDispatcher` ersetzt.
22+
23+
1. Der `MqttPublisher` in `signalduino/mqtt.py` wird eine Instanz des `MqttCommandDispatcher` erhalten.
24+
2. Die Methode `_handle_command` in `MqttPublisher` wird umgeschrieben, um den eingehenden Befehlspfad und Payload direkt an `MqttCommandDispatcher.dispatch()` zu übergeben.
25+
3. Die Fehler- und Erfolgsantworten werden vom `MqttCommandDispatcher` zurückgegeben und von `MqttPublisher` an die entsprechenden MQTT-Topics (`/responses` und `/errors`) publiziert.
26+
27+
[[konsequenzen]]
28+
== Konsequenzen
29+
30+
=== Positive Konsequenzen
31+
* **Erweiterbarkeit:** Neue MQTT-Befehle (wie Factory Reset und Hardware Settings) können einfach durch Hinzufügen eines Eintrags zur `COMMAND_MAP` und der zugehörigen Controller-Methode hinzugefügt werden, ohne `signalduino/mqtt.py` zu ändern.
32+
* **Trennung der Zuständigkeiten:** Die Verarbeitung der Befehlslogik (Validierung, Mapping, Ausführung) wird von der reinen MQTT-Transportlogik getrennt.
33+
* **Validierung:** Alle eingehenden MQTT-Payloads werden automatisch gegen definierte JSON-Schemata validiert, was die Fehleranfälligkeit der Implementierung reduziert.
34+
* **Konsistente Fehlerbehandlung:** Erfolgs- und Fehlerantworten werden an zentraler Stelle standardisiert.
35+
36+
=== Negative Konsequenzen
37+
* **Refactoring-Aufwand:** Die bestehende Logik in `signalduino/mqtt.py` muss entfernt und durch den Dispatcher-Aufruf ersetzt werden.
38+
* **Kopplung an Controller:** Der Dispatcher ist direkt an den `SignalduinoController` gekoppelt (was bereits der Fall war und akzeptiert wird).
39+
40+
[[alternativen]]
41+
== Alternativen
42+
* **Beibehaltung der if/elif-Kette:** Dies wurde abgelehnt, da es gegen die Prinzipien der Wartbarkeit und der Single Responsibility Principle (SRP) verstößt.
43+
* **Anderer Dispatch-Mechanismus:** Die Verwendung des vorhandenen `MqttCommandDispatcher` ist die pragmatischste Lösung, da die Klasse bereits existiert und die Validierungsinfrastruktur bietet.
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
= 003. Verwendung von CC1101-Standardformeln für Frequenz und Datenrate
2+
:revdate: 2026-01-04
3+
:status: Accepted
4+
5+
== Context
6+
Die Implementierung der MQTT SET-Befehle für CC1101-Parameter (Frequenz, Datenrate) erfordert die Umrechnung von physikalischen Werten (MHz, kBaud) in die spezifischen Registerwerte des CC1101-Chips.
7+
8+
Andere Parameter wie Bandbreite, Sensitivity und Rampl verwendeten früher spezielle, abstraktere Kommandos des Signalduino-Firmware-Protokolls (`C101`, `X4C`, `X5C`).
9+
10+
Um die Steuerung der CC1101-Parameter zu konsolidieren, wurden die Spezialbefehle (`X4C`, `X5C`) in der Python-Implementierung durch generische Register-Writes (`W`) ersetzt, wobei die Umrechnungslogik (z.B. Index in Registerwert) in Python implementiert wurde.
11+
12+
Für Frequenz und Datenrate existiert keine solche Abstraktion im Signalduino-Protokoll, oder die vorhandene Logik ist unvollständig/unzureichend für eine präzise Steuerung.
13+
14+
15+
== Decision
16+
Die Umrechnung von Frequenz (MHz) in die drei Registerwerte (FREQ2, FREQ1, FREQ0) und die Umrechnung der Datenrate (kBaud) in MDMCFG4/MDMCFG3-Registerwerte erfolgt *direkt* in der Python-Implementierung von `SignalduinoCommands` unter Verwendung der im CC1101-Datenblatt definierten Standardformeln (z.B. Freq = f_xosc * FREQ / 2^16).
17+
18+
Diese Registerwerte werden dann über generische CC1101-Schreibbefehle des Signalduino-Protokolls (`W<RegisterAddress><Value>`) übertragen.
19+
20+
Nach dem Senden aller Register-SET-Befehle muss die Methode `SignalduinoCommands.cc1101_write_init()` aufgerufen werden, um die CC1101-Konfiguration erneut in das Chip-Register zu schreiben und die Änderungen zu aktivieren.
21+
22+
== Consequences
23+
* **Positiv:** Gewährleistet maximale Präzision bei der Einstellung von Frequenz und Datenrate, da die direkte CC1101-Berechnung verwendet wird. Die Logik ist in Python gekapselt und leicht testbar (Unit Tests).
24+
* **Negativ:** Erhöht die Komplexität der `SignalduinoCommands`-Klasse, da sie nun die CC1101-Register-Berechnungslogik enthalten muss.
25+
* **Neutral:** Alle CC1101-Set-Befehle, die Register schreiben (einschließlich Rampl und Sensitivity), verwenden nun die generischen `W<RegisterAddress><Value>`-Befehle. Dies konsolidiert die Logik in Python (phys. Wert -> Registerwert) und vereinfacht die Implementierung auf Kosten des Verzichts auf spezifische Firmware-Spezialbefehle (`X4C`, `X5C`). Der `C101`-Befehl für die Bandbreite wird vorerst beibehalten, da er eine Abstraktion der Firmware darstellt.
26+
27+
== Alternatives Considered
28+
* **Alternative 1: Nur Signalduino-Spezialbefehle verwenden:**
29+
* _Ablehnungsgrund:_ Für Frequenz und Datenrate gibt es keine oder keine ausreichend präzisen/dokumentierten Signalduino-Spezialbefehle, die eine Einstellung über MQTT in physikalischen Einheiten (MHz, kBaud) ermöglichen.
30+
* **Alternative 2: Berechnung in die Controller-Klasse verschieben:**
31+
* _Ablehnungsgrund:_ Die `SignalduinoCommands`-Klasse ist der logische Ort für die Umrechnung von physikalischen Einheiten in serielle Protokolle, da sie die Schnittstelle zum physischen Gerät darstellt. Die `SignalduinoController`-Klasse soll nur die MQTT-Payload entpacken.
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
= Architektur-Proposal: MQTT-basierter Factory Reset und Hardware-Status
2+
:doctype: article
3+
:encoding: utf-8
4+
:lang: de
5+
:author: Roo (Architekt)
6+
:email: roo@pythonsignalduino.com
7+
:revnumber: 1.0
8+
:revdate: 2026-01-04
9+
:xrefstyle: full
10+
11+
[[status]]
12+
== Status
13+
14+
|===
15+
|Status|Datum der letzten Änderung|Entscheidungsträger
16+
17+
|Draft
18+
|2026-01-04
19+
|Roo (Architekt)
20+
|===
21+
22+
[[zusammenfassung]]
23+
== 1. Zusammenfassung (Executive Summary)
24+
25+
Dieses Proposal beschreibt die Einführung von zwei neuen MQTT-Funktionalitäten: die Durchführung eines Factory Resets auf dem Signalduino-Gerät und das Abrufen der aktuellen CC1101-Hardware-Einstellungen (Frequenz, Bandbreite, Verstärkung, Empfindlichkeit, Datenrate) über MQTT. Die Implementierung basiert auf dem Refactoring des MQTT-Befehls-Handlings, indem der vorhandene `MqttCommandDispatcher` zentral in `signalduino/mqtt.py` verwendet wird.
26+
27+
[[problem-definition]]
28+
== 2. Problemstellung und Motivation
29+
30+
Aktuell müssen Hardware-Einstellungen direkt über die serielle Konsole des Signalduino-Geräts abgefragt werden. Für einen Factory Reset (Serial Command `e (EEPROM Defaults)`) fehlt ein hochstufiger, zugänglicher Mechanismus. Die bestehende MQTT-Befehlslogik in `signalduino/mqtt.py` ist eine unstrukturierte `if/elif`-Kette, die eine Erweiterung erschwert. Die Motivation ist, eine vollständig über MQTT fernsteuerbare und auslesbare Schnittstelle für die Geräteeinstellungen zu schaffen.
31+
32+
[[ziele]]
33+
== 3. Ziele
34+
35+
1. **Refactoring:** Ersetze die `if/elif`-Logik in `signalduino/mqtt.py` durch den [`MqttCommandDispatcher`](signalduino/commands.py:193) (siehe xref:ADR-002-mqtt-command-dispatcher.adoc[ADR 002]).
36+
2. **Factory Reset:** Definiere und implementiere den MQTT-Befehl für den Signalduino Factory Reset (`e`).
37+
3. **Hardware-Status-Abruf:** Implementiere neue Controller-Methoden und MQTT-Befehle, um die aktuellen CC1101-Einstellungen (Freq, Bandwidth, rAmpl, sens, DataRate) auszulesen.
38+
4. **Tooling:** Entwirf die Schnittstelle für ein CLI-Helfer-Tool zum Testen und Steuern dieser Befehle.
39+
40+
[[vorgeschlagene-architektur]]
41+
== 4. Vorgeschlagene Architektur
42+
43+
Die Architektur nutzt die bereits existierende Schichtenarchitektur von PySignalduino (MQTT Publisher -> Controller -> Serial Commands). Der Schlüssel liegt in der Zentralisierung des Befehls-Routings im `MqttCommandDispatcher`.
44+
45+
=== 4.1. Komponenten-Diagramm (Mermaid)
46+
47+
[mermaid]
48+
----
49+
graph TD
50+
A[MQTT Client] --> B(MqttPublisher / Listener);
51+
B --> C{MqttCommandDispatcher};
52+
C --> D[SignalduinoController];
53+
D --> E[SignalduinoCommands (Serial API)];
54+
E --> F[Signalduino Hardware];
55+
56+
subgraph Signal Path (Commands)
57+
B -- Refactored Handler --> C
58+
C -- Payload Validation / Routing --> D
59+
D -- High-Level Call --> E
60+
E -- Low-Level Serial --> F
61+
end
62+
----
63+
64+
=== 4.2. Sequenz-Diagramm (Mermaid)
65+
66+
Dieses Diagramm zeigt den Ablauf für den Factory Reset und das Abrufen der Bandbreite.
67+
68+
[mermaid]
69+
----
70+
sequenceDiagram
71+
participant Mq as MQTT Client (Tool)
72+
participant Mqp as MqttPublisher (signalduino/mqtt.py)
73+
participant Disp as MqttCommandDispatcher
74+
participant Ctrl as SignalduinoController
75+
participant Cmd as SignalduinoCommands
76+
participant SDU as Signalduino Hardware
77+
78+
group Factory Reset (Command)
79+
Mq->>Mqp: PUBLISH (Topic: .../commands/command/factory_reset, Payload: {"req_id": "123"})
80+
Mqp->>Disp: dispatch("command/factory_reset", payload)
81+
Disp->>Ctrl: command_factory_reset(payload)
82+
Ctrl->>Cmd: send_command("e")
83+
Cmd->>SDU: Serial: e
84+
SDU-->>Cmd: Serial: OK / Timeout
85+
Cmd-->>Ctrl: Result
86+
Ctrl-->>Disp: Response Data
87+
Disp-->>Mqp: Result Dict
88+
Mqp->>Mq: PUBLISH (Topic: .../responses, Payload: success: true, req_id: "123")
89+
end
90+
91+
group Hardware Status (GET)
92+
Mq->>Mqp: PUBLISH (Topic: .../commands/get/cc1101/bandwidth, Payload: {"req_id": "456"})
93+
Mqp->>Disp: dispatch("get/cc1101/bandwidth", payload)
94+
Disp->>Ctrl: get_cc1101_bandwidth(payload)
95+
Ctrl->>Cmd: read_cc1101_register(0x10)
96+
Cmd->>SDU: Serial: C10
97+
SDU-->>Cmd: Serial: C10 = 02 (Beispiel)
98+
Cmd->>Cmd: Decode to Bandwidth (z.B. 102 kHz)
99+
Cmd-->>Ctrl: 102 (kHz)
100+
Ctrl-->>Disp: 102
101+
Disp-->>Mqp: Response Data
102+
Mqp->>Mq: PUBLISH (Topic: .../responses, Payload: data: 102, req_id: "456")
103+
end
104+
----
105+
106+
[[schnittstellen]]
107+
== 5. Betroffene Schnittstellen (APIs, MQTT Topics)
108+
109+
=== 5.1. Neue MQTT Topics (PUBLISH an)
110+
* `signalduino/v1/commands/command/factory_reset`
111+
* `signalduino/v1/commands/get/cc1101/bandwidth`
112+
* `signalduino/v1/commands/get/cc1101/rampl`
113+
* `signalduino/v1/commands/get/cc1101/sensitivity`
114+
* `signalduino/v1/commands/get/cc1101/datarate`
115+
116+
=== 5.2. `SignalduinoCommands` Erweiterungen (Serial API)
117+
Neue Methoden in [`SignalduinoCommands`](signalduino/commands.py:20), die das Lesen der CC1101-Register kapseln und die Rohwerte in nutzbare Einheiten (kHz, dB) umrechnen:
118+
* `factory_reset()` (Serial Command `e`)
119+
* `get_bwidth()` (liest Register `0x10` und berechnet die Bandbreite)
120+
* `get_rampl()` (liest Register `0x1B` und decodiert die Verstärkung)
121+
* `get_sens()` (liest Register `0x1D` und decodiert die Empfindlichkeit)
122+
* `get_datarate()` (liest Register `0x10` und `0x11` und berechnet die Datenrate)
123+
124+
=== 5.3. `MqttCommandDispatcher.COMMAND_MAP` Erweiterungen
125+
Neue Einträge in der Map zur Weiterleitung der obigen MQTT Topics an die entsprechenden Controller-Methoden.
126+
127+
[[alternativen]]
128+
== 6. Alternativen in Betracht gezogen
129+
130+
* **Kein Refactoring:** Das Beibehalten der `if/elif`-Kette in `signalduino/mqtt.py` wurde abgelehnt, da es die Wartbarkeit reduziert und dem Architekturprinzip der Trennung der Zuständigkeiten widerspricht (siehe ADR-002).
131+
* **Keine Abfrage einzelner Werte:** Stattdessen nur einen Sammelbefehl (`get/cc1101/status`) implementieren. Dies wurde abgelehnt, da es die Konsistenz mit dem bereits vorhandenen `get/cc1101/frequency` bricht und nicht die Flexibilität für clientspezifische Abfragen bietet.
132+
133+
[[auswirkungen]]
134+
== 7. Auswirkungen und Migration
135+
136+
* **Bestehender Code:** Die Methode `MqttPublisher._handle_command` in `signalduino/mqtt.py` muss vollständig refaktorisiert werden, um den Dispatcher zu verwenden. Die bestehende Logik für `get/system/version` und `get/cc1101/frequency` wird entfernt und über den Dispatcher abgewickelt.
137+
* **Abhängigkeiten:** Keine neuen externen Abhängigkeiten erforderlich.
138+
139+
[[implementierungsplan]]
140+
== 8. Implementierungs-Plan
141+
142+
Der detaillierte Implementierungsplan wird in Phase 2 erstellt, basiert aber auf den folgenden High-Level-Schritten:
143+
144+
1. **Refactoring:** Initialisiere den `MqttCommandDispatcher` in `MqttPublisher.__init__` und aktualisiere `MqttPublisher._handle_command` zur Verwendung des Dispatchers.
145+
2. **Controller-Erweiterung:** Füge die High-Level-Methoden `command_factory_reset`, `get_cc1101_bandwidth`, `get_cc1101_rampl`, `get_cc1101_sensitivity`, `get_cc1101_datarate` zum `SignalduinoController` hinzu.
146+
3. **Serial Commands:** Implementiere die entsprechenden Low-Level-Methoden (`factory_reset`, `get_bwidth`, `get_rampl`, `get_sens`, `get_datarate`) in [`SignalduinoCommands`](signalduino/commands.py:20) inklusive der Register-Decodierungslogik.
147+
4. **Dispatcher-Aktualisierung:** Erweitere `COMMAND_MAP` in `signalduino/commands.py` um die neuen Befehle und deren Schemata.
148+
149+
[[cli-tool]]
150+
== 9. CLI Tool Design
151+
152+
Es wird ein kleines Python-Helfer-Tool (z.B. `signalduino-mqtt-cli`) entworfen, das über die Kommandozeile MQTT-Befehle senden kann. Dieses Tool wird die neuen Funktionen demonstrieren und zur Verifikation dienen.
153+
154+
=== 9.1. Befehlsdesign
155+
* `sd-mqtt-cli reset --req-id <ID>` (Sendet `command/factory_reset`)
156+
* `sd-mqtt-cli get hardware-status --req-id <ID> --parameter bandwidth` (Sendet `get/cc1101/bandwidth`)
157+
* `sd-mqtt-cli get hardware-status --all --req-id <ID>` (Optional: Implementiert einen Batch-Abruf oder ruft alle einzelnen GET-Befehle sequenziell ab und gibt das konsolidierte Ergebnis aus.)
158+
159+
Dieses Tool würde die `MqttPublisher` Logik des Hauptprogramms in einem CLI-Kontext nachbilden, um PUBLISH/SUBSCRIBE für Request/Response zu handhaben.

0 commit comments

Comments
 (0)