Protocollo scheda e cervello
Il formato dei frame binari tra la scheda e il computer, i tipi di messaggio e il collegamento.
La scheda parla con il Mac sulla seriale USB nativa (CDC-ACM, dispositivo "JZ Companion link"). Nessun JSON, nessun TLS: frame binari piccoli, mandati solo quando qualcosa cambia. L'altra metà del dispositivo composito è una tastiera HID ("JZ Companion keys"), usata dai widget per le scorciatoie senza passare dal companion.
Frame
A5 5A | len u16 LE | widget u8 | kind u8 | campi… | crc8lenconta i byte dawidgetall'ultimo campo (crc escluso). Massimo 2048.crc8: polinomio 0x07, valore iniziale 0, calcolato sugli stessi byte dilen.widget: indice nel registro della scheda.0xFFè il sistema (shell).kind: vedi sotto.
Campo: key u8 | type u8 | [len u16 LE] | valore
| type | valore |
|---|---|
| 0 | int32 little-endian (4 byte, niente len) |
| 1 | stringa UTF-8 (len byte, senza NUL) |
| 2 | byte grezzi (len byte) |
Chiavi scalari: 0x00..0x0E. Liste: JZ_K(i, campo) = ((i + 1) << 4) | campo, fino a 14 elementi
(i da 0 a 13) con 16 campi ciascuno. 0x0F (JZ_F_COUNT, int) dice quanti elementi ci sono.
Tipi di messaggio (kind)
| kind | direzione | significato |
|---|---|---|
1 DATA | host → device | stato di un widget. Le chiavi sono documentate in testa a ui/widgets/<id>.c |
2 EVENT | device → host | azione dell'utente. Chiave 0xFE int = azione; le altre sono argomenti. L'azione 0 significa "widget mostrato": il cervello risponde con un DATA fresco |
3 HELLO | device → host | al collegamento, a pezzi: un campo stringa per widget, key = indice, valore = id. Nel primo pezzo anche le chiavi riservate 0xF0–0xFE (vedi sotto: versione del protocollo, del firmware, id della scheda, numero dei widget). Il cervello risponde quando ha ricevuto tutti gli id |
4 TIME | host → device | chiave 0 int epoch UTC, chiave 1 int offset locale in minuti. Aggiorna l'RTC |
5 BLOB | entrambe | pezzo di un blob: 0 int slot, 1 int offset, 2 int totale, 3 bytes. Host → device: immagini (slot scelto dal widget). Device → host: audio del microfono, widget 0xFF, slot 0xA0, PCM 16 kHz mono 16 bit, 1024 byte per frame |
6 PING | entrambe | la scheda risponde con un PING |
7 THEME | host → device | chiave 0 int: 1 scuro, 0 chiaro |
8 FOCUS | host → device | chiave 0 str: id del widget da mostrare |
9 SCENE | device → host | chiave 0 int: scena premuta (0 riunione, 1 focus, 2 stream, 3 pausa, 4 fine) |
10 BUTTON | device → host | chiave 0 int: tasto (0 BOOT, 1 PWR), chiave 1 int: 1 se tenuto. BOOT tenuto = "muto ovunque": il cervello muta microfono di sistema, Discord e la riunione in modo idempotente |
11 STORE | entrambe | archivio dei widget: chiave 0 str nome (pomodoro.oggi), 1 bytes valore, 2 int mtime (epoch). Vince il più recente. Al collegamento la scheda manda le modifiche fatte da staccata, il cervello risponde con tutto l'archivio |
12 LAYOUT | host → device | disposizione dei widget: chiave 0 str la barra di BOOT (id separati da \n, vuota = i confermati), chiave 1 str i gruppi del mission control, uno per riga nome\tid,id (vuota = per categoria). La scheda la salva e la usa anche staccata |
13 CATALOG | device → host | dopo l'HELLO, a pezzi: chiave = indice del widget, str nome\tcategoria\tdescrizione. Serve alla pagina del cervello |
14 LOG | device → host | una riga di log della scheda: chiave 0 int livello (0 debug, 1 info, 2 warn, 3 error), 1 str fonte (ui, wifi, board…), 2 str messaggio, 3 int millisecondi dall'accensione, 4 str campi in più, uno per riga nome=valore. Il cervello la mette nei suoi log con la scheda come origine |
15 FLAGS | host → device | valori delle feature flag già calcolati per questa scheda. Lista: JZ_K(i,0) str nome, JZ_K(i,1) int valore (sì/no e numeri) oppure JZ_K(i,2) str valore (varianti). Chiave 0 int pezzo (da 0), 1 int pezzi totali: quando arriva l'ultimo pezzo i valori ricevuti sostituiscono tutti i precedenti. La scheda li salva e usa i predefiniti del codice per le flag mai ricevute |
16 HOST | host → device | subito dopo l'HELLO: chiave 0 int versione del protocollo, 1 str sistema (mac, windows, linux), 2 str nome del cervello ("MacBook Air di Jacopo"), 3 str versione del cervello. La scheda sceglie le scorciatoie giuste (⌘ o Ctrl) dal sistema |
17 PAIR | entrambe | abbinamento e stretta di mano del canale cifrato (widget 0xFF): chiave 0 int = passo, gli altri campi dipendono dal passo. In chiaro finché il canale non c'è, cifrato dopo. Passi, campi ed errori in abbinamento.md |
18 SEALED | entrambe | un frame del canale cifrato (widget 0xFF): chiave 0 bytes = contatore u64 LE (8) ‖ cifrato ‖ tag (16), ChaCha20-Poly1305. Dentro c'è il corpo di un frame qualunque (da widget all'ultimo campo), fino a 2048 byte: la busta può superare il massimo di 30 byte. Dopo l'abbinamento la scheda e il cervello si parlano solo così (abbinamento.md) |
19 COMMAND | host → device | un comando del cervello alla scheda, accettato solo dentro il canale cifrato (scheda abbinata): chiave 0 str nome. pin_reset: toglie il PIN di blocco (PIN dimenticato), azzera gli errori e sblocca. aggiorna: aggiornamento del firmware, chiavi 1 str URL, 2 int dimensione, 3 str SHA-256 (hex), 4 str firma Ed25519 (hex) del messaggio jz-ota-1 ‖ sha256 ‖ versione, 5 str versione |
Chiavi riservate nell'HELLO: 0xFE protocol, 0xFD firmware, 0xFC device, 0xFB total. Versione attuale del protocollo: 2.
Il companion dovrebbe mandare TIME e THEME subito dopo l'HELLO, poi un DATA per il widget che
riceve l'evento "mostrato" e, da lì in poi, un DATA ogni volta che lo stato cambia. La scheda tiene
in cache l'ultimo DATA di ogni widget: cambiare schermata non aspetta il companion.
Collegamento
- Apri la seriale a qualsiasi baud (è USB) e alza DTR: la scheda considera il companion collegato
quando DTR è alto, manda l'
HELLOe accende il punto verde nella barra in alto. - Un frame corrotto viene scartato in silenzio: il parser cerca il prossimo
A5 5A.
HID
Tastiera (report 1) e consumer control (report 2). I widget chiamano jz_hid_key(mods, tasto),
jz_hid_media(…) e jz_hid_type("testo\n"). Le combinazioni usate dai widget sono scritte nel loro
commento di testa. Il companion non c'entra: funzionano con la scheda attaccata a qualunque computer.
Esempio
DATA per pr_review (indice 3): 4 PR in attesa, una in lista.
A5 5A len 03 01
00 00 04 00 00 00 key 0, int 4
0F 00 01 00 00 00 JZ_F_COUNT, int 1
10 01 1A 00 "feat: filtri per cliente…" JZ_K(0,0), str
11 01 11 00 "webion/gestionale" JZ_K(0,1), str
12 01 05 00 "marco" JZ_K(0,2), str
13 01 03 00 "2 h" JZ_K(0,3), str
crcUn riferimento in C per codifica e decodifica è ui/src/proto.c; il simulatore stampa a console i
frame che la scheda manderebbe ([link] …) e le scorciatoie HID ([hid] …).