|
| 1 | += Architektur-Proposal: MQTT-Kommando `get/cc1101/frequency` |
| 2 | +:author: Roo |
| 3 | +:revdate: 2026-01-03 |
| 4 | +:page-layout: proposal |
| 5 | +:sectnums: |
| 6 | +:toc: left |
| 7 | +:toclevels: 3 |
| 8 | + |
| 9 | +[[section-hintergrund]] |
| 10 | +== 1. Hintergrund und Motivation |
| 11 | + |
| 12 | +Dieses Proposal beschreibt die Implementierung des MQTT-Kommandos `get/cc1101/frequency`, das es Benutzern ermöglicht, die aktuell im CC1101-Transceiver eingestellte Funkfrequenz abzufragen. Dies ist ein notwendiges Feature zur Diagnose und Verifikation der Hardwarekonfiguration und stellt eine Ergänzung zu den bestehenden `get`-Kommandos dar. |
| 13 | + |
| 14 | +[[section-losungsansatz]] |
| 15 | +== 2. Lösungsansatz und Komponenten |
| 16 | + |
| 17 | +Der Befehl wird über das MQTT-Topic `[base_topic]/commands/get/cc1101/frequency` empfangen und löst eine Kette von Funktionsaufrufen aus: |
| 18 | + |
| 19 | +1. *`signalduino/mqtt.py`*: Empfängt das Kommando und ruft die Kommando-Logik auf. |
| 20 | +2. *`signalduino/commands.py`*: Implementiert die High-Level-Logik, welche die Registerwerte von der Hardware abruft und die Frequenz berechnet. |
| 21 | +3. *`signalduino/hardware.py`*: Stellt eine neue Methode zum Lesen der FREQ2, FREQ1 und FREQ0 Register bereit. |
| 22 | + |
| 23 | +Das Ergebnis wird als JSON-Objekt auf dem zentralen Antwort-Topic (`<base_topic>/responses`) veröffentlicht, um Konsistenz mit bestehenden Befehlen zu gewährleisten. |
| 24 | + |
| 25 | +[[section-komponenten-details]] |
| 26 | +== 3. Komponenten-Details |
| 27 | + |
| 28 | +=== 3.1. `signalduino/hardware.py` |
| 29 | + |
| 30 | +Wir benötigen eine Methode, um die drei Frequenzregister des CC1101 auszulesen. Da PySignalduino bereits die Methode `read_register(address)` in der Hardware-Klasse (wahrscheinlich in `signalduino/hardware.py`) implementiert, ist eine neue, dedizierte Methode in der `Hardware`-Klasse erforderlich. |
| 31 | + |
| 32 | +[source, python] |
| 33 | +---- |
| 34 | +# In signalduino/hardware.py (angenommene Klasse) |
| 35 | +async def get_frequency_registers(self) -> int: |
| 36 | + """Liest die CC1101 Frequenzregister (FREQ2, FREQ1, FREQ0) und kombiniert sie zu einem 24-Bit-Wert (F_REG).""" |
| 37 | + # Adressen der Register |
| 38 | + FREQ2 = 0x0D |
| 39 | + FREQ1 = 0x0E |
| 40 | + FREQ0 = 0x0F |
| 41 | +
|
| 42 | + # Annahme: read_register gibt den Wert des Registers zurück |
| 43 | + freq2 = await self.read_register(FREQ2) |
| 44 | + freq1 = await self.read_register(FREQ1) |
| 45 | + freq0 = await self.read_register(FREQ0) |
| 46 | +
|
| 47 | + # Die Register bilden eine 24-Bit-Zahl: (FREQ2 << 16) | (FREQ1 << 8) | FREQ0 |
| 48 | + f_reg = (freq2 << 16) | (freq1 << 8) | freq0 |
| 49 | + return f_reg |
| 50 | +---- |
| 51 | + |
| 52 | +=== 3.2. `signalduino/commands.py` |
| 53 | + |
| 54 | +Die Logik zur Frequenzberechnung wird hier implementiert. Die Frequenzformel lautet: |
| 55 | +$$f_{RF} = \frac{26000000}{65536} \times F_{REG} \times 10^{-6} \, \text{MHz}$$ |
| 56 | +oder vereinfacht: |
| 57 | +$$f_{RF} = \frac{26}{65536} \times F_{REG} \, \text{MHz}$$ |
| 58 | + |
| 59 | +[source, python] |
| 60 | +---- |
| 61 | +# In signalduino/commands.py |
| 62 | +async def get_frequency(self) -> float: |
| 63 | + """Ruft die Frequenzregister ab und berechnet die Frequenz in MHz.""" |
| 64 | + # F_REG: 24-Bit-Wert aus FREQ2, FREQ1, FREQ0 |
| 65 | + f_reg = await self.hardware.get_frequency_registers() |
| 66 | +
|
| 67 | + # Quarzfrequenz (FXOSC) ist 26 MHz |
| 68 | + DIVIDER = 65536.0 |
| 69 | +
|
| 70 | + # Die Frequenz in MHz ist: (26.0 / 65536.0) * F_REG |
| 71 | + frequency_mhz = (26.0 / DIVIDER) * f_reg |
| 72 | + |
| 73 | + return frequency_mhz |
| 74 | +---- |
| 75 | + |
| 76 | +=== 3.3. `signalduino/mqtt.py` |
| 77 | + |
| 78 | +Ein neuer Command Handler wird in der `MqttHandler`-Klasse registriert. Die Antwort folgt dem `<base_topic>/responses` Schema. |
| 79 | + |
| 80 | +[source, python] |
| 81 | +---- |
| 82 | +# In signalduino/mqtt.py (_handle_command Methode) |
| 83 | +elif command_name == "get/cc1101/frequency": |
| 84 | + try: |
| 85 | + # Payload-String zu Dict konvertieren |
| 86 | + payload_dict = json.loads(payload) |
| 87 | + req_id = payload_dict.get("req_id", "NO_REQ_ID") |
| 88 | +
|
| 89 | + # Aufruf der asynchronen Controller-Methode |
| 90 | + frequency_mhz = await self.controller.get_frequency(payload_dict) |
| 91 | + |
| 92 | + response = { |
| 93 | + "command": command_name, |
| 94 | + "success": True, |
| 95 | + "req_id": req_id, |
| 96 | + "payload": { |
| 97 | + "frequency_mhz": round(frequency_mhz, 4) |
| 98 | + }, |
| 99 | + } |
| 100 | + |
| 101 | + await self.publish_simple( |
| 102 | + subtopic="responses", |
| 103 | + payload=json.dumps(response), |
| 104 | + retain=False |
| 105 | + ) |
| 106 | + self.logger.info("Successfully published current frequency for req_id %s.", req_id) |
| 107 | + |
| 108 | + except Exception as e: |
| 109 | + self.logger.exception("Error processing %s command.", command_name) |
| 110 | + |
| 111 | + # Versuch, req_id zu extrahieren, falls JSON-Parsing erfolgreich war |
| 112 | + try: |
| 113 | + req_id = json.loads(payload).get("req_id", "NO_REQ_ID") |
| 114 | + except json.JSONDecodeError: |
| 115 | + req_id = "NO_REQ_ID" |
| 116 | + |
| 117 | + await self.publish_simple( |
| 118 | + subtopic="errors", |
| 119 | + payload=json.dumps({ |
| 120 | + "command": command_name, |
| 121 | + "success": False, |
| 122 | + "req_id": req_id, |
| 123 | + "error": f"Internal error processing command: {e}", |
| 124 | + }), |
| 125 | + retain=False |
| 126 | + ) |
| 127 | +
|
| 128 | +# Registrierung des Handlers im Haupt-Loop ist implizit über die _handle_command Methode |
| 129 | +---- |
| 130 | + |
| 131 | +[[section-mqtt-payload]] |
| 132 | +== 4. MQTT Request- und Response-Payload-Format |
| 133 | + |
| 134 | +=== 4.1. Request Payload (auf \`[base_topic]/commands/get/cc1101/frequency\`) |
| 135 | + |
| 136 | +Der Request MUSS einen JSON-Payload enthalten, der eine `req_id` zur Korrelation der Antwort bereitstellt. |
| 137 | + |
| 138 | +[cols="1,1,2"] |
| 139 | +|=== |
| 140 | +| Feld | Typ | Beschreibung |
| 141 | +| `req_id` | string | Eine vom Client generierte eindeutige ID, um die Antwort (Response/Error) dem Request zuzuordnen. Wird keine `req_id` bereitgestellt, wird automatisch der Wert `"NO_REQ_ID"` verwendet. |
| 142 | +|=== |
| 143 | + |
| 144 | +*Beispiel Request Payload:* |
| 145 | +[source, json] |
| 146 | +---- |
| 147 | +{ |
| 148 | + "req_id": "client-12345-freq-req-A" |
| 149 | +} |
| 150 | +---- |
| 151 | + |
| 152 | + |
| 153 | +=== 4.2. Response Payload (auf \`<base_topic>/responses\`) |
| 154 | + |
| 155 | +Die Antwort wird auf dem Topic `<base_topic>/responses` im JSON-Format veröffentlicht (bei Erfolg). |
| 156 | + |
| 157 | +[cols="1,1,2"] |
| 158 | +|=== |
| 159 | +| Feld | Typ | Beschreibung |
| 160 | +| `command` | string | Der ursprünglich ausgeführte Befehl (`get/cc1101/frequency`). |
| 161 | +| `success` | boolean | Status der Operation (`true` oder `false`). |
| 162 | +| `req_id` | string | Die `req_id` aus dem Request-Payload. |
| 163 | +| `payload` | object | Enthält die Nutzdaten (nur bei `success: true`). |
| 164 | +| `payload.frequency_mhz` | number | Die berechnete Frequenz in MHz, gerundet auf 4 Dezimalstellen. |
| 165 | +| `error` | string | Nur bei `success: false`, enthält die Fehlerbeschreibung. |
| 166 | +|=== |
| 167 | + |
| 168 | +*Erfolgreiche Antwort (auf \`<base_topic>/responses\`):* |
| 169 | +[source, json] |
| 170 | +---- |
| 171 | +{ |
| 172 | + "command": "get/cc1101/frequency", |
| 173 | + "success": true, |
| 174 | + "req_id": "client-12345-freq-req-A", |
| 175 | + "payload": { |
| 176 | + "frequency_mhz": 433.920 |
| 177 | + } |
| 178 | +} |
| 179 | +---- |
| 180 | + |
| 181 | +*Fehlerhafte Antwort (auf \`<base_topic>/errors\`):* |
| 182 | +[source, json] |
| 183 | +---- |
| 184 | +{ |
| 185 | + "command": "get/cc1101/frequency", |
| 186 | + "success": false, |
| 187 | + "req_id": "client-12345-freq-req-A", |
| 188 | + "error": "Hardware nicht initialisiert" |
| 189 | +} |
| 190 | +---- |
| 191 | + |
| 192 | +[[section-sequenzdiagramm]] |
| 193 | +== 5. Sequenzdiagramm |
| 194 | + |
| 195 | +Das folgende Sequenzdiagramm visualisiert den Nachrichtenfluss für den `get/cc1101/frequency`-Befehl. |
| 196 | + |
| 197 | +[mermaid] |
| 198 | +---- |
| 199 | +sequenceDiagram |
| 200 | + participant U as Benutzer (MQTT Client) |
| 201 | + participant M as MqttHandler (signalduino/mqtt.py) |
| 202 | + participant C as Commands (signalduino/commands.py) |
| 203 | + participant H as Hardware (signalduino/hardware.py) |
| 204 | +
|
| 205 | + U->>M: PUBLISH <base_topic>/commands/get/cc1101/frequency {req_id: "..."} |
| 206 | + M->>C: get_frequency({req_id: "..."}) |
| 207 | + C->>H: get_frequency_registers() |
| 208 | + H->>H: read_register(FREQ2) |
| 209 | + H->>H: read_register(FREQ1) |
| 210 | + H->>H: read_register(FREQ0) |
| 211 | + H-->>C: F_REG (24-bit integer) |
| 212 | + C->>C: Berechne Frequenz: (26.0 / 65536.0) * F_REG |
| 213 | + C-->>M: frequency_mhz (float) |
| 214 | + M->>M: Erstelle JSON Payload {command: "get/cc1101/frequency", success: true, req_id: "...", payload: {frequency_mhz: 433.920}} |
| 215 | + M->>U: PUBLISH <base_topic>/responses {payload} |
| 216 | +---- |
0 commit comments