Plancia configurabile su Modbus RTU

Due programmi che condividono uModbusRTU e uGauge:

- Console/PlanciaConsole: plancia nautica descritta da file XML, con
  modalita' plancia e modalita' configurazione. Comandi, spie, selettori,
  strumenti e allarmi sonori; il bus gira in un thread suo perche' la
  finestra non si fermi mai.
- ProjectPlancia: il programma di prova piu' vecchio, usato per collaudare
  i canali.

Le plance sono in Console/*.xml, la documentazione in Console/LEGGIMI.md.
Esclusi dal versionamento i compilati (.exe, .dcu), i file dell'IDE e un
audio da 17 MB.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
f.bittiandClaude Opus 5 committed 2026-09-22 16:56:34 +02:00
commit 4a01a2ec88
69 files changed
+11658

No files matched your search

+740
View File
@@ -0,0 +1,740 @@
# PlanciaConsole
Plancia configurabile su Modbus RTU / RS485. Due modalità, decise dalla riga di
comando; la configurazione vive in un file XML letto all'avvio.
Riusa `..\uModbusRTU.pas` e `..\uGauge.pas` del programma di test
(`ProjectPlancia`): i due progetti condividono quei due file, non ne esistono
copie. Il bus gira in un thread a parte (`uModbusWorker.pas`), così la finestra
non si ferma mai; vedi "Il bus in un thread a parte".
## Avvio
PlanciaConsole.exe aboard, legge plancia.xml
PlanciaConsole.exe aboard
PlanciaConsole.exe config configurazione
PlanciaConsole.exe config -f rotta.xml configurazione su un altro file
PlanciaConsole.exe rotta.xml aboard su un altro file
Senza indicazioni viene riaperta l'ultima plancia usata, e in mancanza di
quella `plancia.xml` accanto all'eseguibile. `-f <percorso>`,
`-file=<percorso>` o un parametro libero indicano un file diverso.
### Modalità aboard
Legge l'XML, apre la porta seriale indicata nel file, si connette da sola e
opera. A vista c'è solo la plancia più una barra di stato con porta, baud, LED
verde/rosso e ultimo messaggio. Nessun comando di configurazione.
La finestra si dimensiona sulla plancia. Se il pannello è più grande dello
schermo lo zoom si riduce automaticamente per farcelo stare: a bordo è meglio
una plancia rimpicciolita che una da raggiungere scorrendo.
### Cambiare plancia a bordo
In fondo a destra, nella barra di stato, una casella elenca le plance della
stessa cartella del file caricato: tutti gli XML che sono configurazioni di
plancia, mostrati con il loro titolo. Scegliendone una:
- la porta seriale viene chiusa e riaperta con porta e baud del nuovo file;
- la finestra si ridimensiona sul nuovo pannello;
- **le bobine non vengono toccate**: i relè restano come sono e la nuova plancia
ne rilegge lo stato al primo ciclo di polling.
Il file viene letto per prova prima di lasciare quello in servizio: se è rotto
resta la plancia di prima e l'errore compare nella barra di stato.
La casella compare solo in aboard e solo se nella cartella ci sono almeno due
plance. In configurazione si usa "Apri", che chiede anche delle modifiche non
salvate. Dopo la scelta il fuoco viene tolto alla casella, così le frecce della
tastiera non cambiano plancia per sbaglio.
### Modalità configuration
Palette a sinistra, pannello a destra con le proprietà dell'elemento
selezionato, barra in alto con Nuovo / Apri / Salva / Salva con nome, griglia e
zoom.
- **Inserire**: trascina una voce della palette sul pannello. L'elemento nasce
centrato sul punto di rilascio; se lì c'è già qualcosa scala in diagonale
finché trova posto libero, così non si impilano tutti nello stesso punto.
- **Spostare**: trascina l'elemento. Un click senza trascinare non lo sposta:
il movimento parte solo oltre la soglia di trascinamento di Windows (pochi
pixel), e un asse su cui il mouse non si è mosso non viene riagganciato alla
griglia.
- **Ridimensionare**: trascina una delle otto maniglie dell'elemento
selezionato, oppure scrivi larghezza e altezza nel pannello proprietà.
- **Ritocchi da tastiera**, sull'elemento selezionato: **Ctrl+freccia** lo
sposta di un pixel, **Maiusc+freccia** cambia le misure di un pixel (destra
e giù ingrandiscono, sinistra e su rimpiccioliscono). Un pixel esatto anche
con la griglia attiva. Non funzionano mentre si scrive in una casella, dove
le frecce restano del testo: cliccare l'elemento riporta i tasti sul pannello.
- **Annulla / Ripeti**: pulsanti nella barra, oppure **Ctrl+Z** e **Ctrl+Y**
(o Ctrl+Maiusc+Z). Coprono spostamenti, misure, proprietà, tipo, elementi
aggiunti e cancellati e le impostazioni generali, fino a 100 passi. Le
modifiche in fila sulla stessa cosa, entro un secondo e mezzo l'una
dall'altra, contano come un passo solo: le cifre di una larghezza digitata,
o Ctrl+freccia tenuto premuto. Lo storico si azzera con Nuovo e Apri.
Un'immagine rimossa dalla libreria non torna con Annulla: tornano i
riferimenti degli elementi, ma il file va riaggiunto.
- **Griglia**: con "Griglia 10 px" attiva posizioni e bordi si agganciano a
multipli di 10.
- **Zoom**: da 50% a 200%. È solo una lente di lavoro: le coordinate salvate
nell'XML restano sempre quelle reali al 100%.
- **Configurare**: seleziona l'elemento e compila i campi. Le modifiche si
vedono subito.
- **Eliminare**: pulsante "Elimina elemento" o tasto Canc.
- **Chiusura**: chiudendo la finestra la plancia viene **salvata da sola** nel
file su cui si stava lavorando, senza chiedere niente. Solo se il
salvataggio non riesce (disco pieno, file di sola lettura) viene chiesto se
chiudere comunque. Nuovo, Apri e il passaggio in plancia invece continuano a
chiedere cosa fare delle modifiche: lì si può volerle buttare.
**Come viene scritto il file.** Il salvataggio non scrive mai sopra il file
buono: scrive un `.tmp` accanto e poi scambia i nomi, tenendo la versione
precedente come `.bak`. Così un programma chiuso male, un disco pieno o due
copie del programma che salvano insieme non possono lasciare un XML troncato,
che alla riapertura non si leggerebbe più. Se un file non si apre, accanto c'è
il `.bak` della volta prima: si rinomina e si riparte da lì.
**Se il file non si è aperto**, chiudendo non ci si salva sopra: il programma
avverte e propone "Salva con nome", perché salvare il pannello vuoto
cancellerebbe quello che il file contiene.
All'avvio senza indicazioni viene riaperta **l'ultima plancia usata**: il
percorso è ricordato in `plancia-ultima.txt` accanto all'eseguibile, e si
aggiorna a ogni apertura, salvataggio e cambio di plancia a bordo. Se quel file
non c'è, o la plancia è stata spostata, si riparte da `plancia.xml`. Un file
indicato sulla riga di comando ha comunque la precedenza.
In configurazione la porta seriale **non** viene aperta: si disegna la plancia
senza rischiare di comandare relè per sbaglio. Per provare i canali si usa il
programma di test `ProjectPlancia`.
### Passare da una modalità all'altra
Chi è partito con `config` trova in fondo a destra, nella barra di stato, un
pulsante **"Vai in plancia"**: la palette e le proprietà spariscono, la finestra
si veste sulla plancia e la porta seriale si apre. Da lì il pulsante diventa
**"Torna a configurare"** e riporta indietro, chiudendo la porta.
Serve a provare quello che si è appena disegnato senza chiudere e riaprire il
programma con un'altra riga di comando.
Tre cose da sapere:
- Se ci sono modifiche non salvate viene chiesto cosa farne **prima** di passare
in plancia, altrimenti si perderebbero in silenzio.
- Tornando a configurare la porta seriale viene **chiusa**, e le spie tornano a
"dato non disponibile": una plancia che non sta leggendo non deve sembrare in
servizio.
- Posizione della finestra e zoom di lavoro vengono ritrovati come li si era
lasciati, perché in plancia la finestra si ridimensiona sul pannello.
**Chi è partito in `aboard` non vede il pulsante.** Una plancia avviata in
servizio non deve offrire la strada per essere modificata per sbaglio; per
configurarla si riavvia con `config`.
### Modalità notturna
Accanto c'è il pulsante **"Notte"**, questo disponibile in entrambe le modalità.
Scurisce l'intera plancia: fondo quasi nero, immagine di sfondo ricalcolata
scura, e ogni colore abbassato alla stessa frazione, così i rapporti fra i
colori restano quelli del giorno e il quadro resta leggibile. Serve in
navigazione notturna, dove un pannello chiaro a tutto schermo brucia
l'adattamento al buio e per qualche minuto non si vede più fuori.
Le scritte non vengono abbassate ma **sostituite con un ambra**: una serigrafia
nera abbassata resterebbe nera, cioè invisibile sul fondo scuro. L'ambra è anche
il colore che disturba meno la visione notturna. Vale per etichette, legende dei
selettori, marchi e scale dei gauge.
Si torna al giorno con lo stesso pulsante, che nel frattempo dice "Giorno". La
scelta **non** viene salvata nell'XML: è una condizione del momento, non una
proprietà della plancia.
## Tipi di elemento
| Tipo | Funzione Modbus | Comportamento |
|---|---|---|
| Pulsante | `WriteSingleCoil` (05) | Chiude alla pressione, riapre al rilascio (es. horn) |
| Interruttore | `WriteSingleCoil` (05) + `ReadCoils` (01) | Commuta alla pressione del mouse e resta premuto |
| Selettore | `WriteSingleCoil` (05) + `ReadCoils` (01) | Manopola rotativa a 2 o 3 posizioni, anche a ritorno di molla |
| Spia | `ReadDiscreteInputs` (02) | Sola lettura, LED di colore configurabile |
| Gauge | `ReadHoldingRegisters` (03) | Colonna con scala, soglie e valore |
| Display | `ReadHoldingRegisters` (03) | Numero a sette segmenti rossi su fondo nero |
| Immagine | nessuna | Grafica decorativa: loghi, sagome, cornici |
| Testo | nessuna | Scritta serigrafata sul pannello |
Il campo `channel` è l'indirizzo Modbus della bobina o del registro, `slave`
l'indirizzo del nodo sul bus. Immagine e Testo non hanno né slave né canale.
Gli interruttori e i selettori rileggono le bobine ad ogni ciclo: se un relè
cambia stato per altra via, o una scrittura non va a segno, il comando a video
si riallinea. I pulsanti momentanei non vengono riallineati, perché il loro
stato dipende dal mouse.
### All'ingresso in plancia la plancia si allinea al campo
Entrando in modalità plancia (all'avvio, tornando dalla configurazione o
cambiando plancia) la prima lettura serve a mettere tutto nella posizione in
cui è il campo, non solo a controllare:
- **interruttori e selettori** dalle bobine (funzione 01);
- **pulsanti** dalle bobine, ma **solo in questa prima lettura**: se un canale
momentaneo è rimasto eccitato va mostrato chiuso, mentre dopo lo stato del
pulsante lo decide il mouse e rileggerlo lo farebbe lampeggiare;
- **spie** dagli ingressi digitali (funzione 02), come sempre.
**In questa prima lettura le bobine si chiedono una per una**, non a blocco. Un
blocco che arriva oltre l'ultimo canale del modulo fallisce tutto, e con lui
fallirebbe l'allineamento anche dei comandi su canali che esistono: chiedendo un
canale per volta fallisce solo la richiesta del canale che non c'è. Costa un
giro più lento, una volta sola. Finito l'allineamento si torna alle richieste
raggruppate, che sono quelle che tengono leggero il polling.
Chiudendo il programma non viene scritto niente sul bus: i relè restano come
sono, ed è la plancia che alla ripartenza si adatta a loro. Quando l'allineamento
è fatto la barra di stato dice quanti canali sono stati letti, quanti comandi
risultano chiusi e quanti canali non hanno risposto; se non risponde nessuna
bobina si riprova al ciclo dopo, invece di dare per aperto quello che non si sa.
Una spia già in allarme all'avvio fa suonare il suo cicalino.
### Più comandi sulla stessa bobina
Lo stesso `slave` e `channel` si possono mettere su più elementi: lo stesso relè
comandato da due punti della plancia, o un pulsante e un interruttore sullo
stesso circuito. Premendone uno **gli altri si muovono subito**, senza aspettare
la rilettura: mezzo secondo di disaccordo fra due comandi che sono la stessa cosa
si nota. Vale in tutte le combinazioni, compresi i selettori (per quelli a 3
posizioni conta la bobina del lato).
Le spie no: leggono gli ingressi digitali, che sono un altro spazio di
indirizzi, quindi una spia sullo stesso numero di canale di una bobina non è la
stessa cosa e non viene toccata.
Una risposta **più vecchia del comando appena dato** viene scartata. Le letture
partono a giri regolari e la risposta può arrivare dopo un click: crederle
farebbe tornare indietro il comando per un giro, e si vedrebbe il comando
spegnersi, riaccendersi e rispegnersi. Ogni comando segna l'istante in cui ha
scritto la sua bobina, e le risposte partite prima di quel momento non vengono
applicate a quella bobina.
**Attenzione**: questo riallineamento è anche il motivo per cui un interruttore
può *sembrare* un pulsante. Se la rilettura della bobina risponde 0 — relè
assente, indirizzo sbagliato, scrittura non andata a segno — l'interruttore
torna su da solo entro un ciclo di polling, e a occhio sembra che non resti
premuto. Il tipo dell'elemento non c'entra: si guarda la barra di stato.
### Cambiare tipo dopo il disegno
Pulsante, interruttore e spia si scambiano fra loro in qualsiasi momento, dalla
casella **Tipo** in cima al pannello proprietà, come si fa con la forma o il
canale. Condividono tutti i campi — canale, colori, forma, immagini, etichetta —
quindi il passaggio non perde niente. Cambia solo il funzionamento: il pulsante
chiude il contatto finché lo tieni premuto, l'interruttore commuta e resta, la
spia non si preme e si accende quando l'ingresso è chiuso.
Serve soprattutto per le lenti colorate che sulla foto di un quadro sembrano
pulsanti ma sono allarmi, come FIRE ALARM e OIL STEERING di ZEBRA.
**Attenzione al canale** passando da comando a spia o viceversa: il numero
resta lo stesso ma cambia significato. Per pulsanti e interruttori è una bobina
da comandare, per la spia un ingresso digitale da leggere (funzione 02), che di
solito sta su un altro modulo. La barra di stato lo ricorda al momento del
cambio.
Cambiando tipo l'elemento riparte da spento, perché un pulsante non viene più
riletto dal campo e resterebbe illuminato per sempre. Se l'etichetta era
ancora quella predefinita segue il nuovo tipo, altrimenti resta la tua.
### Selettore rotativo
La leva **ruota**: passando da una posizione all'altra si vede girare, sempre
passando per l'alto, in 130 millisecondi con partenza e arrivo morbidi. È solo
quello che si vede: il comando sul bus parte subito, all'inizio del movimento.
In configurazione la leva sta ferma dove dice la definizione.
Si "gira" premendo dal lato verso cui lo si vuole portare: pressione a sinistra
della manopola per scendere di una posizione, a destra per salire. La legenda
sopra la manopola è testo libero (`OFF ◄ 0 ► ON`, `STOP ◄ 0 ► START`, …).
Il cablaggio dipende dal numero di posizioni:
- **2 posizioni**: una bobina, quella di `channel`. Chiusa = posizione destra.
- **3 posizioni**: **due bobine**, `channel` e (salvo indicazione) `channel+1`.
La prima chiusa = posizione sinistra, la seconda chiusa = destra, entrambe
aperte = centro (0).
Quindi un selettore a 3 posizioni **occupa due canali**: nel numerare i canali
va lasciato il buco.
Per configurarlo: tipo Selettore, **Posizioni = 3**, e in **Canale** la bobina
del lato sinistro. La bobina di destra si imposta nel riquadro "Selettore", in
**Canale lato destro**: lasciandolo vuoto è il canale successivo (l'etichetta
ricorda quale sarebbe), e si compila solo quando sul modulo le due bobine non
sono contigue. Nell'XML è l'attributo `channel2`, scritto solo se indicato.
Il suggerimento a comparsa dell'elemento mostra entrambe le bobine.
#### Ritorno a molla
Con **Ritorno a molla** spuntato (nell'XML `momentary="true"`) il selettore non
resta dove lo porti: tiene la posizione finché lo tieni premuto e torna al
centro appena lasci il mouse, aprendo entrambe le bobine. È il comando di
avviamento dei quadri veri, `STOP ◄ 0 ► START`: si tiene su START finché il
motore parte, e si molla.
- Si preme direttamente dal lato voluto, senza passare per lo zero.
- Al rilascio torna al centro **sempre**, anche se il mouse è finito fuori
dall'elemento e anche se la scrittura di andata era fallita: un comando di
avviamento non deve mai restare eccitato.
- Finché è premuto il polling non lo sposta, altrimenti una lettura arrivata in
quell'istante lo farebbe scattare al centro sotto il dito.
- Vale anche a 2 posizioni: premuto = chiusa, lasciato = aperta.
In `antago.xml` e in `plancia-mfd.xml` è così il selettore **GENERATOR**.
Le due bobine **non vengono mai chiuse insieme**: a ogni cambio di posizione si
apre prima quella del lato opposto e solo dopo si chiude l'altra. Se la seconda
scrittura fallisce il selettore resta al centro, con tutte e due aperte. Questa
però è una protezione del programma: se i due lati comandano qualcosa che non
deve mai partire insieme (due sensi di marcia, due alimentazioni), serve
comunque l'interblocco elettrico fra i due relè.
### Gauge a quadrante
Con `style="dial"` il gauge non è una colonna ma uno strumento a lancetta, come
quelli dei display di plancia: scala su 270 gradi con tacche e numeri, lancetta,
e sotto il perno la lettura in cifre con le unità (`decimals` ne fissa i
decimali). Se l'elemento è più alto che largo il titolo va sotto al quadrante,
altrimenti dentro.
La fascia della scala è grigia. Con `warnBelow` / `warnAbove` impostati diventa
verde nell'intervallo buono e rossa fuori: senza soglie resta grigia, perché un
quadrante tutto verde direbbe "tutto a posto" senza saperlo. Senza dato valido
la lancetta non c'è e la lettura mostra `---`.
Stile e decimali del quadrante per ora si impostano solo nell'XML; il pannello
proprietà non li mostra ma il salvataggio li conserva.
### Display
Mostra il registro scalato in unità reali, allineato a destra sul numero di
cifre richiesto, con zeri davanti come i display veri. `digits` è il numero di
cifre, `decimals` quante dopo la virgola. Senza dato valido i segmenti restano
tutti spenti.
### Spia
Il colore da accesa si imposta con l'attributo `color` (`#RRGGBB`): rosso per
gli allarmi, verde per i consensi, giallo e arancio per i livelli. Se
l'etichetta è vuota il LED — o l'immagine PNG che lo sostituisce — occupa tutto
l'elemento, così si possono appoggiare spie piccole sopra una grafica. Vale per
qualunque elemento con l'etichetta fuori dal disegno: senza etichetta non viene
riservata nessuna fascia.
#### Allarme sonoro
Ogni spia può far suonare qualcosa quando si accende. Nel pannello proprietà,
riquadro **Allarme sonoro**:
- **Suona quando si accende**: la casella che accende o spegne l'allarme.
- **File**: si sceglie fra i suoni presenti nella cartella **`suoni\` accanto
al file XML della plancia** (MP3 e WAV). Come per le immagini, i suoni
viaggiano con la plancia quando si copia la cartella.
- **Aggiorna**: rilegge la cartella. Serve quando si aggiunge un file mentre il
programma è già aperto, senza doverlo riavviare.
Nell'XML sono gli attributi `alarm="true"` e `sound="cicalino.mp3"` sulla spia.
Il nome del file resta salvato anche con l'allarme spento, così si può
riaccendere senza riscegliere il suono.
Come suona:
- **Parte sul fronte**, quando la spia passa da spenta ad accesa, e poi **va in
ciclo**: finito il file ricomincia, e continua finché l'allarme c'è. Un
cicalino che suona una volta sola lo si perde se in quel momento si sta
guardando altrove. Si ferma quando l'allarme rientra, quando lo si zittisce
premendo la spia, o lasciando la plancia.
- **Se l'allarme è già attivo all'avvio** suona appena arriva la prima lettura:
una plancia che parte con una sentina piena deve dirlo.
- **Non blocca niente**: il suono va per conto suo, la plancia resta comandabile.
- Due allarmi diversi si sovrappongono; lo stesso allarme che si ripete riparte
da capo.
- **Quando l'allarme rientra il suono si ferma subito**, senza aspettare la
fine del file: serve per i suoni lunghi, una sirena che continua a suonare
su un allarme già passato è peggio del silenzio. Se lo stesso file è
assegnato a più spie, tace solo quando si è spenta l'ultima ancora accesa.
- **Se il file manca o non si può suonare**, la barra di stato lo dice: un
cicalino muto senza avviso è peggio di nessun cicalino.
- Uscendo dalla plancia (torna a configurare, chiusura) i suoni si fermano.
#### Zittire un allarme
**Si preme la spia che sta suonando**: è il gesto che viene naturale, si preme
quello che dà fastidio. Ogni pressione allunga il silenzio:
| Pressioni | Silenzio |
|---|---|
| 1 | un minuto |
| 2 | dieci minuti |
| 3 | un'ora |
| 4 | allarme di nuovo attivo |
Le pressioni contano finché il silenzio dura: dopo che è scaduto si ricomincia
da un minuto. Il suono in corso si ferma subito alla prima pressione.
**Il silenzio non spegne la spia**: il LED resta acceso, con una sbarra sopra,
perché guardando il quadro si deve capire che quell'allarme c'è ancora e sta
suonando a vuoto. Premere una spia spenta, o una senza allarme, non fa niente.
**Quando il silenzio scade, se la condizione è ancora presente l'allarme torna
a suonare**: è il senso di zittire "per un minuto" invece che per sempre.
In basso a destra, nella barra di stato, compare il tasto **"N allarmi zittiti
(tempo) — riattiva"**, che dice quanti sono e quanto manca al primo che torna a
suonare. Premendolo si riattivano tutti subito, e quelli ancora presenti
suonano di nuovo. Il tasto si vede solo finché c'è almeno un allarme zittito.
Se l'allarme rientra da solo, il silenzio si azzera con lui: la volta dopo si
riparte da un minuto. Tornando a configurare, o cambiando plancia, i silenzi si
azzerano tutti.
In `suoni\` c'è `cicalino.wav`, due bip generati per provare subito; va
sostituito con il suono vero.
La spia accetta anche la forma (`shape`) dei pulsanti, per somigliare alle
lenti di allarme dei quadri:
- **`round`** — ghiera metallica e lente tonda. Spenta la lente resta del suo
colore ma scura (`colorOff`, o `color` scurito se manca), accesa prende
`color` pieno con un riflesso. La ghiera non cambia: è una spia, non un
comando premuto.
- **`screen`** — tasto a video che si riempie di `color` quando l'ingresso è
chiuso.
- le altre forme — il LED tondo di sempre.
In ogni forma cliccare una spia non fa nulla e non manda niente sul bus.
### Aspetto di pulsanti, interruttori e spie
**La lente di un pulsante o di un interruttore non cambia mai colore.** Lo stato
si legge solo dal bordo, come su un quadro vero, dove il vetro è sempre dello
stesso colore e quello che cambia è la luce dietro.
- Forma **tonda**: la ghiera attorno alla lente è grigia a riposo e diventa
**azzurra luminosa** quando il comando è acceso, o mentre un pulsante è tenuto
premuto — più chiara contro la lente e più carica all'orlo, come se la luce
venisse da dentro.
- Forma **squadrata o a pillola**: non c'è ghiera, quindi è il bordo stesso che
si ingrossa e prende lo stesso azzurro.
Il colore della lente è **`colorOff`**. Su pulsanti e interruttori `color` non
tocca più il vetro: resta usato dalle spie, dove il cambio di colore è tutto
quello che c'è da vedere. Anche l'etichetta scritta *dentro* al comando resta
uguale: prima diventava bianca e in grassetto da acceso, ed era un secondo modo
di dire la stessa cosa che ora dice il bordo.
Tre attributi permettono di far somigliare i comandi a quelli del quadro vero,
e si impostano anche dal riquadro "Aspetto" del pannello proprietà:
- **`shape`** — `auto` (pillola per i pulsanti, squadrato per gli
interruttori), `round`, `rect`, `pill`, `screen`. I quadri di bordo hanno
quasi sempre pulsanti tondi: con `round` l'elemento viene disegnato con
ghiera metallica e lente, come un pulsante illuminato da incasso.
`screen` ("a video") è il tasto di un display multifunzione ed è l'eccezione
alla regola della lente: spento è un riquadro scuro con il filo chiaro
(`colorOff` per tingerlo), acceso **si riempie** del colore `color`. La
scritta sopra passa da chiara a nera quando il fondo diventa chiaro.
- **`captionPos`** — `center` (etichetta scritta sul comando), `below`,
`above`. Sui quadri veri l'etichetta è serigrafata **sotto** al pulsante:
con `below` il disegno si restringe per farle posto e il testo resta nero
sulla lamiera invece di stare sopra la lente. Le etichette lunghe vanno a
capo e la fascia si allarga da sola.
- **`color`** e **`colorOff`** — colore della lente accesa e a riposo. Serve
perché un pulsante STOP è rosso anche da spento e si limita a illuminarsi:
senza `colorOff` la lente a riposo è grigio-azzurra neutra.
### Testo e marchi
L'elemento Testo non serve solo alle scritte: con una cornice diventa un
marchio serigrafato, disegnato **con il font e non con un'immagine**, quindi
nitido a qualsiasi zoom invece di sgranare come farebbe un PNG ingrandito.
- **`fontName`** — nome del font, per esempio `Times New Roman` o
`Arial Black`. Vuoto = quello del pannello. Funziona su qualsiasi elemento,
non solo sul Testo.
- **`spacing`** — pixel in più fra una lettera e l'altra. I marchi hanno quasi
sempre le lettere larghe e senza questo non somigliano.
- **`frame`** — `none`, `oval`, `rect`, `round` (rettangolo stondato, cioè a
pastiglia). Disegnata con GDI+ in antialiasing, altrimenti un ovale grande
verrebbe scalettato.
- **`frameWidth`** — spessore del tratto della cornice.
- **`color`** — colore di inchiostro e cornice.
Un marchio su più righe si compone sovrapponendo più elementi: uno con la sola
cornice (etichetta vuota), uno con il nome in grande, uno con il sottotitolo
piccolo. È così che sono fatti i loghi ZEBRA e ANTAGO negli esempi.
Per andare a capo dentro un'etichetta si usa `&#10;`, per esempio
`caption="PORT&#10;ENGINE"`.
## Immagini
La libreria immagini è nel pannello di sinistra: "Aggiungi..." accetta PNG, JPG
e BMP (selezione multipla), "Rimuovi" toglie l'immagine e la sgancia dagli
elementi che la usavano, chiedendo conferma.
Non è una `TImageList`: quella imporrebbe a tutte le immagini la stessa
dimensione, mentre qui un LED da 32 px e un logo da 600 px devono convivere.
**Nell'XML si salvano nome e percorso, non i pixel.** I file restano sul disco e
si possono sostituire senza toccare la configurazione. I percorsi sono relativi
alla cartella del file XML quando possibile, così l'intera plancia si trasporta
copiando una cartella; "Salva con nome" in un'altra cartella ricalcola i
percorsi da solo.
Dove si usano:
- **Pulsante, interruttore, spia**: due immagini, a riposo e attiva. Se il campo
"immagine a riposo" è vuoto si torna al disegno vettoriale predefinito. Se è
valorizzata solo quella a riposo, l'elemento non cambia aspetto quando si
attiva.
- **Immagine**: una sola, decorativa.
- **Sfondo del pannello**: si sceglie in "Generale → Immagine di sfondo", ed è
stirata su tutto il pannello.
Le PNG con trasparenza funzionano: gli elementi non riempiono il proprio sfondo,
quindi un logo con alpha si fonde con lo sfondo della plancia.
L'etichetta continua a essere scritta sopra l'immagine, in bianco con contorno
nero perché resti leggibile su fondo chiaro e scuro; sulle spie va in basso per
non coprire il LED. Se l'immagine contiene già la scritta, basta svuotare il
campo Etichetta.
Se un file manca o non si apre, nella libreria il nome compare seguito da `[!]`
e l'elemento mostra un riquadro tratteggiato in configurazione.
## Il file XML
<?xml version="1.0" encoding="UTF-8"?>
<plancia version="1">
<connection port="COM1" baud="9600" timeoutMs="500" pollMs="500"/>
<panel title="Plancia ZEBRA" width="980" height="560" background="sfondo"/>
<images>
<image name="sfondo" file="immagini\sfondo.png"/>
<image name="btn-off" file="immagini\btn-off.png"/>
<image name="btn-on" file="immagini\btn-on.png"/>
</images>
<elements>
<element kind="button" caption="HORN" slave="1" channel="5"
left="40" top="60" width="180" height="70"
imageOff="btn-off" imageOn="btn-on"/>
<element kind="image" caption="" image="logo-zebra"
left="620" top="440" width="320" height="90"/>
<element kind="gauge" caption="Serbatoio" slave="5" channel="1"
left="640" top="200" width="130" height="200"
rawMin="0" rawMax="4095" engMin="0" engMax="100"
units="%" warnBelow="15" warnAbove="1E30"/>
</elements>
</plancia>
`kind` vale `button`, `switch`, `rotary`, `lamp`, `gauge`, `display`, `image`
o `label`.
- `fontSize`: altezza del testo in pixel logici; assente = font del pannello.
- `imageOff` / `imageOn`: nomi presi da `<images>`; solo per pulsanti,
interruttori e spie.
- `image`: l'immagine dell'elemento decorativo.
- `background` su `<panel>`: immagine di sfondo.
- `ink` su `<panel>`: colore della serigrafia, `#RRGGBB`, nero se assente.
Vale per etichette sotto/sopra i comandi, legende e titoli di selettori,
display e quadranti, e per i `label` senza `color`. Le plance a fondo scuro
lo mettono chiaro. Si imposta solo nell'XML.
- `style` (`bar` o `dial`) e `decimals`: aspetto del `gauge`, vedi sopra.
- `color` / `colorOff`: colore acceso e a riposo di spie, pulsanti e
interruttori, `#RRGGBB`. Su `label` è il colore di inchiostro e cornice.
- `shape` e `captionPos`: forma del comando e posizione dell'etichetta.
- `fontName` e `spacing`: font e spaziatura fra le lettere.
- `frame` e `frameWidth`: cornice attorno a un `label`.
- `alarm` e `sound`: solo per `lamp`, allarme sonoro all'accensione; il file
sta nella cartella `suoni\` accanto all'XML.
- `positions` (2 o 3), `legend`, `channel2` e `momentary`: solo per `rotary`.
`channel2` è la bobina del lato destro di un selettore a 3 posizioni;
assente = `channel+1`. `momentary="true"` è il ritorno a molla.
- `digits` e `decimals`: solo per `display`.
- `raw*`, `eng*` e `units` valgono per `gauge` e `display`:
`rawMin`/`rawMax` sono il fondo scala del modulo (4095 = ADC a 12 bit),
`engMin`/`engMax` i valori reali corrispondenti.
- `warnBelow` / `warnAbove`: solo per `gauge`, sono le soglie oltre le quali la
colonna diventa rossa (`-1E30` e `1E30` significano "soglia non impostata").
I numeri decimali si scrivono con il punto. Il file si può modificare a mano:
un attributo mancante prende il valore di default. Per i caratteri speciali
nelle legende si usano le entità XML, per esempio `&#9664;` e `&#9654;` per le
frecce ◄ ►.
## File di esempio
- `esempio-plancia.xml` — solo elementi vettoriali, nessuna immagine.
- `esempio-immagini.xml` — usa la cartella `immagini\`: sfondo, logo con
trasparenza, pulsante e LED con grafica. Le immagini sono segnaposto generate
per la prova, da sostituire con quelle vere.
- `antago.xml` — riproduzione del quadro elettrico ANTAGO, ricavata dalla foto
del quadro reale. Il pannello è 1270×950 come la foto, così ogni elemento sta
dove sta sul quadro vero.
- `zebra.xml` — riproduzione della pulsantiera ZEBRA: 7 colonne × 6 righe di
pulsanti tondi illuminati, i due selettori delle ventole, i comandi motore
con gli ovali PORT/STBD ENGINE e il selettore PARALLEL a tre posizioni.
La foto è in prospettiva, quindi la griglia è stata ridisegnata dritta
(passo 120 in orizzontale, 145 in verticale); etichette, forme e colori delle
lenti vengono dalla foto.
- `plancia-mfd.xml` — tutti i comandi, le spie e le misure di ZEBRA e ANTAGO
su una sola plancia, nello stile della foto `immagini\Esempio plancia.jpg`:
cornice scura con tasti a video ai lati, schermo nero con quattro quadranti,
i due display dei caricabatterie, il sinottico della barca con le spie e i
tasti illuminati di motori e servizi; sotto, due file di selettori. Pannello
1900×1080. Gli indirizzi di ZEBRA sono quelli di `zebra.xml`; **le bobine
di ANTAGO sono spostate da slave 1 a slave 4**, perché sullo slave 1 si
sovrapponevano a quelle di ZEBRA, e i due comandi di prova "Luce" e "Horn"
di `antago.xml` (che stavano sul canale 0, lo stesso del selettore
VOLTMETER) sono sui canali 26 e 27 dello slave 4.
In tutti **slave e canali sono inventati** e vanno rimappati sui moduli
reali prima di collegare il bus.
## Strumenti
`strumenti\` contiene gli script PowerShell usati per preparare la grafica dei
due quadri:
| Script | Cosa fa |
|---|---|
| `ruota.ps1` | Raddrizza una foto scattata in verticale |
| `ritaglia-grafica.ps1` | Ritaglia un pezzo di serigrafia dalla foto e rende trasparente il fondo chiaro, lasciando solo il tratto |
| `genera-sfondo.ps1` | Disegna la lamiera del pannello con cornice e viti, di qualsiasi misura |
| `genera-mfd.ps1` | Disegna cornice e schermo di `plancia-mfd.xml` e la sagoma della barca ANTAGO in chiaro per il fondo nero |
Il ritaglio dalla foto conviene solo per i disegni che non si possono
ricostruire, come la sagoma della barca del quadro ANTAGO. **Le scritte no**:
i marchi ZEBRA e ANTAGO e gli ovali PORT/STBD ENGINE sono elementi Testo con
font e cornice, non immagini, così restano nitidi a ogni ingrandimento.
### Diagnostica del bus
Due programmi a riga di comando servono quando un modulo non si legge. Vanno
lanciati **con PlanciaConsole chiusa**, perché la porta si apre una volta sola.
Si ricompilano con i `build-*.bat` accanto ai sorgenti.
| Programma | Cosa fa |
|---|---|
| `scanbus.exe [porta] [baud] [ultimoIndirizzo]` | Interroga gli indirizzi uno per uno e dice chi risponde, con quali funzioni e che dati |
| `dumpframe.exe [porta] [baud] [slave] [funzione] [start] [quantità]` | Manda una sola richiesta e stampa i byte grezzi della risposta con la verifica della CRC |
| `monitorio.exe [porta] [baud] [slave] [canali] [cicli]` | Mostra gli ingressi digitali in tempo reale, una riga ad ogni cambiamento |
| `ringtest.exe [file.bmp]` | Disegna i comandi tondi spenti e accesi a tre misure e salva l'immagine: serve a giudicare una modifica ai colori guardandola |
`scanbus` serve a trovare l'indirizzo di un modulo appena montato. `dumpframe`
serve quando `scanbus` dà risultati incoerenti: mostrando il frame byte per byte
distingue un modulo che non risponde da due moduli che rispondono insieme.
**Due nodi con lo stesso indirizzo** è il guasto più frequente quando si
aggiunge un modulo: i moduli escono quasi tutti con indirizzo 1, e sul bus le
due risposte si sovrappongono. Il sintomo è una risposta di lunghezza variabile,
spesso con byte a zero in testa, e la CRC che non torna.
## Il bus in un thread a parte
Il thread principale non parla mai con la porta seriale. Tutto il Modbus vive
in un thread suo (`uModbusWorker.pas`), che apre la COM, fa le letture e le
scritture, e lascia i risultati in una cassetta condivisa. La finestra li
ritira con il suo timer. Così un timeout — mezzo secondo per richiesta — costa
tempo al thread del bus e non alla plancia, che resta trascinabile, ridisegnabile
e cliccabile anche con tutti i moduli staccati.
**Un thread solo, non uno per le letture e uno per le scritture.** Su RS485 il
filo è uno: due richieste insieme si sovrappongono e le risposte arrivano
mescolate. Chi comanda deve comunque aspettare la fine della richiesta in
corso, quindi un secondo thread aggiungerebbe solo un lucchetto attorno alla
porta. La reattività dei comandi si ottiene con la **precedenza**: la coda
delle scritture viene svuotata prima di ogni lettura, non solo a ogni giro,
quindi un pulsante premuto parte al massimo dopo la richiesta in corso.
Come si comporta:
- **Cliccare un comando torna subito.** La scrittura va in coda; l'elemento si
accende fidandosi. Se la scrittura non va a segno lo dice la barra di stato,
e la rilettura delle bobine rimette l'elemento a posto entro un ciclo.
- **L'ordine è garantito**: le due bobine di un selettore a 3 posizioni, o la
pressione e il rilascio di un pulsante, partono nella sequenza in cui sono
state messe in coda.
- **La porta si riapre da sola.** Se non c'è, o se cade (adattatore USB
staccato), il thread riprova ogni due secondi: non serve più riavviare il
programma.
- **Chiudendo**, l'attesa del thread dura al massimo quanto la richiesta in
corso.
## Aggiungere slave e canali
Niente nel programma è legato ai moduli montati adesso: una plancia si può
disegnare prima, e i moduli si aggiungono quando arrivano.
- **Slave**: qualsiasi indirizzo da 1 a 247, quanti se ne vuole sullo stesso
bus. Ogni slave nuovo aggiunge solo le sue richieste al giro di polling.
- **Canali**: qualsiasi numero da 0 a 65535, senza doverli tenere contigui.
Canali molto distanti sullo stesso slave allargano però l'intervallo letto
(vedi Polling), quindi conviene raggrupparli.
- **Limiti di una singola richiesta**, imposti dal protocollo: 2000 bit per
bobine e ingressi, 125 registri. Oltre, la richiesta viene saltata e la barra
di stato lo dice: si spezza l'intervallo o si sposta l'elemento su un altro
slave.
- **Nuove porte**: la porta è una per plancia (`<connection port=...>`). Due
bus separati si fanno con due plance, e si passa dall'una all'altra con la
casella in basso a destra.
**Una richiesta che non riesce si divide da sola.** Se la lettura di un blocco
di bobine fallisce, il programma la spezza a metà e riprova con le due metà, e
così via: un modulo da 32 canali interrogato fino al 38 finisce per farsi
leggere i suoi 32 in poche richieste, invece di non dare più niente. Ogni
divisione compare nella barra di stato. Le divisioni imparate valgono finché
non si cambia plancia o non si riapre la porta.
**Le richieste che non rispondono non danno fastidio.** Dopo tre errori di
fila una richiesta muta viene messa in pausa e riprovata ogni cinque secondi,
invece di costarne il timeout a ogni giro: così una plancia disegnata in
anticipo resta scorrevole e i moduli che rispondono vengono letti alla velocità
giusta. Appena il modulo viene collegato riparte da solo, entro quei cinque
secondi. La barra di stato distingue i due casi: `Timeout: nessuna risposta` è
una richiesta che si sta ancora provando, `non risponde, riprovo fra 4 s` è una
messa in pausa. La pausa si azzera da sola quando si cambia plancia, quando si
modificano i canali in configurazione e quando la porta viene riaperta.
La pausa vale **per singola richiesta**, non per modulo: sullo stesso nodo le
bobine possono rispondere benissimo mentre gli ingressi, che quel modulo non ha,
danno errore. Mettere in pausa tutto il nodo per colpa degli ingressi
lascerebbe la plancia cieca proprio su quello che si legge.
## Polling
Ad ogni ciclo (`pollMs`) gli elementi vengono raggruppati per slave e tipo, e
per ogni gruppo parte **una sola** richiesta che copre i canali da quello più
basso a quello più alto. Aggiungere elementi contigui non aumenta il traffico
sul bus; canali molto distanti sullo stesso slave sì, perché allargano
l'intervallo letto.
Ogni richiesta va per conto suo: se un modulo non risponde, gli altri gruppi
vengono letti comunque. Solo le spie e i gauge di **quella** richiesta passano a
"dato non disponibile"; il resto della plancia continua a leggere. Interruttori
e selettori invece non si azzerano, perché la loro posizione è un comando dato,
non una misura.
L'errore finisce nella barra di stato dicendo **cosa** non si è potuto leggere:
slave, funzione e intervallo di canali, per esempio
Lettura fallita su 4 richieste, la prima: slave 3, ingressi 0-14 (FC02): timeout
Così si capisce subito se manca un modulo, se l'indirizzo è sbagliato o se si
sta leggendo oltre i canali che il modulo ha (tipico: un modulo da 8 bobine
interrogato per 37, perché un elemento ha un canale alto). Il polling continua
e si riallinea da solo appena il bus risponde.