zwischenstand

This commit is contained in:
2026-03-19 07:39:25 +01:00
parent ff63dda086
commit b863b04505
15 changed files with 647 additions and 337 deletions

View File

@@ -1,365 +1,474 @@
# Buzzer Protocol (Wire Specification)
## 1. Zweck und Geltungsbereich
Das Buzzer Protocol definiert ein transportunabhaengiges, binaeres Frame-Format fuer die Kommunikation zwischen Host und Device.
Unterstuetzte Transporte sind aktuell BLE und USB CDC ACM/UART.
Stand: 2026-03-18
Quelle: aktueller Implementierungsstand aus Firmware (`buzz_proto`, `fs_mgmt`, `ble_mgmt`) und Web-Client (`transport`, `parser`).
Das Protokoll spezifiziert:
- Frame-Struktur (Header + Payload)
- Frametypen
- Datentypen fuer Request/Response
- Semantik fuer Stream-Transfers (Verzeichnisliste, Datei, Firmware)
## 1. Ziel und Scope
## 2. Transport- und Codierungsregeln
- Alle ganzzahligen Felder werden in Little Endian uebertragen.
- Die im Header angegebene `payload_length` bezieht sich ausschliesslich auf die Nutzdaten ohne Header.
- Bei UART kann optional eine Synchronisationssequenz `BUZZ` (`0x42 0x55 0x5A 0x5A`) vor einem Frame verwendet werden, um Framing nach Leitungsstoerungen zu resynchronisieren.
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 (3 Byte)
### 3.1 Header
```c
uint8_t frame_type
uint16_t payload_length // Little Endian
uint8_t frame_type;
uint16_t payload_length; // LE
```
### 3.2 Gesamtframe
```
+------------------+-------------------------+
| Header (3 Byte) | Payload (optional) |
| frame_type (1B) | payload_length Byte |
| payload_len (2B) | |
+------------------+-------------------------+
### 3.2 Paketstruktur
```mermaid
---
title: "Basic Packet Structure"
---
packet
+8: "Frame type"
+16: "Payload length (LE)"
+40: "Payload (variable length)"
```
## 4. Frametypen
### 3.3 Maximalgröße
### 4.1 Steuer- und Anfrageframes
| Wert | Name | Richtung | Beschreibung |
|--------|------------|----------------|---------------------------------------|
| `0x00` | `REQUEST` | Host → Device | Abfrage eines Datentyps |
| `0x10` | `RESPONSE` | Device → Host | Antwort auf `REQUEST` |
| `0x11` | `ACK` | Host ↔ Device | Flusskontrolle bei Stream-Transfers |
| `0x12` | `ERROR` | Device → Host | Fehlerantwort mit Fehlercode |
| `0x13` | `SUCCESS` | Device → Host | Bestaetigung einer Operation |
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:
### 4.2 Datei-Transfer
| Wert | Name | Richtung | Beschreibung |
|--------|--------------|----------------|---------------------------------------------|
| `0x20` | `FILE_START` | Host ↔ Device | Beginn eines Dateitransfers |
| `0x21` | `FILE_CHUNK` | Host ↔ Device | Ein Datenblock des Dateitransfers |
| `0x22` | `FILE_END` | Host ↔ Device | Abschluss des Dateitransfers inkl. CRC32 |
`PROTO_INFO.max_chunk_size` wird dynamisch berechnet als:
### 4.3 Firmware-Transfer (reserviert, noch nicht implementiert)
| Wert | Name |
|--------|------------|
| `0x30` | `FW_START` |
| `0x31` | `FW_CHUNK` |
| `0x32` | `FW_END` |
`min(slab_size - 3, transport_max_payload - 3)`
### 4.4 Verzeichnisliste
| Wert | Name | Richtung | Beschreibung |
|--------|------------|----------------|---------------------------------|
| `0x40` | `LS_START` | Device → Host | Beginn des Listing-Streams |
| `0x41` | `LS_ENTRY` | Device → Host | Ein Verzeichniseintrag |
| `0x42` | `LS_END` | Device → Host | Ende des Listing-Streams |
Das Protokoll ist so ausgelegt, dass es mit mindestens 100 Bytes auskommen sollte.
## 5. Request/Response-Schema
## 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
### 5.1 Request (`frame_type = 0x00`)
Payload-Mindestformat:
```c
uint8_t data_type // Nutzt enum buzz_data_type
// optional: datentypspezifische Parameter
uint8_t data_type;
// optional daten_typspezifische Parameter
```
Wire-Format:
```
[0x00][payload_length LE][data_type][optional parameters]
Wire:
```text
[0x00][payload_len LE][data_type][optional...]
```
### 5.2 Response (`frame_type = 0x10`)
Payload-Mindestformat:
```c
uint8_t data_type // Echo des angefragten data_type
// danach: datentypspezifische Response-Daten
```mermaid
---
title: "Generic Requst Structure"
---
packet
+8: "Frame type REQUEST: 0x00"
+16: "Payload length (LE)"
+8: "Data type"
+32: "Optional payload (variable length)"
```
Wire-Format:
```
[0x10][payload_length LE][data_type][response payload]
```
## 6.2 `PROTO_INFO` (`0x01`)
## 6. Datentypen (Request/Response)
Definierte `data_type`-Werte:
| Wert | Name | Beschreibung |
|--------|---------------|--------------------------------------|
| `0x01` | `PROTO_INFO` | Protokollversion und Chunk-Groesse |
| `0x02` | `DEVICE_INFO` | Geraeteinformationen (TBD) |
| `0x03` | `FS_INFO` | Dateisystem-Statistik und Pfadnamen |
| `0x20` | `FILE_GET` | Datei vom Device anfordern |
| `0x21` | `FILE_PUT` | Datei auf das Device hochladen |
| `0x22` | `TAGS_GET` | Metadaten-Tags anfordern |
| `0x23` | `TAGS_PUT` | Metadaten-Tags schreiben |
| `0x24` | `RM_FILE` | Datei loeschen |
| `0x25` | `RENAME_FILE` | Datei umbenennen |
| `0x30` | `FW_UPDATE` | Firmware-Update starten |
| `0x40` | `LS` | Verzeichnisliste starten |
### 6.1 `PROTO_INFO` (`0x01`)
Request-Parameter: keine
Request: keine Zusatzdaten.
Response-Payload:
```c
uint8_t data_type; // 0x01
uint16_t version; // Protokollversion (LE)
uint16_t max_chunk_size; // max. Nutzdaten pro Frame ohne Header (LE)
uint16_t version; // LE
uint16_t max_chunk_size; // LE
```
Hinweis: `max_chunk_size` ergibt sich aus der internen Slab-Konfiguration (`CONFIG_BUZZ_PROTO_SLAB_SIZE - 3`).
```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.2 `DEVICE_INFO` (`0x02`)
TBD
## 6.3 `FS_INFO` (`0x03`)
### 6.3 `FS_INFO` (`0x03`)
Request-Parameter: keine
Request: keine Zusatzdaten.
Response-Payload:
```c
uint8_t data_type; // 0x03
uint32_t total_size; // Gesamtgroesse Flash in Bytes (LE)
uint32_t free_size; // Freier Speicher in Bytes (LE)
uint8_t max_path_length; // Maximal erlaubte Pfadlaenge
uint8_t sys_path_length; // Laenge des System-Pfades (ohne 0-Terminator)
uint8_t audio_path_length; // Laenge des Audio-Pfades (ohne 0-Terminator)
uint8_t data[]; // sys_path gefolgt von audio_path, nicht nullterminiert
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)
```
### 6.4 `LS` (`0x40`) — Verzeichnisliste anfordern
Startet einen LS-Stream fuer den angegebenen Pfad.
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[]; // Pfad ohne 0-Terminator, Laenge ergibt sich aus payload_length - 1
uint8_t data_type; // 0x40
char path[]; // ohne Nullterminierung
```
Wire-Format (Beispiel fuer Pfad `/a`):
```
[0x00][0x03 0x00][0x40][0x2F 0x61]
```
## 6.5 `FILE_GET` (`0x20`) und `TAGS_GET` (`0x22`)
Das Device antwortet mit dem LS-Stream (siehe Abschnitt 8).
### 6.5 `RM_FILE` (`0x24`) — Datei loeschen
Request-Payload:
```c
uint8_t data_type; // 0x24
uint8_t path_length; // Laenge des Pfads
char path[]; // Pfad ohne 0-Terminator
uint8_t data_type; // 0x20 oder 0x22
char path[]; // ohne Nullterminierung
```
### 6.6 `RENAME_FILE` (`0x25`) — Datei umbenennen
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; // 0x25
uint8_t old_path_length; // Laenge des alten Pfads
uint8_t new_path_length; // Laenge des neuen Pfads
char paths[]; // Alter Pfad, direkt gefolgt vom neuen Pfad (beide ohne 0-Terminator)
uint8_t data_type; // 0x21 oder 0x23
uint32_t total_size; // LE
char path[]; // ohne Nullterminierung
```
### 6.7 `FILE_PUT` (`0x21`) / `TAGS_PUT` (`0x23`) — Upload initiieren
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; // 0x21 (Datei) oder 0x23 (Tags)
uint32_t total_size; // Dateigroesse in Bytes (LE)
char path[]; // Zielpfad ohne 0-Terminator
uint8_t data_type; // 0x24
uint8_t path_length;
char path[]; // ohne Nullterminierung
```
## 7. ACK-, ERROR- und SUCCESS-Frames
## 6.8 `RENAME_FILE` (`0x25`)
### 7.1 ACK (`frame_type = 0x11`) — Host ↔ Device
Wird waehrend eines laufenden Stream-Transfers gesendet, um der sendenden Seite Credits (Sendeerlaubnisse) zu erteilen.
Bei einem Download (`LS` oder `FILE_GET`) sendet der Host das ACK. Bei einem Upload (`FILE_PUT` oder `TAGS_PUT`) sendet das Device das ACK.
Request-Payload:
Format:
```c
// Header:
uint8_t frame_type; // 0x11
uint16_t payload_length; // 0x0002
// Payload:
uint16_t credits; // Anzahl der Entries, die das Device senden darf (LE)
uint8_t data_type; // 0x25
uint8_t old_path_length;
uint8_t new_path_length;
char paths[]; // old_path + new_path (jeweils ohne Nullterminierung)
```
Wire-Format (Beispiel: 64 Credits):
```
[0x11][0x02 0x00][0x40 0x00]
```
## 7. ACK / ERROR / SUCCESS
Semantik:
- Der Host sendet nach Empfang von `LS_START` initial Credits (typisch 64).
- Das Device dekrementiert seinen internen Credit-Zaehler mit jeder gesendeten `LS_ENTRY`.
- Bei 0 Credits wartet das Device auf ein weiteres ACK (Timeout: 5 × 500 ms, danach Abbruch).
- Der Host soll bei Bedarf weitere Credits nachsenden, bevor die bisherigen aufgebraucht sind.
### 7.2 ERROR (`frame_type = 0x12`) — Device → Host
Format:
```c
// Header:
uint8_t frame_type; // 0x12
uint16_t payload_length; // 0x0002
// Payload:
uint16_t error_code; // Positiver Zephyr-errno-Wert (LE)
```
Wire-Format (Beispiel: ENOENT = 2):
```
[0x12][0x02 0x00][0x02 0x00]
```
ERROR kann jederzeit als Antwort auf einen REQUEST oder waehrend eines Streams gesendet werden.
Ein ERROR-Frame waehrend eines aktiven LS-Streams beendet diesen implizit.
Fehlercode-Tabelle (Zephyr errno, positiver Wert):
| Code | Zephyr-Name | Bedeutung |
|------|----------------|---------------------------------------------|
| 1 | `EPERM` | Fehlende Berechtigung |
| 2 | `ENOENT` | Datei oder Verzeichnis nicht gefunden |
| 5 | `EIO` | Ein-/Ausgabefehler auf dem Flash |
| 12 | `ENOMEM` | Nicht genuegend Speicher frei |
| 16 | `EBUSY` | Geraet oder Ressource belegt |
| 22 | `EINVAL` | Ungültiges Argument oder Parameter |
| 24 | `EMFILE` | Zu viele offene Dateien |
| 28 | `ENOSPC` | Kein freier Speicherplatz mehr |
| 36 | `ENAMETOOLONG` | Dateiname oder Pfad zu lang |
| 88 | `ENOSYS` | Funktion nicht implementiert |
| 134 | `ENOTSUP` | Operation nicht unterstuetzt |
### 7.3 SUCCESS (`frame_type = 0x13`) — Device → Host
Bestaetigt den erfolgreichen Abschluss einer Operation, z. B. nach Beendigung eines Uploads oder einer Dateioperation (Loeschen, Umbenennen).
Format:
```c
// Header:
uint8_t frame_type; // 0x13
uint16_t payload_length; // 0x0001
// Payload:
uint8_t data_type; // Der Befehl, der erfolgreich war (z.B. 0x21 fuer FILE_PUT)
```
Wire-Format (Beispiel: Erfolg bei RM_FILE):
```
[0x13][0x01 0x00][0x24]
```
## 8. LS-Stream (Verzeichnisliste)
Der LS-Stream wird durch einen `REQUEST` mit `data_type = 0x40` ausgeloest und laeuft wie folgt ab:
```
Host Device
| |
|-- REQUEST (data_type=LS, path) -->|
| | (oeffnet Verzeichnis)
|<--------- LS_START (leer) --------|
| |
|------ ACK (credits=64) ---------->|
| |
|<-- LS_ENTRY (entry 1) ------------|
|<-- LS_ENTRY (entry 2) ------------|
| ... |
|<-- LS_ENTRY (entry 64) -----------| (credits = 0, Device wartet)
| |
|------ ACK (credits=64) ---------->|
| |
|<-- LS_ENTRY (entry 65) -----------|
| ... |
|<--------- LS_END -----------------|
```
### 8.1 `LS_START` (`0x40`) — Device → Host
Signalisiert den Beginn des Streams. Keine Payload.
```
[0x40][0x00 0x00]
```
### 8.2 `LS_ENTRY` (`0x41`) — Device → Host
Ein Eintrag pro Verzeichniselement.
## 7.1 ACK (`0x11`)
Payload:
```c
uint8_t type; // 0x00 = Datei, 0x01 = Verzeichnis (buzz_fs_entry_type)
uint32_t size; // Dateigroesse in Bytes (LE); bei Verzeichnissen 0
uint8_t name_length; // Laenge des Namens (ohne 0-Terminator)
char name[]; // Datei-/Verzeichnisname, nicht nullterminiert
uint16_t credits; // LE
```
`type`-Werte:
| Wert | Bedeutung |
|--------|---------------|
| `0x00` | Datei (FILE) |
| `0x01` | Verzeichnis (DIR) |
Wichtig: Es gibt zwei Semantiken je nach Richtung.
### 8.3 `LS_END` (`0x42`) — Device → Host
Signalisiert das Ende des Streams.
- 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
uint32_t total_entries; // Gesamtzahl gesendeter Eintraege (LE)
uint16_t error_code; // positiver errno-Wert, LE
```
Der Host kann `total_entries` mit der empfangenen Anzahl von `LS_ENTRY`-Frames vergleichen, um Vollstaendigkeit zu pruefen.
Häufige Codes:
### 8.4 Fehler- und Timeoutbehandlung
- Tritt ein Fehler beim Lesen auf, sendet das Device einen `ERROR`-Frame und beendet den Stream.
- Empfaengt das Device 5 Mal in Folge keine Credits innerhalb von je 500 ms (2,5 s gesamt), bricht es den Stream intern ab (kein ERROR-Frame, Stream wird still verworfen).
- Der Host sollte einen eigenen Watchdog implementieren; empfohlener Timeout: 3 s ohne empfangenen Frame.
| 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 |
## 9. Beispiele
## 7.3 SUCCESS (`0x13`)
### 9.1 PROTO_INFO abfragen
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
```
- `00`: `REQUEST`
- `01 00`: `payload_length = 1`
- `01`: `data_type = PROTO_INFO`
Response (Beispielwerte):
```
Beispiel-Response:
```text
10 05 00 01 01 00 FD 00
```
- `10`: `RESPONSE`
- `05 00`: `payload_length = 5`
- `01`: `data_type = PROTO_INFO`
- `01 00`: `version = 1`
- `FD 00`: `max_chunk_size = 253`
### 9.2 Verzeichnisliste `/a` anfordern
Interpretation:
- `10`: RESPONSE
- `05 00`: Payload 5 Byte
- `01`: PROTO_INFO
- `01 00`: Version 1
- `FD 00`: max_chunk_size = 253
Request:
```
## 10.2 `LS` Request für `/a`
```text
00 03 00 40 2F 61
```
- `00`: `REQUEST`
- `03 00`: `payload_length = 3`
- `40`: `data_type = LS`
- `2F 61`: Pfad `/a`
Antwort (Sequenz):
```
40 00 00 // LS_START, keine Payload
// Host sendet ACK mit Credits
11 02 00 40 00 // ACK, 64 Credits
// Device sendet Eintraege
41 0A 00 00 00 00 00 00 06 73 6F 75 6E 64 31 // LS_ENTRY: FILE, size=0, name="sound1" (gekuerzt)
// ... weitere Eintraege ...
42 04 00 01 00 00 00 // LS_END, total_entries = 1
```
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.