Vai al contenuto

Progetta il tuo dispositivo IoT

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

  1. In ThingsBoard: Entità → Dispositivi → +, nome cisterna, poi Copia token di accesso.
  2. In cisterna_tb.py incollate il token nella riga TOKEN (solo quella: SERVER è già giusto).
  3. python cisterna_tb.py: deve scrivere Cisterna 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):

  1. Che cosa cambia nel tempo? Quello è lo stato (nella cisterna: i litri).
  2. Come cambia da solo? Quella è la riga nel ciclo (nella cisterna: si consuma acqua).
  3. Che cosa fa il comando? Quello è il ramo if nella funzione comando.

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.