# Buzzer Protocol (Wire Specification) Stand: 2026-03-18 Quelle: aktueller Implementierungsstand aus Firmware (`buzz_proto`, `fs_mgmt`, `ble_mgmt`) und Web-Client (`transport`, `parser`). ## 1. Ziel und Scope Das Buzzer Protocol ist ein binäres Frame-Protokoll für Host <-> Device Kommunikation. Abgedeckte Funktionen: - Capability-Abfrage (`PROTO_INFO`) - Dateisystem-Info (`FS_INFO`) - Verzeichnisliste als Stream (`LS`) - Datei-/Tag-Download (`FILE_GET`, `TAGS_GET`) - Datei-/Tag-Upload (`FILE_PUT`, `TAGS_PUT`) - Datei löschen / umbenennen (`RM_FILE`, `RENAME_FILE`) Nicht produktiv implementiert: - `DEVICE_INFO` (`0x02`) - Firmware-Update (`FW_*`, `FW_UPDATE`) ## 2. Transport und Grundregeln - Alle Integer-Felder sind Little Endian. - Jedes Frame hat einen 3-Byte Header. - `payload_length` enthält nur die Payload-Länge (ohne Header). - Aktiver Produktiv-Transport ist BLE GATT (RX Write Without Response, TX Notify). - Es darf genau ein Stream gleichzeitig aktiv sein (`LS`, `FILE_GET`, `FILE_PUT`, `TAGS_*`). BLE Service UUIDs: - Service: `e517d988-bab5-4574-8479-97c6cb115ca0` - RX: `e517d988-bab5-4574-8479-97c6cb115ca1` - TX: `e517d988-bab5-4574-8479-97c6cb115ca2` ## 3. Frame-Format ### 3.1 Header ```c uint8_t frame_type; uint16_t payload_length; // LE ``` ### 3.2 Paketstruktur ```mermaid --- title: "Basic Packet Structure" --- packet +8: "Frame type" +16: "Payload length (LE)" +40: "Payload (variable length)" ``` ### 3.3 Maximalgröße Firmware-Buffer ist slab-basiert (`CONFIG_BUZZ_PROTO_SLAB_SIZE`). Der effektive Chunk für Transfers wird zusätzlich durch den Transport limitiert. Bei Bluetooth sind das zum Beispiel 3 Bytes: `PROTO_INFO.max_chunk_size` wird dynamisch berechnet als: `min(slab_size - 3, transport_max_payload - 3)` Das Protokoll ist so ausgelegt, dass es mit mindestens 100 Bytes auskommen sollte. ## 4. Frame-Typen | Wert | Name | Richtung | Bedeutung | |---|---|---|---| | `0x00` | `REQUEST` | Host -> Device | API-Aufruf per `data_type` | | `0x10` | `RESPONSE` | Device -> Host | Antwort auf `REQUEST` | | `0x11` | `ACK` | Host <-> Device | Credit-basierte Flusskontrolle | | `0x12` | `ERROR` | Device -> Host | Fehler mit errno-Code | | `0x13` | `SUCCESS` | Device -> Host | Erfolgreicher Abschluss | | `0x20` | `FILE_START` | Device -> Host | Start Download-Stream | | `0x21` | `FILE_CHUNK` | Host <-> Device | Datenblock (Upload/Download) | | `0x22` | `FILE_END` | Host <-> Device | Streamende mit CRC32 | | `0x30` | `FW_START` | reserviert | nicht aktiv | | `0x31` | `FW_CHUNK` | reserviert | aktuell `ENOSYS` | | `0x32` | `FW_END` | reserviert | nicht aktiv | | `0x40` | `LS_START` | Device -> Host | Start Verzeichnis-Stream | | `0x41` | `LS_ENTRY` | Device -> Host | Verzeichniseintrag | | `0x42` | `LS_END` | Device -> Host | Ende Verzeichnis-Stream | ## 5. Data-Typen (`REQUEST`) | Wert | Name | Status | Beschreibung | |---|---|---|---| | `0x01` | `PROTO_INFO` | aktiv | Protokollversion + max Chunkgröße | | `0x02` | `DEVICE_INFO` | reserviert | aktuell nicht bedient | | `0x03` | `FS_INFO` | aktiv | Dateisystem- und Pfadinfos | | `0x20` | `FILE_GET` | aktiv | Datei vom Device streamen | | `0x21` | `FILE_PUT` | aktiv | Datei zum Device hochladen | | `0x22` | `TAGS_GET` | aktiv | nur Tag-Bereich streamen | | `0x23` | `TAGS_PUT` | aktiv | nur Tag-Bereich schreiben | | `0x24` | `RM_FILE` | aktiv | Datei löschen | | `0x25` | `RENAME_FILE` | aktiv | Datei umbenennen | | `0x30` | `FW_UPDATE` | reserviert | aktuell nicht bedient | | `0x40` | `LS` | aktiv | Verzeichnisliste starten | ## 6. Request/Response-Formate ## 6.1 Generischer Request ```c uint8_t data_type; // optional daten_typspezifische Parameter ``` Wire: ```text [0x00][payload_len LE][data_type][optional...] ``` ```mermaid --- title: "Generic Requst Structure" --- packet +8: "Frame type REQUEST: 0x00" +16: "Payload length (LE)" +8: "Data type" +32: "Optional payload (variable length)" ``` ## 6.2 `PROTO_INFO` (`0x01`) Request: keine Zusatzdaten. Response-Payload: ```c uint8_t data_type; // 0x01 uint16_t version; // LE uint16_t max_chunk_size; // LE ``` ```mermaid --- title: "PROTO_INFO response structure" --- packet +8: "Frame type RESPONSE: 0x10" +16: "Payload length (LE): 5" +8: "Data type PROTO_INFO: 0x01" +16: "Protocol Version (LE)" +16: "Max Chunk Size (LE)" ``` ## 6.3 `FS_INFO` (`0x03`) Request: keine Zusatzdaten. Response-Payload: ```c uint8_t data_type; // 0x03 uint32_t total_size; // LE uint32_t free_size; // LE uint8_t max_path_length; uint8_t sys_path_length; uint8_t audio_path_length; uint8_t data[]; // sys_path + audio_path (beide nicht nullterminiert) ``` Im `data` folgen sich System- und Audiopfad ohne Abstand, und ohne 0-Terminierung (`\0`). Beispiel für Systempfad `/lfs/sys`und Audiopfad `/lfs/a`: `/lfs/sys/lfs/a` `sys_path_len` ist in diesem Beispiel 8 und `audio_path_len` ist 6. ```mermaid --- title: "FS_INFO response structure" --- packet +8: "Frame type RESPONSE: 0x10" +16: "Payload length (LE): variable, 12 + sys path length + audio path length" +8: "Data type FS_INFO: 0x03" +32: "Total size (LE)" +32: "Free size (LE)" +8: "Max path length" +8: "Sys path length" +8: "Audio path length" +40: "Sys path + Audio Path (variable)" ``` Beispielpaket mit **8 MiB Flash**, wovon **7 MiB frei** sind und den Pfaden `/lfs/sys` und `/lfs/a`. Die **maximale Pfadlänge** sind **32** Zeichen: ```mermaid --- title: "FS_INFO response example" --- packet +8: "Frame type RESPONSE: 0x10" +16: "Payload length (LE): 26 (0x1A 0x00)" +8: "Data type FS_INFO: 0x03" +32: "Total size (LE): 8388608 (0x00 0x00 0x80 0x00)" +32: "Free size (LE): 7340032 (0x00 0x00 0x70 0x00)" +8: "Max path length: 32" +8: "Sys path length: 8" +8: "Audio path length: 6" +112: "data: '/lfs/sys/lfs/a'" ``` Das Beispiel schaut in HEX so aus: ```text 0x10 0x1A 0x00 0x03 0x00 0x00 0x80 0x00 0x00 0x00 0x70 0x00 0x20 0x08 0x06 0x2F 0x6C 0x66 0x73 0x2F 0x73 0x79 0x73 0x2F 0x6C 0x66 0x73 0x2F 0x61 ``` ## 6.4 `LS` (`0x40`) Request-Payload: ```c uint8_t data_type; // 0x40 char path[]; // ohne Nullterminierung ``` ## 6.5 `FILE_GET` (`0x20`) und `TAGS_GET` (`0x22`) Request-Payload: ```c uint8_t data_type; // 0x20 oder 0x22 char path[]; // ohne Nullterminierung ``` Antwort ist ein Stream aus `FILE_START` -> `FILE_CHUNK`* -> `FILE_END`. ## 6.6 `FILE_PUT` (`0x21`) und `TAGS_PUT` (`0x23`) Request-Payload: ```c uint8_t data_type; // 0x21 oder 0x23 uint32_t total_size; // LE char path[]; // ohne Nullterminierung ``` Danach sendet der Host: - `FILE_CHUNK` Frames - abschließend `FILE_END` mit CRC32 ## 6.7 `RM_FILE` (`0x24`) Request-Payload: ```c uint8_t data_type; // 0x24 uint8_t path_length; char path[]; // ohne Nullterminierung ``` ## 6.8 `RENAME_FILE` (`0x25`) Request-Payload: ```c uint8_t data_type; // 0x25 uint8_t old_path_length; uint8_t new_path_length; char paths[]; // old_path + new_path (jeweils ohne Nullterminierung) ``` ## 7. ACK / ERROR / SUCCESS ## 7.1 ACK (`0x11`) Payload: ```c uint16_t credits; // LE ``` Wichtig: Es gibt zwei Semantiken je nach Richtung. - Download (`LS`, `FILE_GET`, `TAGS_GET`): Host -> Device, Credits werden im Device als absoluter neuer Stand gesetzt. - Upload (`FILE_PUT`, `TAGS_PUT`): Device -> Host, Credits sind zusätzlich gewährte Tokens (Host addiert sie). ## 7.2 ERROR (`0x12`) Payload: ```c uint16_t error_code; // positiver errno-Wert, LE ``` Häufige Codes: | Code | Name | Bedeutung | |---|---|---| | `1` | `EPERM` | fehlende Rechte | | `2` | `ENOENT` | Datei/Ordner nicht gefunden | | `5` | `EIO` | Flash-I/O-Fehler | | `16` | `EBUSY` | Stream/Ressource belegt | | `22` | `EINVAL` | ungültige Nutzdaten | | `36` | `ENAMETOOLONG` | Pfad zu lang | | `71` | `EPROTO` | unzulässiger Frame-Typ | | `74` | `EBADMSG` | ungültiger Stream-Frame/CRC Fehler | | `88` | `ENOSYS` | nicht implementiert | | `90` | `EMSGSIZE` | max Payload ungültig | | `116` | `ETIMEDOUT` | Credit-/Stream-Timeout | | `134` | `ENOTSUP` | nicht unterstützt | ## 7.3 SUCCESS (`0x13`) Payload: ```c uint8_t data_type; // erfolgreich abgeschlossener Befehl ``` Wird derzeit u.a. für `FILE_PUT`, `TAGS_PUT`, `RM_FILE`, `RENAME_FILE` genutzt. ## 8. Stream-Sequenzen (Mermaid) ## 8.1 Verzeichnisliste (`LS`) ```mermaid sequenceDiagram participant Host participant Device Host->>Device: REQUEST(LS, path) Device-->>Host: LS_START Host->>Device: ACK(credits=64) loop solange credits > 0 Device-->>Host: LS_ENTRY end alt Verzeichnis vollständig Device-->>Host: LS_END(total_entries) else keine Credits/Timeout Device-->>Host: ERROR(ETIMEDOUT) end ``` Hinweis zum aktuellen Web-Client (März 2026): Für `LS` wird initial `ACK(64)` gesendet, ein dynamisches Nachfüllen ist noch nicht implementiert. Große Verzeichnisse können deshalb in `ETIMEDOUT` laufen. ## 8.2 Datei-/Tag-Download (`FILE_GET`, `TAGS_GET`) ```mermaid sequenceDiagram participant Host participant Device Host->>Device: REQUEST(FILE_GET|TAGS_GET, path) Device-->>Host: FILE_START(total_size) Host->>Device: ACK(credits=128) loop chunks Device-->>Host: FILE_CHUNK(data) Note over Host: Credits dekrementieren alt Credits <= 64 Host->>Device: ACK(credits=128) end end Device-->>Host: FILE_END(crc32) Note over Host: CRC prüfen ``` ## 8.3 Datei-/Tag-Upload (`FILE_PUT`, `TAGS_PUT`) ```mermaid sequenceDiagram participant Host participant Device Host->>Device: REQUEST(FILE_PUT|TAGS_PUT, total_size, path) Device-->>Host: ACK(initial credits) loop solange Host-Credits > 0 Host->>Device: FILE_CHUNK(data) Note over Device: schreibt Flash, zählt unacked_chunks alt ACK_WATERMARK erreicht Device-->>Host: ACK(additional credits) end end Host->>Device: FILE_END(crc32) alt CRC korrekt Device-->>Host: SUCCESS(FILE_PUT|TAGS_PUT) else CRC/Write Fehler Device-->>Host: ERROR(EBADMSG/EIO/...) end ``` ## 9. Payload-Frames im Detail ## 9.1 `LS_ENTRY` (`0x41`) ```c uint8_t type; // 0x00 file, 0x01 dir uint32_t size; // LE uint8_t name_length; char name[]; // ohne Nullterminierung ``` ## 9.2 `LS_END` (`0x42`) ```c uint32_t total_entries; // LE ``` ## 9.3 `FILE_START` (`0x20`) ```c uint32_t total_size; // LE ``` `FILE_GET`: komplette Dateigröße. `TAGS_GET`: nur Tag-Teil der Datei. ## 9.4 `FILE_CHUNK` (`0x21`) Payload sind rohe Nutzdatenbytes. ## 9.5 `FILE_END` (`0x22`) ```c uint32_t crc32; // LE, IEEE CRC32 ``` ## 10. Beispiel-Frames (Hex) ## 10.1 `PROTO_INFO` Request/Response Request: ```text 00 01 00 01 ``` Beispiel-Response: ```text 10 05 00 01 01 00 FD 00 ``` Interpretation: - `10`: RESPONSE - `05 00`: Payload 5 Byte - `01`: PROTO_INFO - `01 00`: Version 1 - `FD 00`: max_chunk_size = 253 ## 10.2 `LS` Request für `/a` ```text 00 03 00 40 2F 61 ``` Interpretation: - `00`: REQUEST - `03 00`: Payload 3 Byte - `40`: data_type LS - `2F 61`: `/a` ## 11. Implementierungsnotizen - Unknown `REQUEST.data_type` wird aktuell mit `ERROR(EINVAL)` beantwortet. - Unbekannte/unerwartete `frame_type` im aktiven Protokollthread führen zu `ERROR(EPROTO)`. - Stream-Timeout in Firmware erzeugt aktiv `ERROR(ETIMEDOUT)`. - Upload-Timeout im FS-Thread (2 s Inaktivität) bricht intern ab; die Host-Seite sollte eigene Watchdogs haben.