# Buzzer-Kommunikationsprotokoll (vNext Entwurf) Diese Datei beschreibt den geplanten Neuentwurf des Protokolls. Ziel ist ein klar strukturiertes Binärprotokoll mit stabilen Typen, expliziter Endianness und sauberem Streaming für große Daten. ## 1) Grundregeln - Transport: USB CDC (Byte-Stream) - Multi-Byte-Integer: **Little Endian** - Integer-Typen im Protokoll: nur feste Breiten (`uint8_t`, `uint16_t`, `uint32_t`, `uint64_t`) - `bool`: als `uint8_t` (`0` = false, `1` = true) - Strings: immer `len + bytes`, ohne implizites `\0` - `unsigned long` wird im Protokoll **nicht** verwendet (plattformabhängig) > Hinweis: Obwohl nRF52 und STM32G0 beide Little Endian sind, bleibt Endianness explizit Teil der Spezifikation. ## 2) Frame-Struktur Alle Requests und Responses nutzen denselben Header. ### 2.1 Header | Feld | Typ | Beschreibung | |---|---|---| | `sync` | 4 Byte | Immer `BUZZ` (`0x42 0x55 0x5A 0x5A`) | | `frame_type` | `uint8_t` | Typ des Frames (siehe unten) | | `command_id` | `uint8_t` | Kommando-ID | | `sequence` | `uint16_t` | Anfrage-/Antwort-Korrelation | | `payload_len` | `uint32_t` | Länge von `payload` in Bytes | | `header_crc16` | `uint16_t` | CRC16 über Header ohne `sync` und ohne `header_crc16` | Danach folgen: | Feld | Typ | Beschreibung | |---|---|---| | `payload` | `byte[payload_len]` | Kommandoabhängige Nutzdaten | | `payload_crc32` | `uint32_t` | CRC32 über `payload` | ### 2.2 `frame_type` | Wert | Name | Bedeutung | |---:|---|---| | `0x01` | `REQ` | Host → Device Anfrage | | `0x10` | `RESP_ACK` | Erfolgreich, ohne Nutzdaten | | `0x11` | `RESP_DATA` | Erfolgreich, mit Nutzdaten | | `0x12` | `RESP_STREAM_START` | Start eines Datenstroms | | `0x13` | `RESP_STREAM_CHUNK` | Chunk eines Datenstroms | | `0x14` | `RESP_STREAM_END` | Ende eines Datenstroms | | `0x7F` | `RESP_ERROR` | Fehlerantwort | ## 3) Antwortmodell (ACK vs DATA) Ja, wir unterscheiden explizit: - **`RESP_ACK`**: „OK ohne Payload“ (Host muss nur Erfolg verbuchen) - **`RESP_DATA`**: „OK mit Payload“ - **`RESP_ERROR`**: Fehlercode + optionaler Detailtext Damit ist Parsing eindeutig und ohne Sonderfälle wie „OK aber vielleicht mit Daten“. ## 4) Fehlercodes Fehlercodes bleiben wie bisher inhaltlich erhalten (`P_ERR_*`), werden aber als Binärwert in `RESP_ERROR` übertragen. ### 4.1 Fehler-Payload (`RESP_ERROR`) | Feld | Typ | Beschreibung | |---|---|---| | `error_code` | `uint8_t` | Protokollfehlercode | | `detail_len` | `uint8_t` | Länge von `detail` | | `detail` | `byte[detail_len]` | Optionaler Kurztext (ASCII/UTF-8) | ## 5) Kommandos (erste Aufteilung) ## 5.1 `0x00` `GET_PROTOCOL_VERSION` **Request-Payload:** keine **Response:** `RESP_DATA` | Feld | Typ | Beschreibung | |---|---|---| | `protocol_version` | `uint16_t` | Versionsnummer des Protokolls | ## 5.2 `0x01` `GET_FIRMWARE_STATUS` **Request-Payload:** keine **Response:** `RESP_DATA` | Feld | Typ | Beschreibung | |---|---|---| | `status` | `uint8_t` | `0` = pending, `1` = confirmed | | `version_len` | `uint8_t` | Länge des Versionsstrings | | `version` | `byte[version_len]` | Firmware-Version | ## 5.3 `0x02` `GET_FLASH_STATUS` **Request-Payload:** keine **Response:** `RESP_DATA` | Feld | Typ | Beschreibung | |---|---|---| | `block_size` | `uint32_t` | Bytes pro Block | | `total_blocks` | `uint32_t` | Gesamtblöcke | | `free_blocks` | `uint32_t` | Freie Blöcke | | `path_max_len` | `uint32_t` | Maximale erlaubte Pfadlänge in Bytes | ## 5.4 `0x10` `LIST_DIR` **Request-Payload:** | Feld | Typ | Beschreibung | |---|---|---| | `path_len` | `uint8_t` | Länge von `path` | | `path` | `byte[path_len]` | Zielverzeichnis, z. B. `/` oder `/lfs/a` | `path_len` darf den in `GET_FLASH_STATUS.path_max_len` gemeldeten Wert nicht überschreiten. **Response:** `RESP_DATA` `LIST_DIR` liefert Einträge als Stream: 1. `RESP_STREAM_START` 2. `RESP_STREAM_CHUNK` pro Verzeichniseintrag 3. `RESP_STREAM_END` ### `RESP_STREAM_START` Payload | Feld | Typ | Beschreibung | |---|---|---| | `entry_count` | `uint32_t` | Anzahl Einträge, `0xFFFFFFFF` = unbekannt | ### `RESP_STREAM_CHUNK` Payload (ein Eintrag) | Feld | Typ | Beschreibung | |---|---|---| | `entry_type` | `uint8_t` | `0` = Datei, `1` = Verzeichnis | | `name_len` | `uint8_t` | Länge von `name` | | `size` | `uint32_t` | Dateigröße in Bytes (`0` für Verzeichnisse) | | `name` | `byte[name_len]` | Name ohne führenden Pfad | ### `RESP_STREAM_END` Payload - leer **Hinweis:** Rekursion bleibt Host-seitig. Der Host setzt aus angefragtem Basispfad + `name` den vollständigen Pfad zusammen. ## 5.5 `0x11` `CHECK_FILE_CRC` **Request-Payload:** | Feld | Typ | Beschreibung | |---|---|---| | `path_len` | `uint8_t` | Länge von `path` | | `path` | `byte[path_len]` | Pfad zur Datei | `path_len` darf den in `GET_FLASH_STATUS.path_max_len` gemeldeten Wert nicht überschreiten. **Response:** `RESP_DATA` | Feld | Typ | Beschreibung | |---|---|---| | `crc32` | `uint32_t` | CRC32 (Little Endian) über den Audio-Inhalt | **Hinweis:** Die CRC-Berechnung folgt der aktuellen Firmware-Logik für Audio-Dateien (ohne angehängte Tag-Daten). ## 5.6 `0x12` `MKDIR` **Request-Payload:** | Feld | Typ | Beschreibung | |---|---|---| | `path_len` | `uint8_t` | Länge von `path` | | `path` | `byte[path_len]` | Zielpfad für neues Verzeichnis | `path_len` darf den in `GET_FLASH_STATUS.path_max_len` gemeldeten Wert nicht überschreiten. **Response:** `RESP_ACK` Bei Erfolg wird eine leere ACK-Antwort gesendet. Bei Fehlern `RESP_ERROR` mit passendem `P_ERR_*`. ## 5.7 `0x13` `RM` **Request-Payload:** | Feld | Typ | Beschreibung | |---|---|---| | `path_len` | `uint8_t` | Länge von `path` | | `path` | `byte[path_len]` | Pfad zu Datei oder leerem Verzeichnis | `path_len` darf den in `GET_FLASH_STATUS.path_max_len` gemeldeten Wert nicht überschreiten. **Response:** `RESP_ACK` Bei Erfolg wird eine leere ACK-Antwort gesendet. Bei Fehlern `RESP_ERROR` mit passendem `P_ERR_*`. **Hinweis:** Rekursives Löschen bleibt Host-seitig (mehrere `LIST_DIR` + `RM` Aufrufe). ## 5.8 `0x20` `GET_TAG_BLOB` **Request-Payload:** | Feld | Typ | Beschreibung | |---|---|---| | `path_len` | `uint8_t` | Länge von `path` | | `path` | `byte[path_len]` | Pfad zur Datei | `path_len` darf den in `GET_FLASH_STATUS.path_max_len` gemeldeten Wert nicht überschreiten. **Response:** Stream 1. `RESP_STREAM_START` mit `total_len:uint32_t` 2. `RESP_STREAM_CHUNK` mit rohen Tag-Blob-Bytes 3. `RESP_STREAM_END` (leer) Der Blob-Inhalt ist die TLV-Metadatenstruktur aus `Tags.md` (ohne Footer `version+len+TAG!`). ## 5.9 `0x21` `SET_TAG_BLOB_START` Startet eine Tag-Blob-Übertragung. **Request-Payload:** | Feld | Typ | Beschreibung | |---|---|---| | `path_len` | `uint8_t` | Länge von `path` | | `path` | `byte[path_len]` | Pfad zur Datei | | `total_len` | `uint16_t` | Gesamtlänge des folgenden Blobs | **Response:** `RESP_ACK` ## 5.10 `0x22` `SET_TAG_BLOB_CHUNK` Sendet einen weiteren Chunk des Blobs. **Request-Payload:** | Feld | Typ | Beschreibung | |---|---|---| | `data` | `byte[]` | Chunk-Daten | **Response:** `RESP_ACK` ## 5.11 `0x23` `SET_TAG_BLOB_END` Schließt den Upload ab und schreibt den Blob in die Datei (ersetzt bestehende Tags). **Request-Payload:** leer **Response:** `RESP_ACK` Hinweis: Ein leerer Blob (`total_len=0`) ist erlaubt und entspricht "Tags leeren". ## 6) Große Daten (Dateien, Audio, Firmware) Für mehrere MB nicht als einzelnes `RESP_DATA` senden, sondern streamen. ### 6.1 Warum nicht ein riesiges `payload_len`? - Höheres Risiko bei Paketverlust/Timeout - Mehr RAM-/Pufferdruck auf MCU - Schlechtere Wiederanlaufstrategie bei Fehlern ### 6.2 Streaming-Ablauf 1. `RESP_STREAM_START` mit Metadaten 2. `RESP_STREAM_CHUNK` (mehrfach) 3. `RESP_STREAM_END` mit Gesamt-CRC/Abschlussstatus ### 6.3 Stream-Metadaten (`RESP_STREAM_START`) | Feld | Typ | Beschreibung | |---|---|---| | `stream_id` | `uint16_t` | ID des Streams | | `total_len` | `uint32_t` | Gesamtlänge in Bytes | | `chunk_size` | `uint16_t` | Ziel-Chunkgröße | ### 6.4 Chunk-Payload (`RESP_STREAM_CHUNK`) | Feld | Typ | Beschreibung | |---|---|---| | `stream_id` | `uint16_t` | Zuordnung zum Stream | | `offset` | `uint32_t` | Byte-Offset im Gesamtstrom | | `data_len` | `uint16_t` | Länge von `data` | | `data` | `byte[data_len]` | Nutzdaten | `total_len` ist `uint32_t`, damit sind bis 4 GiB abbildbar. Für euer Gerät ist das mehr als ausreichend und trotzdem standardisiert. ## 7) Offene Punkte für die Implementierung - Fixe maximale `chunk_size` (z. B. 256/512/1024 Bytes) - ACK/NACK auf Chunk-Ebene nötig oder „best effort + Retry auf Kommando-Ebene“? - Timeout-/Retry-Policy pro Kommando - Welche Kommandos zuerst in `protocol.c` migriert werden ## 8) Kurzfazit für den nächsten Schritt - Ja, getrennte Kommandos sind sinnvoll. - Ja, Endianness muss explizit definiert sein (Little Endian). - Ja, `ACK` und `DATA` sollten als unterschiedliche Frame-Typen geführt werden. - Für große Dateien: `uint32_t total_len` + Chunk-Streaming statt Einmal-Payload.