Documentazione

Protocollo JSON

Struttura del protocollo di comunicazione USB tra un programma e un dispositivo Expansum: comandi, risposte, errori ed esempi.

Stato del documento: descrive il protocollo realmente implementato nel firmware attuale. Le funzioni non ancora presenti (motore logico, variabili, timer, contatori) sono elencate in fondo alla pagina come funzioni escluse, non come promesse.

Scopo del protocollo

Il protocollo di Expansum definisce il modo in cui un programma comunica con un dispositivo Expansum attraverso una connessione USB seriale.

Il protocollo permette oggi di:

  • identificare il dispositivo collegato (nome, gruppo, modello, versione firmware);
  • leggere uno o più ingressi digitali in un’unica richiesta;
  • comandare una o più uscite digitali in un’unica richiesta;
  • assegnare un nome e un gruppo al dispositivo, in modo persistente;
  • passare a una modalità silenziosa pensata per software e script automatici.

Timer, contatori, variabili, logiche interne e comunicazione fra più dispositivi non fanno parte di questa versione del protocollo.

Livelli di comunicazione

Expansum espone due canali equivalenti sulla stessa connessione seriale:

  1. Comandi nativi, in italiano, pensati per l’uso diretto da terminale;
  2. Comando JSON, pensato per essere usato da un programma.

Entrambi i canali richiamano internamente le stesse funzioni: non esiste una differenza di comportamento fra i due, solo di sintassi.

Parametri della connessione seriale

Parametro Valore
Interfaccia USB seriale (adattatore CP210x)
Velocità 115200 baud
Formato 8 bit dati, nessuna parità, 1 bit di stop (8N1)
Prompt expansum:~$ (disattivabile in modalità silenziosa, vedi sotto)

Comandi nativi

I comandi nativi si digitano direttamente al prompt della shell, in italiano:

alto UD1
basso UD1
stato ID1 ID2 ID3
id
setnome quadro1
setgruppo linea1
silente

Accettano più pin separati da spazio dove ha senso (stato, alto, basso).

Comando JSON

Il comando shell e accetta come argomento un singolo oggetto JSON, inviato per intero sulla stessa riga:

e {"funzione":"STATO","ingresso":["ID1","ID2"]}

La chiave funzione è la chiave canonica del dispatcher. Accetta come alias f e fn, oltre a traduzioni in diverse lingue (vedi sezione Multilinguismo). Il valore di funzione è sempre uno fra: ALTO, BASSO, STATO, ID, SETNOME, SETGRUPPO, SILENTE.

Corrispondenza fra comando nativo e comando JSON

Comando nativo funzione JSON Campi aggiuntivi
alto <pin> [<pin> ...] ALTO uscita: nome pin o array di nomi
basso <pin> [<pin> ...] BASSO uscita: nome pin o array di nomi
stato <pin> [<pin> ...] STATO ingresso: array di nomi pin
id ID nessuno
setnome <nome> SETNOME valore: stringa
setgruppo <gruppo> SETGRUPPO valore: stringa
silente SILENTE nessuno

Struttura delle risposte

Ogni risposta è un singolo oggetto JSON, in una delle tre forme seguenti.

Successo senza dati:

{"ok":1}

Errore:

{"e":<codice>}

Il codice è un numero intero:

Codice Categoria Significato
100 Comando/parsing La riga inviata non è un JSON valido
101 Comando/parsing Manca il campo funzione (o l’alias f/fn), oppure non è una stringa
102 Comando/parsing Il valore di funzione non corrisponde a nessun alias noto
200 Oggetto Il nome del pin (ingresso o uscita) non è riconosciuto
201 Oggetto Il campo atteso non è né una stringa né un array

Le bande 3xx (motore logico) e 4xx (persistenza) sono riservate per funzioni non ancora implementate: al momento non esistono codici in queste fasce.

Dati diretti:

{"ID1":"1","ID2":"0"}

I valori booleani sono sempre rappresentati come stringhe "1" o "0", mai come numeri o come true/false.

Lettura degli ingressi

Il comando STATO legge uno o più ingressi in un’unica richiesta.

e {"funzione":"STATO","ingresso":["ID1","ID2","ID3","ID4","ID5","ID6","ID7","ID8"]}
{"ID1":"1","ID2":"0","ID3":"0","ID4":"1","ID5":"0","ID6":"0","ID7":"0","ID8":"0"}

La lettura richiede sempre un array esplicito dei pin desiderati: non esiste un comando che restituisca “tutti gli ingressi” senza elencarli.

Comando delle uscite

I comandi ALTO e BASSO impostano una o più uscite e rispondono con lo stato appena scritto:

e {"funzione":"ALTO","uscita":["UD1","UD2"]}
{"UD1":"1","UD2":"1"}

Non esiste un comando di rilettura delle uscite: l’hardware non può leggere indietro lo stato fisico di un’uscita. Un programma che vuole conoscere lo stato delle uscite deve tenerne traccia in memoria propria, aggiornandola a ogni risposta di ALTO/BASSO.

Identificazione del dispositivo

Il comando ID restituisce le informazioni del dispositivo, sempre nello stesso ordine di campi:

