01 Overview del corso
Il corso di Internet of Things (Prof. Matteo Cesana, 5 CFU) è strutturato attorno a una sequenza di challenge che attraversano l'intero stack applicativo IoT: dal nodo sensore embedded fino al livello applicativo (broker, dashboard, cloud). Ogni challenge unisce una parte di esercizio teorico e una parte progettuale.
- Challenge 1 — Progettazione di un sensor node ESP32 con sensore PIR + fotoresistore, comunicazione ESP-NOW verso un sink node, profilo energetico e stima della vita utile.
- Challenge 2 — Confronto energetico CoAP vs MQTT in scenari a basso ciclo di lavoro, e analisi forense del traffico IoT a partire da cattura PCAP (Wireshark) con CoAP, MQTT e MQTT-SN.
- Challenge 3 — Dimensionamento di una rete LoRaWAN con modello Pure ALOHA e implementazione in Node-RED di una pipeline di elaborazione di pacchetti ZigBee con upload su ThingSpeak.
- Homework — Tre esercizi analitici a complemento delle challenge: sizing della Contention-Free Period in IEEE 802.15.4 beacon-enabled, redesign event-driven del beaconing BLE per la localizzazione indoor, e analisi di efficienza del Dynamic Frame ALOHA.
Progetto svolto in coppia con Bruno Arena. Tutti i deliverable elencati di seguito sono pubblicati nei rispettivi PDF scaricabili dalla sezione finale.
02 Challenge 1 — ESP32 Smart-Home Sensor Node
L'obiettivo è progettare un nodo IoT per smart home che rileva presenza umana e luminosità ambientale, trasmette l'informazione a un sink centrale e ottimizza il consumo energetico tramite Deep Sleep. La piattaforma di sviluppo è ESP32 DevKit V1 simulato su Wokwi; la comunicazione avviene in ESP-NOW broadcast verso l'indirizzo MAC FF:FF:FF:FF:FF:FF.
2.1 Hardware & ciclo operativo
Il nodo esegue un ciclo completo di sensing → trasmissione → deep sleep contenuto interamente nella setup(); loop() non viene mai eseguita. Le cinque fasi sono:
MOTION_DETECTED-LUMINOSITY:ZZZ oppure MOTION_NOT_DETECTED-LUMINOSITY:ZZZIl PIR è collegato al GPIO 27 in modalità input digitale; il fotoresistore al GPIO 34 letto come ADC a 12 bit. La durata del Deep Sleep, fissata dalla specifica della challenge, è di 1 s.
2.2 Profilo energetico & stima della vita utile
I tempi di esecuzione di ciascuna fase sono stati misurati a runtime tramite micros(); i valori medi di potenza sono ricavati dai CSV di consumo forniti per la challenge.
| Fase | Δt medio | P media | E per ciclo |
|---|---|---|---|
| Sensing | 474 μs | 350.86 mW | 0.21 mJ |
| Building | 174 μs | 350.86 mW | ≈ 0 mJ |
| WiFi setup | 216 597 μs | 633.00 mW | 137.11 mJ |
| Transmission (19.5 dBm) | 187 μs | 687.93 mW | 0.13 mJ |
| Idle (WiFi OFF) | 8 539 μs | 249.54 mW | 2.13 mJ |
| Deep Sleep | 1 s | 45.64 mW | 45.64 mJ |
| Totale | 1.23 s | — | ≈ 0.19 J |
$$ T_{life} = \frac{Y_{batt}}{E_{cycle}} \cdot \frac{T_{cycle}}{3600\,\text{s/h}} = \frac{16\,605}{0.19} \cdot \frac{1.23}{3600} \approx 30.53\,\text{h} $$
L'energia della batteria virtuale assegnata dalla challenge è \(Y_{batt} = 16\,605\,\text{J}\). Il bottleneck dominante è la fase di WiFi setup (137 mJ ≈ 72 % dell'energia totale), seguita dal Deep Sleep stesso a causa della sua durata.
2.3 Optimization
Sul baseline sono state introdotte tre ottimizzazioni mirate ai contributori principali del consumo energetico, mantenendo il payload e la semantica applicativa invariati:
- Trasmissione event-driven — variabili
RTC_DATA_ATTR(lastLux,lastMotion) confrontate contro una sogliaLUX_THRESHOLD = 100: se nulla cambia, si rientra immediatamente in Deep Sleep senza accendere la radio. - Riduzione di potenza TX —
WiFi.setTxPower(WIFI_POWER_2dBm)al posto di 19.5 dBm: sufficiente per la portata smart-home target. - Wake-up esterno su PIR —
esp_sleep_enable_ext0_wakeup(GPIO 27, HIGH): la presenza umana risveglia il nodo all'istante, eliminando il polling periodico per il solo PIR.
L'effetto combinato sposta il nodo da un ciclo strettamente periodico ad alto costo (re-init Wi-Fi ad ogni risveglio) a un comportamento adattivo: la radio si accende solo quando c'è una variazione reale, e la transizione tra acquisizione e riposo avviene attraverso una funzione dedicata entraInDeepSleep() che configura sia il timer sia il trigger esterno.
03 Challenge 2 — CoAP / MQTT: energia & traffic analysis
3.1 Exercise — energia di comunicazione CoAP vs MQTT
Smart-building con un sensore di temperatura a batteria (TX ogni 2 min), un attuatore fan-controller a batteria e un gateway MQTT alimentato da rete. Costi energetici: \(E_{TX} = 50\,\text{nJ/bit}\), \(E_{RX} = 58\,\text{nJ/bit}\); canale ideale. Le connessioni MQTT vengono chiuse durante lo sleep e la disconnessione non è acknowledgeata.
| Protocollo | Scenario | Energia totale/ora | Note |
|---|---|---|---|
| CoAP CON PUT | 30 cicli/h | ≈ 2.20 mJ | Solo PUT + ACK |
| MQTT QoS 1 | 30 cicli/h | ≈ 9.06 mJ | CONNECT+CONNACK+PUBLISH+PUBACK+DISCONNECT su entrambi i lati |
| MQTT retained + clean_session | Att. wake-up 30 min | ≈ 0.36 mJ | Best per long-term operation |
Quando l'attuatore dorme la maggior parte del tempo (wake-up ogni 30 min) la scelta migliore è MQTT con retain=True sul sensore e clean_session=True sull'attuatore: il broker conserva l'ultima misura e la consegna immediatamente al subscriber al wake-up successivo, senza dover attendere la prossima trasmissione del sensore. CoAP Observe, invece, perde tutte le notifiche inviate durante lo sleep e dopo 3 retransmission MAX deregistra l'observer — costo netto superiore.
3.2 Challenge — PCAP analysis (Wireshark)
Otto domande forensi su due catture (A.pcapng, B.pcapng) contenenti traffico CoAP, MQTT (locale + HiveMQ + Mosquitto) e MQTT-SN. Filtri e risultati principali:
| # | Domanda | Filtro chiave | Risposta |
|---|---|---|---|
| 1 | NON DELETE riusciti su coap.me | coap.type==1 && coap.code==4 | 1 (MID 30800) |
| 2 | Risorse con #POST = #PUT > 0 | coap.type==0 && code∈{2,3} | 1 — /dining_room |
| 3 | Notifiche Observe uniche / inutili | coap.opt.observe && token==89:b9 | 11 / 0 |
| 4 | MQTT-SN broker → client su :1885 | udp.srcport==1885 | 0 pacchetti |
| 5 | LWT consegnati via wildcard | Cross-match Will Topic ↔ SUBSCRIBE +/# | 1 (university/#) |
| 6a/b | Client che svuotano retain su HiveMQ | mqtt.retain==1 && mqtt.len==0 | 3 / 1 |
| 7 | SUBSCRIBE locali con ≥ 2 wildcard | mqtt.msgtype==8 && ipv6.dst==::1 | 9 |
| 8 | PUBLISH a broker locale | mqtt.msgtype==3 && (ip|ipv6).dst==localhost | A: 706 · B: 1269 |
Le risposte alle Q5 e Q7 dipendono criticamente dal parsing corretto di MQTT v5: i frame dei client lstvfjwz e zmjnxudohrkaegmh contengono un campo Properties Length (1 byte 0x00) assente in v3.1.1. Un parser non version-aware lo assorbe nella lunghezza successiva, perdendo silenziosamente il Will Topic e i filtri university/#, house/department3/+/+, house/+/room1/#.
04 Challenge 3 — LoRaWAN & Node-RED pipeline
4.1 Exercise — dimensionamento LoRa con Pure ALOHA
Rete a singola cella con \(N = 40\) end-device, payload 20 B, banda 868 MHz e bandwidth canale \(B = 125\,\text{kHz}\). Ogni nodo trasmette secondo un processo di Poisson di intensità \(\lambda = 2\,\text{pkt/min}\). L'accesso al canale è non-coordinato → modello Pure ALOHA:
$$ \text{PSR} = e^{-2G} \geq 0.75 \quad\Longleftrightarrow\quad T_{air} \leq \frac{-\ln(0.75)}{2 N \lambda} = \frac{0.2877}{2 \cdot 40 \cdot 1/30} \approx 108\,\text{ms} $$
Airtime LoRa con CR = 4/5, header esplicito, CRC, preamble 8 sym (formula Semtech standard): SF7 ≈ 57 ms ✓ · SF8 ≈ 103 ms ✓ · SF9 ≈ 185 ms ✗ · SF10–12 ≫ 108 ms ✗. Verifica numerica: SF8 → G ≈ 0.137, PSR ≈ 76 % (just-above); SF9 → G ≈ 0.247, PSR ≈ 61 % (fail).
La risposta è SF8. Sul deployment reale, performance basse e disuniformi tra nodi indicano un problema di link budget, non di congestione (i collisioni colpiscono tutti uniformemente). Azione correttiva: avvicinare i nodi al gateway (path-loss reduction), non ridurre N né aumentare SF — quest'ultimo raddoppierebbe l'airtime peggiorando l'offered load G.
4.2 Challenge — Node-RED pipeline ZigBee → MQTT → ThingSpeak
Flow Node-RED che elabora in tempo reale i pacchetti ZigBee di un dataset CSV. Il flow è strutturato in sette blocchi funzionali; il broker Mosquitto locale è esposto sulla porta 1884. La pipeline si arresta automaticamente dopo 200 messaggi processati, momento in cui esegue il flush e l'upload remoto.
- ID generator — emette ogni secondo un ID random in
[0, 30000]con timestamp UNIX; pubblica suchallenge3/id_generator(QoS 0) e logga su/data/id_log.csv. - Subscriber + classificazione — recupera la riga del dataset con
N = id % 5218; uno switch instrada su tre rami in base al Command String: ZBEE_ZCL, Link Status (0x08), altrimenti scarta. - Ramo ZCL — costruisce un JSON {timestamp, id, wpan/zbee src/dst, topic
ZigBee/<DeviceName>, payload} e ripubblica con rate-limit 10 msg/min; in parallelo, filtra gli attributi Active Power, RMS Current, RMS Voltage, li salva sufiltered_elems.csve li alimenta su dueui_chartdella dashboard. - Ramo Link Status — aggiorna
linkmap[src][dst] = costnel flow context; al flush, serializza la mappa suoutgoing_cost.csv. - ThingSpeak upload — selezione del source ZigBee con valore esadecimale minimo, sort dei destinatari, rate-limit 1 msg / 20 s (tier free),
HTTP GETconfield1 = costverso il channel 3366185.
Il flow esportato (nodered.json) e i file di output prodotti durante un run di riferimento (id_log.csv, filtered_elems.csv, outgoing_cost.csv) sono inclusi nei file scaricabili.
05 Homework — tre esercizi analitici
L'homework raccoglie tre esercizi indipendenti che estendono i temi delle challenge sul piano analitico: dimensionamento di una rete IEEE 802.15.4, progettazione energetica di un sistema BLE e teoria dell'accesso al canale per RFID. Tutti e tre i PDF sono scaricabili dalla sezione finale.
5.1 Exercise 1 — sizing della CFP in IEEE 802.15.4
Un nodo ESP32 produce un payload la cui dimensione dipende dal numero di picchi di vibrazione rilevati in una finestra di 10 s. Il conteggio dei picchi è una variabile di Poisson con \(\lambda = 0.4\) picchi/finestra. Il sistema opera in 802.15.4 beacon-enabled, sola CFP (contention-free): \(R = 250\,\text{kbps}\), pacchetto \(L = 128\,\text{B}\), periodo di reporting \(T_{rep} = 10\,\text{s}\).
| Branch | Probabilità | Payload | Output rate \(r\) | Slot \(=\!L\) da 128 B |
|---|---|---|---|---|
| 0 picchi | \(e^{-\lambda}=0.6703\) | 512 B | 409.6 bps | 4 |
| 1 picco | \(\lambda e^{-\lambda}=0.2681\) | 2048 B | 1638.4 bps | 16 |
| ≥ 2 picchi | 0.0616 | 4096 B | 3276.8 bps | 32 |
$$ E[s] = 0.6703\cdot 4 + 0.2681\cdot 16 + 0.0616\cdot 32 = 8.94\ \tfrac{\text{slot}}{\text{nodo}} \;\to\; 9\ \tfrac{\text{slot}}{\text{nodo}} $$
Dimensionando la CFP «secondo il carico atteso» e arrotondando per eccesso a 9 slot/nodo, con 3 nodi di monitoraggio servono \(N_{slot} = 9 \times 3 = 27\) slot garantiti per superframe.
5.2 Exercise 2 — redesign del beaconing BLE (localizzazione protesi)
Sistema di localizzazione indoor di protesi dentali in una casa di riposo: beacon BLE annegati in ogni dentiera, gateway che stimano la posizione via RSSI. Il firmware originale trasmette un advertising ogni 10 s incondizionatamente, sprecando energia anche quando la localizzazione non serve. La proposta rende il beaconing event-driven e context-aware, con tre regimi operativi:
| Regime | Trigger | Advertising rate |
|---|---|---|
| Deep idle / heartbeat | nessun movimento | 1 beacon / 30 min |
| Motion event | accelerometro wake-on-motion | 1 beacon / 10 s · finestra 2 min |
| Search | trigger del gateway (scan 10 ms / 30 s) | 1 beacon / s · fino a 5 min |
Il movimento non è interpretato come «protesi smarrita» ma come trigger a basso consumo per risvegliare il SoC e scegliere il regime corretto. Si aggiunge un accelerometro MEMS a 3 assi con wake-on-motion hardware (< 1 µA in monitoraggio); un sensore opzionale di temperatura/contatto distingue «in uso» (caldo) da «non in uso» (freddo), evitando beaconing inutile sulle dentiere effettivamente indossate.
5.3 Exercise 3 — efficienza del Dynamic Frame ALOHA (RFID)
Sistema RFID con \(N = 4\) tag e Dynamic Frame ALOHA: ogni tag sceglie uniformemente uno slot in un frame di dimensione \(r\); gli slot con un solo tag sono successi, quelli con ≥ 2 tag sono collisioni e formano il backlog risolto nei frame successivi (dimensionati pari al backlog). Si valuta l'efficienza \(\eta = N / E[\text{slot totali}]\) al variare della frame size iniziale \(r_1 \in \{1,\dots,6\}\).
$$ L_4 = 4 + \tfrac{9}{16}L_2 + \tfrac{3}{16}L_3 + \tfrac{5}{32}L_4 \;\Longrightarrow\; L_4 = \tfrac{953}{108} \approx 8.824\ \text{slot} $$
con \(L_2 = 4\) e \(L_3 = 51/8\) dati. La distribuzione del backlog dopo il primo frame di dimensione \(r=4\) (256 esiti) è ricavata per analisi combinatoria delle partizioni: \(P(0)=3/32\), \(P(2)=9/16\), \(P(3)=3/16\), \(P(4)=5/32\).
| \(r_1\) | 1 | 2 | 3 | 4 | 5 | 6 |
|---|---|---|---|---|---|---|
| \(E[\text{slot}]\) | 9.824 | 9.599 | 8.954 | 8.824 | 9.038 | 9.466 |
| \(\eta = 4/E\) | 0.407 | 0.417 | 0.447 | 0.453 ★ | 0.443 | 0.423 |
L'efficienza massima \(\eta \approx 0.453\) si ottiene a \(r_1 = N = 4\), coerentemente con la condizione di ottimalità del Frame Slotted ALOHA (frame iniziale = numero di tag). La curva ha la classica forma a campana: \(r_1 < N\) genera cascate di collisioni (degrado ripido), \(r_1 > N\) spreca slot vuoti (degrado additivo, più dolce) — il decadimento è quindi asimmetrico.
06 File di progetto
Tutti i deliverable in PDF, i file di output del flow Node-RED e i tre esercizi dell'homework sono raccolti qui sotto. I PDF degli esercizi (Challenge 2 & 3) sono inclusi accanto alle relative challenge.