11 KiB
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_lengthenthä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
uint8_t frame_type;
uint16_t payload_length; // LE
3.2 Paketstruktur
---
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
uint8_t data_type;
// optional daten_typspezifische Parameter
Wire:
[0x00][payload_len LE][data_type][optional...]
---
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:
uint8_t data_type; // 0x01
uint16_t version; // LE
uint16_t max_chunk_size; // LE
---
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:
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/sysund Audiopfad /lfs/a:
/lfs/sys/lfs/a
sys_path_len ist in diesem Beispiel 8 und audio_path_len ist 6.
---
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:
---
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:
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:
uint8_t data_type; // 0x40
char path[]; // ohne Nullterminierung
6.5 FILE_GET (0x20) und TAGS_GET (0x22)
Request-Payload:
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:
uint8_t data_type; // 0x21 oder 0x23
uint32_t total_size; // LE
char path[]; // ohne Nullterminierung
Danach sendet der Host:
FILE_CHUNKFrames- abschließend
FILE_ENDmit CRC32
6.7 RM_FILE (0x24)
Request-Payload:
uint8_t data_type; // 0x24
uint8_t path_length;
char path[]; // ohne Nullterminierung
6.8 RENAME_FILE (0x25)
Request-Payload:
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:
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:
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:
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)
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)
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)
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)
uint8_t type; // 0x00 file, 0x01 dir
uint32_t size; // LE
uint8_t name_length;
char name[]; // ohne Nullterminierung
9.2 LS_END (0x42)
uint32_t total_entries; // LE
9.3 FILE_START (0x20)
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)
uint32_t crc32; // LE, IEEE CRC32
10. Beispiel-Frames (Hex)
10.1 PROTO_INFO Request/Response
Request:
00 01 00 01
Beispiel-Response:
10 05 00 01 01 00 FD 00
Interpretation:
10: RESPONSE05 00: Payload 5 Byte01: PROTO_INFO01 00: Version 1FD 00: max_chunk_size = 253
10.2 LS Request für /a
00 03 00 40 2F 61
Interpretation:
00: REQUEST03 00: Payload 3 Byte40: data_type LS2F 61:/a
11. Implementierungsnotizen
- Unknown
REQUEST.data_typewird aktuell mitERROR(EINVAL)beantwortet. - Unbekannte/unerwartete
frame_typeim aktiven Protokollthread führen zuERROR(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.