12 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).
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)
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
Frame-Format
Header
uint8_t frame_type;
uint16_t payload_length; // LE
Paketstruktur
---
title: "Basic Packet Structure"
---
packet
+8: "Frame type"
+16: "Payload length (LE)"
+40: "Payload (variable length)"
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.
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 |
Data-Typen (REQUEST)
| Wert | Name | Status | Beschreibung |
|---|---|---|---|
0x01 |
PROTO_INFO |
aktiv | Protokollversion + max Chunkgröße |
0x02 |
DEVICE_INFO |
aktiv | Device-Infos (Board, Revision, SOC, ID) |
0x03 |
FS_INFO |
aktiv | Dateisystem- und Pfadinfos |
0x04 |
FW_INFO |
aktiv | Info über Firmware-Status und -Version sowie Kernelversion |
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 |
Request/Response-Formate
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)"
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)"
DEVICE_INFO (0x02)
Request: keine Zusatzdaten.
Respone-Payload:
uint8_t data_type; // 0x02
uint8_t[8] device_id;
uint8_t board_len;
uint8_t rev_len;
uint8_t soc_len;
uint8_t data[]; // board, rev und soc, ohne Nullterminierung
Hinweis: In der aktuellen implementierung werden die Strings auf eine maximale Länge von 32 Zeichen beschränkt. Dies sollte für alle Fälle genügen.
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 ohne Nullterminierung
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
FW_INFO (0x04)
Request: keine Zusatzdaten
Response:
uint8_t fw_status; /* 0x00: Confirmed, 0x01: Pending, 0x02: Testing, 0xFF: Unbekannt */
uint32_t slot1_size; /* (LE) Grösse des Firmware Update Slots */
uint8_t fw_version_len; /* Länge des Firmware-Versionsstring */
uint8_t kernel_version_len; /* Länge des Kernel-Versionsstrings */
uint8_t data[]; /* FW-Version und Kernelversion, ohne Nullterminierung */
Hinweis: in der Aktuellen implementierung werden die Versionen auf 32 Zeichen limitiert.
LS (0x40)
Request-Payload:
uint8_t data_type; // 0x40
char path[]; // ohne Nullterminierung
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.
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
RM_FILE (0x24)
Request-Payload:
uint8_t data_type; // 0x24
uint8_t path_length;
char path[]; // ohne Nullterminierung
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)
ACK / ERROR / SUCCESS
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).
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 |
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.
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.
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
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
Payload-Frames im Detail
LS_ENTRY (0x41)
uint8_t type; // 0x00 file, 0x01 dir
uint32_t size; // LE
uint8_t name_length;
char name[]; // ohne Nullterminierung
LS_END (0x42)
uint32_t total_entries; // LE
FILE_START (0x20)
uint32_t total_size; // LE
FILE_GET: komplette Dateigröße.
TAGS_GET: nur Tag-Teil der Datei.
FILE_CHUNK (0x21)
Payload sind rohe Nutzdatenbytes.
FILE_END (0x22)
uint32_t crc32; // LE, IEEE CRC32
Beispiel-Frames (Hex)
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
LS Request für /a
00 03 00 40 2F 61
Interpretation:
00: REQUEST03 00: Payload 3 Byte40: data_type LS2F 61:/a
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.