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:

// SENSOR NODE CYCLE — ESP32 + ESP-NOW
Sensing
PIR + ADC lux
Build msg
snprintf()
WiFi STA
ESP-NOW init
TX
esp_now_send()
Idle
WiFi OFF
Deep Sleep
timer wake-up
// Payload format: MOTION_DETECTED-LUMINOSITY:ZZZ oppure MOTION_NOT_DETECTED-LUMINOSITY:ZZZ

Il 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 medioP mediaE per ciclo
Sensing474 μs350.86 mW0.21 mJ
Building174 μs350.86 mW≈ 0 mJ
WiFi setup216 597 μs633.00 mW137.11 mJ
Transmission (19.5 dBm)187 μs687.93 mW0.13 mJ
Idle (WiFi OFF)8 539 μs249.54 mW2.13 mJ
Deep Sleep1 s45.64 mW45.64 mJ
Totale1.23 s≈ 0.19 J
Lifetime estimation

$$ 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 soglia LUX_THRESHOLD = 100: se nulla cambia, si rientra immediatamente in Deep Sleep senza accendere la radio.
  • Riduzione di potenza TXWiFi.setTxPower(WIFI_POWER_2dBm) al posto di 19.5 dBm: sufficiente per la portata smart-home target.
  • Wake-up esterno su PIResp_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.

ProtocolloScenarioEnergia totale/oraNote
CoAP CON PUT30 cicli/h≈ 2.20 mJSolo PUT + ACK
MQTT QoS 130 cicli/h≈ 9.06 mJCONNECT+CONNACK+PUBLISH+PUBACK+DISCONNECT su entrambi i lati
MQTT retained + clean_sessionAtt. wake-up 30 min≈ 0.36 mJBest 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:

#DomandaFiltro chiaveRisposta
1NON DELETE riusciti su coap.mecoap.type==1 && coap.code==41 (MID 30800)
2Risorse con #POST = #PUT > 0coap.type==0 && code∈{2,3}1 — /dining_room
3Notifiche Observe uniche / inutilicoap.opt.observe && token==89:b911 / 0
4MQTT-SN broker → client su :1885udp.srcport==18850 pacchetti
5LWT consegnati via wildcardCross-match Will Topic ↔ SUBSCRIBE +/#1 (university/#)
6a/bClient che svuotano retain su HiveMQmqtt.retain==1 && mqtt.len==03 / 1
7SUBSCRIBE locali con ≥ 2 wildcardmqtt.msgtype==8 && ipv6.dst==::19
8PUBLISH a broker localemqtt.msgtype==3 && (ip|ipv6).dst==localhostA: 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:

PSR constraint & airtime budget

$$ \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.

// NODE-RED FLOW — 7 BLOCCHI
Init
load CSV · O(1) index
ID Gen 1 Hz
MQTT pub :1884
Sub + lookup
N = id % 5218
Switch
ZCL · LinkSt · else
ZCL publish
10 msg/min
+
ZCL filter
P · I · V → CSV + chart
+
LinkStatus
linkmap[src][dst]
Flush + ThingSpeak
min(src) hex · 1 / 20 s
// Stop condition: counter rcv == 200 → sets flow.stop = true e attiva il flush pipeline
  • ID generator — emette ogni secondo un ID random in [0, 30000] con timestamp UNIX; pubblica su challenge3/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 su filtered_elems.csv e li alimenta su due ui_chart della dashboard.
  • Ramo Link Status — aggiorna linkmap[src][dst] = cost nel flow context; al flush, serializza la mappa su outgoing_cost.csv.
  • ThingSpeak upload — selezione del source ZigBee con valore esadecimale minimo, sort dei destinatari, rate-limit 1 msg / 20 s (tier free), HTTP GET con field1 = cost verso 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}\).

BranchProbabilitàPayloadOutput rate \(r\)Slot \(=\!L\) da 128 B
0 picchi\(e^{-\lambda}=0.6703\)512 B409.6 bps4
1 picco\(\lambda e^{-\lambda}=0.2681\)2048 B1638.4 bps16
≥ 2 picchi0.06164096 B3276.8 bps32
CFP slot budget

$$ 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:

RegimeTriggerAdvertising rate
Deep idle / heartbeatnessun movimento1 beacon / 30 min
Motion eventaccelerometro wake-on-motion1 beacon / 10 s · finestra 2 min
Searchtrigger 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\}\).

Costo di risoluzione per N = 4

$$ 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\)123456
\(E[\text{slot}]\)9.8249.5998.9548.8249.0389.466
\(\eta = 4/E\)0.4070.4170.4470.453 ★0.4430.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.

Challenge 1 — ESP32 Sensor Node
Sensor node ESP32 + PIR + lux con ESP-NOW: requisiti, codice originale, profilo energetico per fase, stima lifetime su batteria virtuale 16 605 J, ottimizzazioni event-driven + 2 dBm + wake-up esterno.
Apri PDF
// PDF · 32 pp · Wokwi + Arduino C++
Challenge 2 — Exercise (energy)
Calcolo dei costi energetici CoAP CON PUT vs MQTT QoS 1 su orizzonte 1 h; analisi long-term con retain + clean_session per ridurre il consumo dell'attuatore.
Apri PDF
// PDF · 6 pp
Challenge 2 — PCAP analysis
Otto domande forensi su due catture (A.pcapng, B.pcapng) con CoAP, MQTT e MQTT-SN: filtri Wireshark, analisi LWT via wildcard, retain erase, depth dei topic, parsing MQTT v5.
Apri PDF
// PDF · 30 pp · Wireshark
Challenge 3 — Exercise (LoRaWAN)
Pure ALOHA, vincolo PSR ≥ 75 % su 40 nodi @ 868 MHz / 125 kHz: derivazione di SF8 come massimo SF ammissibile e analisi della correzione corretta (avvicinare i nodi al gateway).
Apri PDF
// PDF · 6 pp
Challenge 3 — Node-RED flow
Pipeline ZigBee → MQTT locale → ThingSpeak: ID generator 1 Hz, classificatore ZBEE_ZCL / LinkStatus, filtro Active Power / RMS V·I su dashboard, mappa link cost, upload remoto rate-limited.
Apri PDF
// PDF · 19 pp · Node-RED + Mosquitto
Node-RED export & output
Esportazione del flow Node-RED in JSON + i tre CSV generati da un run di riferimento (id_log, filtered_elems, outgoing_cost) e copia .txt del flow per ispezione rapida.
nodered.json
// + id_log.csv · filtered_elems.csv · outgoing_cost.csv
Homework — Exercise 1 (802.15.4)
PMF dell'output rate per un nodo ESP32 con payload Poisson-dipendente (λ = 0.4) e dimensionamento della Contention-Free Period in IEEE 802.15.4 beacon-enabled: 9 slot/nodo, 27 slot per 3 nodi.
Apri PDF
// PDF · CFP sizing
Homework — Exercise 2 (BLE)
Redesign event-driven e context-aware del beaconing BLE per la localizzazione indoor di protesi dentali: tre regimi (heartbeat / motion / search), accelerometro wake-on-motion < 1 µA e sensore di contatto opzionale.
Apri PDF
// PDF · low-power redesign
Homework — Exercise 3 (Frame ALOHA)
Efficienza del Dynamic Frame ALOHA con N = 4 tag: derivazione combinatoria di L₄ = 953/108, η(r₁) per r₁ ∈ {1..6} e dimostrazione dell'ottimo r₁ = N = 4 (η ≈ 0.453) con curva a campana asimmetrica.
Apri PDF
// PDF · RFID efficiency