Files
buzzer_2/protocol.md
2026-03-21 13:49:05 +01:00

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_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

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_CHUNK Frames
  • abschließend FILE_END mit 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: RESPONSE
  • 05 00: Payload 5 Byte
  • 01: PROTO_INFO
  • 01 00: Version 1
  • FD 00: max_chunk_size = 253

LS Request für /a

00 03 00 40 2F 61

Interpretation:

  • 00: REQUEST
  • 03 00: Payload 3 Byte
  • 40: data_type LS
  • 2F 61: /a

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.