e {"funzione":"ID"}
{"gruppo":"linea1","nome":"quadro1","modello":"8ID8UD","fw":"1.0.0"}
Campo Modificabile Note
gruppo sì, tramite SETGRUPPO pensato per future reti multi-dispositivo
nome sì, tramite SETNOME
modello no determina il numero di ingressi/uscite lato software
fw no versione firmware

Nome e gruppo del dispositivo

e {"funzione":"SETNOME","valore":"quadro1"}
{"ok":1}

SETGRUPPO funziona allo stesso modo, con funzione diverso. Entrambi i valori vengono salvati in memoria persistente (NVS): sopravvivono allo spegnimento e a un successivo aggiornamento firmware. Solo una cancellazione esplicita della partizione di storage li azzera.

Modalità silenziosa

Il comando SILENTE disattiva l’eco dei caratteri digitati e rimuove il prompt dalla shell:

e {"funzione":"SILENTE"}

Dopo questo comando la shell non stampa più expansum:~$ prima di ogni riga. È pensata per essere il primo comando inviato da un programma o da uno script, in modo da poter interpretare l’output senza dover filtrare prompt ed eco residui.

Multilinguismo

Il canale JSON (non i comandi nativi) accetta alias in più lingue su tre livelli indipendenti:

  1. nomi dei pin;
  2. nomi dei campi (funzione/uscita/ingresso/valore);
  3. valori della funzione (ALTO/BASSO/STATO/…).

Le lingue attualmente supportate, oltre all’italiano (canonico): inglese, spagnolo, francese, portoghese, indonesiano, tedesco, swahili, turco, vietnamita, olandese, polacco, rumeno, ceco, ungherese, svedese, norvegese, danese, finlandese, croato.

Un limite tecnico della shell Zephyr esclude le lingue che non usano l’alfabeto latino (cinese, arabo, russo, hindi, giapponese, coreano e altre): i caratteri non stampabili in ASCII esteso vengono scartati dalla shell prima ancora che il comando raggiunga il parser JSON. Per lo stesso motivo, accenti e diacritici vengono sempre rimossi anche nelle lingue latine supportate.

I comandi nativi restano disponibili solo in italiano.

Prestazioni

Il tempo di andata e ritorno (round-trip) di un comando è fisso a circa 12 ms su adattatore CP210x, indipendentemente da baud rate e dimensione del payload. Per cicli di lettura/scrittura continui si consiglia un intervallo minimo di 50 ms per ciclo, per restare con margine sotto il limite fisico osservato.

Esempio da terminale

$ e {"funzione":"ID"}
{"gruppo":"linea1","nome":"quadro1","modello":"8ID8UD","fw":"1.0.0"}

$ e {"funzione":"STATO","ingresso":["ID1"]}
{"ID1":"0"}

$ e {"funzione":"ALTO","uscita":["UD1"]}
{"UD1":"1"}

Esempio Python minimale

import json
import serial

port = serial.Serial("/dev/ttyUSB0", 115200, timeout=1)

def invia(funzione, **campi):
    richiesta = {"funzione": funzione, **campi}
    linea = "e " + json.dumps(richiesta) + "\n"
    port.write(linea.encode("utf-8"))
    risposta = port.readline().decode("utf-8").strip()
    return json.loads(risposta)

# Modalità silenziosa, da inviare per prima
invia("SILENTE")

# Identificazione del dispositivo
print(invia("ID"))

# Lettura di un ingresso
print(invia("STATO", ingresso=["ID1"]))

# Attivazione di un'uscita
print(invia("ALTO", uscita=["UD1"]))

port.close()

Elenco dei comandi

funzione Effetto Campi aggiuntivi
ID Identifica il dispositivo nessuno
STATO Legge uno o più ingressi ingresso (array)
ALTO Attiva una o più uscite uscita (array)
BASSO Disattiva una o più uscite uscita (array)
SETNOME Imposta il nome del dispositivo valore
SETGRUPPO Imposta il gruppo del dispositivo valore
SILENTE Disattiva eco e prompt nessuno

Elementi da confermare o completare

  • comportamento delle uscite alla disconnessione USB o al riavvio (oggi: tutte a OFF all’avvio, per specifica generale del progetto — da confermare come comportamento firmware effettivo);
  • dimensione massima del buffer comando in caratteri (attualmente confermata fino a circa 4095 caratteri utili);
  • eventuale numero di versione del protocollo, non ancora previsto.

Funzioni escluse da questa versione

Le seguenti funzioni sono previste nella visione generale del progetto ma non fanno ancora parte del firmware:

  • variabili persistenti e volatili;
  • contatori e timer;
  • logiche AND/OR/NOT fra oggetti;
  • comunicazione fra più dispositivi (RS485);
  • aggiornamento firmware via protocollo;
  • diagnostica avanzata e registrazione eventi.

Verranno documentate separatamente quando saranno disponibili sul firmware.

Sintesi

programma -> e {"funzione":"...", ...}  -> Expansum
programma <- {"ok":1} | {"e":<codice>} | {"chiave":"valore"} <- Expansum

Ogni comando ha un equivalente nativo in italiano e un equivalente JSON con gli stessi effetti. Il canale JSON aggiunge il supporto multilingua sugli alias, restando comunque tradotto verso la stessa chiave canonica funzione.