In questa lezione impari a
- decidere che cosa misura un dispositivo e come chiamare i dati
- simulare in Python un dispositivo che cambia nel tempo
- impostare su ThingsBoard una regola di allarme con una soglia
- ricevere un comando dalla dashboard (RPC) e cambiare lo stato del dispositivo
Nel laboratorio 3 il sensore era già pronto: lo avete solo collegato. Oggi invertiamo le parti: il dispositivo lo progettate voi. Decidete che cosa misura, quali dati manda, quando deve scattare l'allarme e quale comando può ricevere.
Per capire come si fa, partiamo da un esempio completo: una cisterna d'acqua che si svuota e avvisa quando sta per finire.
Il kit della lezione
| File | A cosa serve |
|---|---|
cisterna_tb.py |
il modello: la cisterna simulata |
SCHEDA-CONSEGNA.txt |
la scheda da compilare e consegnare con il vostro progetto |
requirements.txt |
la libreria: paho-mqtt |
ThingsBoard si apre come nel laboratorio 3: http://192.168.104.117:8080, con la vostra email e password.
📘 Le schermate di tutti i passaggi (regola di allarme, widget, terminale RPC) sono nella Guida a ThingsBoard, che usa proprio la cisterna come esempio.
Passo 1: far partire la cisterna
- In ThingsBoard: Entità → Dispositivi → +, nome
cisterna, poi Copia token di accesso. - In
cisterna_tb.pyincollate il token nella rigaTOKEN(solo quella:SERVERè già giusto). python cisterna_tb.py: deve scrivereCisterna collegata a ThingsBoard!e poi il livello ogni 3 secondi.
Livello 98 % (984 litri)
Livello 97 % (971 litri)
...
In Ultima telemetria compaiono tre dati: litri, livello e pompa.
Come funziona la cisterna
Un dispositivo simulato ha sempre la stessa forma: uno stato (come è fatto adesso), un ciclo che lo fa cambiare e lo manda a ThingsBoard, e — se serve — un comando che arriva da fuori.
┌──────────── ogni 3 secondi ────────────┐
▼ │
lo stato cambia ──► calcolo la misura ──► publish su ThingsBoard
(si consuma acqua, (litri, livello %, │
o la pompa riempie) pompa sì/no) │
▲ ▼
│ ThingsBoard: dashboard
comando "riempi" ◄──── dashboard ◄──── e regola di allarme
Lo stato
CAPACITA = 1000 # litri quando è piena
CONSUMO = (5, 25) # litri usati a ogni giro: tra 5 e 25
PAUSA = 3 # secondi tra una misura e l'altra
litri = CAPACITA # si parte con la cisterna piena
pompa = False # True = la pompa sta riempiendo
Le costanti in MAIUSCOLO descrivono il dispositivo: cambiandole, cambia il "carattere" della cisterna (più grande, più veloce a svuotarsi). Le variabili litri e pompa sono lo stato, che cambia nel tempo.
Il ciclo
while True:
if pompa:
litri = litri + 80 # la pompa riempie 80 litri a giro
if litri >= CAPACITA:
litri = CAPACITA
pompa = False # piena: la pompa si spegne da sola
else:
litri = litri - random.randint(*CONSUMO) # qualcuno usa l'acqua
litri = max(0, litri) # non si va sotto zero
livello = round(litri / CAPACITA * 100) # in percentuale
misura = {"litri": litri, "livello": livello, "pompa": pompa}
client.publish(TOPIC, json.dumps(misura), qos=1)
time.sleep(PAUSA)
random.randint(*CONSUMO)sceglie un numero tra 5 e 25: l'asterisco "apre" la coppia(5, 25)nei due valori.max(0, litri)impedisce i litri negativi: un dispositivo simulato deve restare realistico.- Il livello in percentuale è comodo per la dashboard e per l'allarme: "sotto il 20 %" vale per qualsiasi cisterna.
Il comando
def comando(client, userdata, msg):
global pompa
richiesta = json.loads(msg.payload) # es. {"method": "riempi", "params": {}}
numero = msg.topic.split("/")[-1] # il numero della richiesta
if richiesta.get("method") == "riempi":
pompa = True
risposta = {"pompa": "accesa"}
else:
risposta = {"errore": "comando sconosciuto"}
client.publish(f"v1/devices/me/rpc/response/{numero}", json.dumps(risposta))
È la ventola del laboratorio 3, con un altro nome. global pompa serve perché la funzione cambia una variabile che sta fuori da lei.
Chi decide l'allarme? Non lo script: ThingsBoard, con una regola. È una scelta voluta: la soglia si cambia in un attimo dall'interfaccia, senza toccare il dispositivo (che nella realtà potrebbe stare su un tetto).
Passo 2: l'allarme
Sul dispositivo cisterna: Regole allarme → + → Crea nuova regola allarme.
| Campo | Valore |
|---|---|
| Tipo di allarme | Cisterna quasi vuota |
| Gravità | Avviso |
| Condizione | livello minore di 20 |
| Condizione di cancellazione | livello maggiore di 20 (l'allarme si chiude da solo quando la cisterna viene riempita) |
Aspettate qualche minuto: quando il livello scende sotto il 20 %, l'allarme compare nel menu Allarmi.
Passo 3: la dashboard
Dashboard → + → Crea nuova dashboard, poi Aggiungi nuovo widget:
| Widget | Raccolta | Chiave |
|---|---|---|
| un serbatoio che si svuota | Liquid level | livello |
| la tabella degli allarmi | Alarm widgets → Alarms table | — |
| il terminale dei comandi | Control widgets → RPC debug terminal | — |
Nel terminale scrivete riempi e premete Invio: la risposta {"pompa": "accesa"} compare sotto, il livello risale e l'allarme si chiude.
Il vostro dispositivo
Scegliete uno di questi scenari (o inventatene uno, chiedendo al docente):
| Dispositivo | Cosa manda | Allarme | Comando |
|---|---|---|---|
| Serra | temperatura, umidità del terreno | terreno sotto il 30 % | irriga |
| Frigorifero di una farmacia | temperatura interna, porta aperta/chiusa | temperatura sopra 8 °C | chiudi |
| Parcheggio | posti liberi | posti liberi = 0 | apri (si libera un posto) |
| Cassonetto smart | riempimento % | sopra il 90 % | svuotato |
| Pannello solare | potenza prodotta, batteria % | batteria sotto il 10 % | risparmio |
| Aula | numero di persone, CO₂ | CO₂ sopra 1000 ppm | apri finestre |
Prima di scrivere codice, rispondete a tre domande (sono anche nella scheda di consegna):
- Che cosa cambia nel tempo? Quello è lo stato (nella cisterna: i litri).
- Come cambia da solo? Quella è la riga nel ciclo (nella cisterna: si consuma acqua).
- Che cosa fa il comando? Quello è il ramo
ifnella funzionecomando.
Se qualcosa non va
| Problema | Causa | Soluzione |
|---|---|---|
Prima incollate in TOKEN… |
il token non c'è | Passo 1 |
Not authorized |
token sbagliato | ricopiatelo con Copia token di accesso |
| Il widget mostra N/A | il widget legge una chiave che il dispositivo non manda | modificate il widget e scegliete la chiave giusta dall'elenco |
| L'allarme non scatta mai | la soglia non viene mai raggiunta, o la chiave è scritta diversa | controllate in Ultima telemetria il nome esatto della chiave e i valori |
| Il comando non fa niente | nello script il nome del comando è diverso da quello scritto nel terminale | stesso nome, stesse maiuscole e minuscole |
NameError dentro comando |
manca global |
global nome_variabile all'inizio della funzione |
Missioni
⭐ Missione 1 – La cisterna più grande
Fate una cisterna da 5000 litri che si svuota più in fretta (tra 50 e 150 litri a giro). Sono due righe in alto nello script. Poi impostate l'allarme sotto il 20 % e fatelo scattare.
⭐ Missione 2 – Il mio dispositivo, versione base
Scegliete il vostro scenario dalla tabella. Fate una copia di cisterna_tb.py con un nome nuovo (es. serra_tb.py), create in ThingsBoard un dispositivo con quel nome e incollate il suo token. Poi cambiate nomi e numeri: lo stato, le costanti in alto e le chiavi del dizionario misura. Per ora tenete il comando com'è. Verificate in Ultima telemetria che arrivino i vostri dati.
⭐⭐ Missione 3 – Dashboard e allarme
Per il vostro dispositivo create una dashboard con almeno due widget adatti ai dati (un indicatore e un grafico, oppure un «Liquid level») e una regola di allarme con la soglia della tabella. Fate scattare l'allarme e fate uno screenshot della dashboard.
Aiutino
Se l'allarme ci mette troppo a scattare, nello script fate partire lo stato già vicino alla soglia (es. umidità del terreno al 40 % invece che al 70 %), oppure aumentate di quanto cambia a ogni giro.
⭐⭐⭐ Missione 4 – Il comando
Cambiate la funzione comando perché il vostro dispositivo risponda al suo comando (es. irriga) e faccia la cosa giusta sullo stato: la serra bagna il terreno, il parcheggio libera un posto, il cassonetto torna vuoto. Provatelo con il widget RPC debug terminal: l'allarme si deve chiudere da solo.
Aiutino
Nella cisterna il comando accende la pompa e il ciclo, a ogni giro, aggiunge litri finché è piena. Per la serra potete fare uguale: irrigazione = True nel comando, e nel ciclo if irrigazione: terreno = terreno + 10 finché arriva all'80 %. Se il comando ha un effetto immediato (il cassonetto svuotato), basta cambiare lo stato direttamente nel comando: riempimento = 0. Ricordate global per ogni variabile che il comando cambia.
⭐⭐⭐ Missione 5 – La consegna
Compilate SCHEDA-CONSEGNA.txt e consegnate: lo script, lo screenshot della dashboard con l'allarme scattato e la scheda. Nella scheda spiegate perché avete scelto quella soglia: è la parte che fa la differenza tra un esercizio e un progetto.