Tohum

Playbook end-to-end per lo sviluppo di prodotti IoT – Fase 4: Sviluppo firmware e software cloud

Illustrazione: un microcontrollore che scambia dati con una nuvola con ingranaggi

La Fase 4 porta il prodotto sul lato destro della V: implementazione e testing. Nel classico V-model dello sviluppo prodotto, il lato sinistro riguarda il design (che abbiamo affrontato nelle fasi precedenti); qui sviluppiamo il firmware (il software che gira sul vostro dispositivo IoT) e il software cloud (server, database, API, interfacce utente) che insieme danno vita al vostro prodotto. A differenza dello sviluppo hardware, che è relativamente rigido una volta stampato su un circuito, lo sviluppo software è altamente iterativo e agile. In effetti, potreste aver già scritto pezzi di firmware nella Fase 3 mentre testavate il vostro design hardware. Ora è il momento di accelerare. Il software è come l’argilla – facile da modellare e cambiare al volo – quindi possiamo iterare velocemente e rifinire continuamente. Questa fase correrà in parallelo con qualsiasi lavoro hardware rimanente, ciascuno che informa l’altro secondo necessità.

La Fase 4 può essere pensata in due parti principali: prima pianificare l’architettura e il design del software (per porre una base solida), e poi un ciclo continuo di sprint di sviluppo in cui costruiamo, testiamo e ripetiamo finché il software è solido. Scomponiamola.

4.1 Architettura e design del software

Prima che qualcuno si butti nel codice, è saggio abbozzare un piano di gioco per l’architettura software – sia per il firmware sul dispositivo sia per i componenti cloud. Pensatelo come pianificare un viaggio in auto. Non partireste alla guida senza una mappa; allo stesso modo, abbozziamo i componenti principali e come interagiscono prima di scrivere migliaia di righe di codice. È analogo all’architettura di sistema fatta nella Fase 2, ma ora a livello di codice. Una buona pianificazione dell’architettura software vi aiuta a evitare lo “spaghetti code” più avanti. Ecco le cose chiave da stabilire in questa fase:

Architettura firmware (approccio a strati e considerazioni sull’RTOS)

Definite la struttura del vostro codice embedded organizzandola a strati. Un approccio comune nel firmware è un’architettura a strati, che aiuta a organizzare il codice logicamente e lo rende più facile da mantenere. Per esempio, al livello più basso potreste avere un Board Support Package (BSP) o un hardware abstraction layer – questo contiene i driver di basso livello specifici per il vostro microcontrollore e le periferiche hardware. Sopra quello, potreste avere device driver o moduli per i vostri sensori e attuatori (che gestiscono cose come leggere un sensore di temperatura o pilotare un LED). Sopra i driver, potreste avere uno strato di servizi – che potrebbe implementare protocolli di comunicazione, algoritmi di elaborazione dati o altra logica che processa i dati grezzi. Infine, in cima, avete lo strato applicativo, che è il cervello del vostro dispositivo: lega tutto insieme per soddisfare lo scopo del vostro prodotto (per esempio leggere i valori dei sensori, prendere decisioni, inviare dati al cloud e reagire ai comandi). Considerate di disegnare un diagramma semplice (diagramma dei componenti UML o anche solo scatole e frecce su una lavagna) per visualizzare questi strati. Aiuta tutti nel team a capire la separazione delle responsabilità – come costruire una casa con fondamenta, telaio e tetto, ogni strato ha il suo compito.

Un’altra grande decisione architetturale sul lato firmware è se usare un RTOS (Real-Time Operating System) o no. Un RTOS è come avere un piccolo sistema operativo sul vostro dispositivo che può eseguire più task apparentemente contemporaneamente (tramite scheduling). Se il vostro dispositivo fa una sola cosa alla volta, potreste non aver bisogno di un RTOS. Ma la maggior parte dei gadget IoT ha molte cose in corso contemporaneamente – per esempio, leggere sensori periodicamente, ascoltare messaggi in arrivo (dal cloud o dall’input utente), aggiornare un display o un LED e gestire la comunicazione di rete. Con un RTOS, potete progettare il vostro firmware come un insieme di task o thread. Per esempio, potreste avere un task dedicato al campionamento dei sensori, un altro dedicato alla gestione delle comunicazioni (inviare dati o ricevere comandi) e un altro per l’housekeeping o gli aggiornamenti dell’interfaccia utente. Come parte del design dell’architettura, delineate questi task e come comunicheranno tra loro – i meccanismi comuni includono message queue, event flag o dati condivisi protetti da mutex (per evitare conflitti). Deciderlo presto aiuterà a garantire che il vostro firmware non diventi un groviglio di codice intrecciato dove tutto dipende da tutto il resto (alias spaghetti code). Invece, avrete interfacce chiare: magari il task dei sensori mette le nuove letture in una coda, e il task di comunicazione le raccoglie per inviarle al cloud. Se progettate queste interfacce chiaramente ora, la scrittura del codice sarà molto più fluida e vi ringrazierete dopo quando sarà il momento di risolvere problemi o estendere il firmware.

Perché tutta questa pignoleria architetturale per il firmware? Perché vi semplifica la vita lungo la strada. Con un buon design a strati e una buona struttura dei task, potete modificare una parte (per dire, sostituire il driver di un sensore o ritoccare un algoritmo) senza rompere tutto il resto. Rende anche più facile per più sviluppatori lavorare insieme senza pestarsi i piedi – una persona può sviluppare il driver del sensore mentre un’altra lavora sul codice di comunicazione cloud, purché abbiate concordato come quelle parti si interfacciano (per esempio il formato dati di un messaggio di lettura del sensore). In breve, un po’ di pianificazione qui evita molto dolore dopo.

Architettura cloud (struttura di server, database e applicazione)

Ora consideriamo il lato cloud. Il dispositivo IoT non vive in isolamento – di solito parla con qualche servizio cloud o server. Un ragionamento architetturale simile è necessario qui: abbozzate i componenti principali del vostro software cloud. Per esempio, potreste avere un API server che riceve dati dai dispositivi e serve anche le richieste dalle applicazioni utente. Ci sarà probabilmente un database dove archiviate i dati dei dispositivi (letture dei sensori, stati dei dispositivi, informazioni utente, ecc.). Ci possono essere servizi o microservizi aggiuntivi che fanno elaborazione dati o analytics – per esempio, analizzare i dati per anomalie, attivare alert o azioni quando certe condizioni sono soddisfatte (per esempio inviare un’email se una lettura di temperatura è troppo alta), o eseguire aggregazioni per i report. Oltre a questo, considerate le applicazioni client: forse una dashboard web o un’app mobile con cui gli utenti interagiscono. Quei client comunicheranno col server (spesso via API) per visualizzare i dati e inviare comandi utente (come un utente che preme un pulsante per accendere la sua lampadina IoT dal telefono).

È utile disegnare un diagramma di questo ecosistema – il C4 model (Client-Container-Component-Code) è un modo popolare di diagrammare l’architettura software. Disegnate almeno il livello Container: mostrate il dispositivo, il server, il database e l’app client, e come i dati fluiscono tra di loro. Per esempio, il vostro diagramma potrebbe avere una freccia da Device a Cloud API (“invia dati del sensore via HTTP/MQTT”), una freccia da Cloud a Database (“archivia i dati”) e una freccia da Client App a Cloud (“l’utente richiede dati o invia un comando”), e così via. Questa mappa visiva assicura che tutti (firmware developer, cloud engineer, frontend developer, ecc.) abbiano un modello mentale chiaro di come le cose si collegano. È molto più facile discutere e intercettare presto i problemi di design con un diagramma del genere in mano. Forse scoprite in questa fase che vi serve un componente aggiuntivo – per esempio, un message broker o un servizio di piattaforma IoT – per gestire lo scaling a molti dispositivi. Meglio pensarci ora che quando siete nel profondo del codice.

Un altro aspetto: pensate a scalabilità e affidabilità nel vostro design cloud. Se vi aspettate 100.000 dispositivi che si connettono alla fine, il database e il design del server scelti possono reggerlo? Se un server va giù, cosa succede (avete ridondanza)? Quei dettagli potrebbero essere approfonditi in una fase successiva, ma il design ad alto livello dovrebbe tenere conto fin da subito delle preoccupazioni principali come queste. Per esempio, potreste decidere di usare una piattaforma IoT cloud (AWS IoT, Azure IoT Hub, ecc.) invece di costruire tutto da zero, il che diventerebbe parte della vostra decisione di architettura.

Protocolli di comunicazione e formati dati (comunicazione dispositivo-cloud)

Un prodotto IoT comporta intrinsecamente comunicazione tra il dispositivo e il cloud. Parte del design software è decidere come parlano tra loro e in quale lingua (protocolli e formati dati). Ci sono alcune scelte di protocollo comuni nell’IoT: per esempio, il vostro dispositivo userà MQTT, HTTP/REST, CoAP, o magari qualcosa come WebSocket? Ognuno ha pro e contro. MQTT è un protocollo publish/subscribe leggero ottimo per l’IoT perché è efficiente e funziona bene su reti inaffidabili – molti sistemi IoT lo usano per la messaggistica dispositivo-cloud. HTTP/REST è molto comune e facile da testare (dato che potete colpirlo con strumenti web standard) ma potrebbe avere un po’ più di overhead per messaggio. CoAP è un protocollo IoT specializzato simile a un HTTP leggero, spesso usato in reti vincolate. La chiave è sceglierne uno che si adatti alle capacità del vostro dispositivo e alla vostra infrastruttura cloud. Per esempio, su un dispositivo fortemente vincolato (microcontrollore a 8-bit a batteria), MQTT o CoAP sono spesso una scelta migliore di HTTP. Se il vostro dispositivo è più capace (gira Linux o simile), HTTP/REST va bene ed è più semplice.

Una volta scelto il protocollo, progettate il formato dati per i messaggi. Molti progetti usano JSON perché è leggibile dall’uomo e facile da lavorare (la maggior parte dei linguaggi ha parser JSON). Per esempio, un messaggio di lettura della temperatura in JSON potrebbe assomigliare a:

{ "device_id": "ABC123", "timestamp": 1692124800, "temperature_c": 22.5 }

Questo espone chiaramente i campi. JSON è un po’ verboso, però, quindi se il vostro dispositivo ha bisogno di inviare dati molto efficientemente (pensate a migliaia di messaggi o banda molto bassa), potreste usare un formato binario (come Protocol Buffers, CBOR o uno schema binario custom) per risparmiare byte. La scelta di design qui influenzerà sia il firmware sia il cloud: entrambi i lati devono concordare su protocollo e formato. È un po’ come concordare una lingua prima di iniziare una conversazione – se il dispositivo “parla” MQTT+JSON e il server si aspetta qualcos’altro, non si capiranno.

Uno strumento utile in questa fase è disegnare un sequence diagram o semplicemente scrivere una sequenza di esempio di comunicazione. Per esempio: “Il dispositivo prende una lettura del sensore, formatta un payload JSON, lo invia via MQTT al topic X; il broker cloud lo riceve, il servizio cloud lo elabora e lo conferma, poi lo archivia nel database; dopo, un utente richiede i dati via app, la cloud API li recupera dal database e restituisce JSON all’app.” Scrivere questo flusso assicura che tutti (il firmware dev e il cloud dev, specialmente) siano letteralmente sulla stessa pagina su come i dati si muovono. Può prevenire malintesi come “Ah, pensavo usassimo il timestamp UNIX in secondi, non in millisecondi” o “Ma stavamo comprimendo i dati o no?”. Intercettateli ora nel design, vi risparmierà tempo di debug dopo.

Standard di codifica e strumenti del team (qualità fin dall’inizio)

Ora parliamo di come scriveremo il codice e collaboreremo, perché far seguire all’intero team pratiche comuni è essenziale. Concordate gli standard di codifica per ogni parte del sistema. Per lo sviluppo in C embedded, molti team adottano le linee guida MISRA-C – un insieme di regole per il linguaggio C mirate a evitare costrutti insicuri o soggetti a bug (nate dall’industria automotive, ma ampiamente utili in qualsiasi firmware critico). Se usate C++ nel firmware, potreste decidere quali funzionalità del C++ moderno sono permesse o vietate (per mantenere le cose sicure per i sistemi embedded – per esempio, magari evitando l’allocazione dinamica a runtime, ecc.). Per i linguaggi di più alto livello sul lato cloud come Python, probabilmente seguirete la style guide PEP 8, e per JavaScript/TypeScript magari userete un linter con lo stile Airbnb o qualsiasi standard. L’obiettivo non è essere pignoli, è assicurare coerenza e intercettare le trappole comuni. Quando tutti scrivono in uno stile coerente, la codebase sembra un unico prodotto anziché un patchwork, ed è più facile leggere e mantenere il codice scritto da qualcun altro nel team.

Oltre allo stile del codice, scegliete gli strumenti e l’infrastruttura che il team userà. Questo include version control e setup del repository (quasi certamente Git – se non lo state già usando, fatelo assolutamente, dato che è la spina dorsale del coding collaborativo). Decidete la strategia di branching: userete un semplice trunk-based development, o GitFlow con feature branch, ecc.? Inoltre, configurate un sistema di issue tracking se non l’avete fatto (come Jira, Trello, Asana o GitHub Issues) per gestire i task e le user story nei vostri sprint. È anche un buon momento per configurare la pipeline di Continuous Integration (CI): scegliete un servizio CI (GitHub Actions, GitLab CI, Jenkins, CircleCI, ecc.) e configuratelo per compilare e testare automaticamente il vostro codice ogni volta che vengono pushate modifiche. Parleremo di più della CI nella prossima sezione, ma la fase di architettura è dove vi assicurate che tutti gli sviluppatori abbiano accesso a questi strumenti e sappiano come usarli.

Un’altra parte della “cultura del codice” è decidere cose come le pratiche di code review – per esempio, ogni pull request richiederà l’approvazione di almeno un altro sviluppatore? Stabilire queste aspettative presto è salutare. Può sembrare formale, ma vi salva da scenari in cui il codice non revisionato di qualcuno introduce un bug cattivo o una falla di sicurezza che gli altri intercettano solo molto dopo. Concordando che “facciamo sempre code review, seguiamo la style guide ed eseguiamo i test prima del merge”, instillate una mentalità di qualità dal primo giorno. Lo sviluppo moderno riguarda la collaborazione e la disciplina del team tanto quanto il genio del codice.

Infine, ricordate che questa pianificazione della Fase 4.1 non riguarda il disegnare bei diagrammi che nessuno guarderà mai – riguarda il dare al team una comprensione condivisa e una roadmap tecnica. È come pianificare il percorso di un viaggio in auto e mettere in valigia una mappa: sapete dove sono le grandi svolte e come navigherete, ma potete comunque essere flessibili con soste o deviazioni. Dedicate una quantità ragionevole di tempo a questo design dell’architettura, ma non puntate alla perfezione né dettagliate ogni singola funzione. Volete design sufficiente perché le persone possano iniziare a programmare in modo coordinato. Con questa base posta, siete pronti a buttarvi nello sviluppo con fiducia.

4.2 Sprint di sviluppo – Iterare e conquistare

Con il progetto dell’architettura in mano, la costruzione comincia – scrivere codice, testarlo e guardare il vostro prodotto IoT fare davvero cose. Affrontiamo questo in iterazioni, tipicamente usando gli sprint come nella metodologia Agile. Uno sprint è un ciclo breve e a lunghezza fissa (comunemente 2 settimane, ma potrebbe essere 1 settimana o fino a 4 a seconda delle preferenze del team) in cui il team si impegna a consegnare un insieme di funzionalità o miglioramenti entro la fine. L’idea è scomporre l’enorme lavoro di “sviluppare tutto il software” in pezzi della dimensione di un boccone che possiamo affrontare costantemente e su cui fare progressi tangibili.

Questo approccio iterativo ha benefici chiari: forza l’integrazione continua dei componenti (così firmware e software cloud vengono testati insieme frequentemente) e vi dà molte opportunità di ottenere feedback e aggiustare la rotta se necessario. È molto meglio che programmare per 6 mesi e solo allora provare a vedere se tutto funziona (che è una ricetta per sorprese spiacevoli). Vediamo come potrebbe apparire un tipico ciclo di sprint per lo sviluppo del nostro prodotto IoT:

Sprint planning e user story

Ogni sprint inizia con una riunione di Sprint Planning. Il team guarda il product backlog – questa è la lista principale di tutte le funzionalità, correzioni e task da fare, derivata inizialmente dai requisiti raccolti nella Fase 1 (e ampliata man mano che abbiamo imparato di più nelle fasi successive). Nel planning, il team seleziona un insieme di user story o task dal backlog da affrontare nello sprint, in base alla priorità e alla capacità del team.

Una user story è una descrizione della dimensione di un boccone di una funzionalità dalla prospettiva dell’utente finale, spesso seguendo il template “Come <tipo di utente>, voglio <qualche capacità> così che <beneficio>.” Per esempio, una story potrebbe essere: “Come utente, voglio che il dispositivo IoT invii i dati di temperatura al cloud ogni 10 minuti, così da poter monitorare la temperatura della mia stanza da remoto.” Da questa story, il team capirà cosa deve essere fatto sia sul lato dispositivo sia su quello cloud: forse sul firmware del dispositivo dobbiamo leggere il sensore di temperatura e inviare un messaggio MQTT ogni 600 secondi; sul cloud, ci serve un’API o un message handler per ricevere quei dati e un posto dove archiviarli, e magari una semplice interfaccia per visualizzarli.

Durante lo sprint planning, il team discute ogni story scelta per fissare i criteri di accettazione – in sostanza, come sappiamo quando questa story è “fatta” e funzionante? Per l’esempio sopra, i criteri potrebbero essere: “Dato un dispositivo con un sensore di temperatura, quando funziona per 10 minuti, almeno una lettura di temperatura dovrebbe essere visibile nel database cloud e sulla dashboard utente.” Essere specifici aiuta il team a capire esattamente cosa costruire e come verificarlo. Questo è il momento di fare domande e chiarire i dettagli. È molto meglio appianare le ambiguità ora che scoprire dopo che firmware e cloud avevano assunzioni diverse (per esempio uno sviluppatore pensava fosse ogni 10 minuti quando attivo, un altro pensava sempre, ecc.). Una volta che il planning è fatto, tutti dovrebbero sapere di quale pezzo di lavoro sono responsabili nello sprint in arrivo.

Implementazione e Continuous Integration (CI)

Ora passiamo all’implementazione – scrivere il codice per dare vita a quelle user story. Gli sviluppatori inizieranno a programmare le funzionalità firmware e cloud identificate per lo sprint. Una best practice chiave qui è scrivere anche test automatizzati per ogni nuova funzionalità man mano che viene sviluppata. Per il firmware, questo potrebbe includere scrivere unit test per la logica che può girare su una macchina host (per esempio, se avete una funzione che fa il parsing di una lettura del sensore o una state machine per il dispositivo, spesso potete scrivere un test basato su PC per essa). Per il software cloud, di solito è più facile: potete scrivere unit test per le vostre funzioni e integration test per le vostre API.

Man mano che gli sviluppatori scrivono codice, lo caricano nel version control (Git). È qui che il nostro setup di Continuous Integration (CI) brilla. Con la CI, ogni volta che il codice viene pushato o una pull request viene aperta, una build automatizzata parte su un server CI. La CI compilerà il codice firmware (magari in configurazioni multiple se necessario), eseguirà gli unit test che avete e forse deployerà persino il firmware su un simulatore o dispositivo di test. Compilerà anche il software cloud, eseguirà tutti i suoi test, controllerà lo stile del codice (linter), eseguirà strumenti di static analysis e misurerà persino la code coverage (quanto codice è esercitato dai test). L’obiettivo è intercettare qualsiasi problema immediatamente. Se uno sviluppatore ha accidentalmente introdotto un bug che fa fallire un test, il sistema CI lo segnalerà subito – entro pochi minuti dalla scrittura del codice – così il team può risolverlo prima che diventi un problema più grande. Questo ciclo di feedback rapido è cruciale. È molto più facile correggere un bug introdotto un’ora fa (vi ricordate ancora cosa stavate facendo) che un bug del mese scorso (che nel frattempo si è intrecciato in molte altre modifiche).

Per esempio, immaginate che uno sviluppatore modifichi il codice di lettura del sensore e introduca accidentalmente un bug di overflow per valori molto alti. Se avete un unit test che spinge un valore del sensore all’estremo, la CI intercetterà quel fallimento e allerterà il team. Lo sviluppatore può quindi affrontarlo immediatamente. Senza quel test, il bug potrebbe annidarsi finché non arriva un’ondata di caldo, i sensori reali riportano temperature elevate e il dispositivo va in crash sul campo. CI e testing esistono per intercettare esattamente questi momenti – automaticamente, ed entro pochi minuti dalla scrittura del codice.

La Continuous Integration non riguarda solo i test; i team spesso integrano nella CI anche strumenti di static analysis. Gli strumenti di static analysis esaminano il vostro codice sorgente alla ricerca di potenziali bug, vulnerabilità di sicurezza o violazioni di stile senza eseguirlo. Per esempio, possono intercettare cose come “possibile dereferenziazione di null pointer” o “variabili inutilizzate” o altri code smell. Molti team embedded usano strumenti come PC-Lint o Cppcheck o clang-tidy per C/C++, e i team cloud usano linter (ESLint per JavaScript, Pylint per Python, ecc.) e magari security scanner. La CI può essere configurata per far fallire la build se questi strumenti trovano problemi seri. Questo stabilisce un’asticella di qualità – per esempio, “il codice firmware deve avere 0 nuovi warning e tutti i test devono passare perché un merge sia permesso.” Può suonare rigido, ma mantiene la codebase in salute.

Inoltre, la CI può tracciare la code coverage – quale percentuale del vostro codice è esercitata dai test automatizzati. Sebbene il 100% di coverage sia spesso irrealistico (e non tutto il codice è ugualmente facile da testare), molti team fissano un obiettivo (diciamo, almeno l’80% di coverage) e lo tracciano. Se un nuovo rilascio di codice fa improvvisamente scendere la coverage dall’80% al 50%, è un campanello d’allarme – forse i test sono stati saltati o è stato aggiunto molto codice non testato. Monitorando queste metriche sprint dopo sprint, mantenete alta la qualità del codice durante tutto lo sviluppo, invece di cercare di “imbullonarla” dopo.

In sintesi, l’implementazione in un contesto iterativo non è solo “scrivi un mucchio di codice.” È scrivi codice, integra continuamente quel codice in una codebase condivisa, testalo continuamente e risolvi i problemi man mano. Il risultato è un sistema software che è sempre in uno stato semi-funzionante, che migliora un po’ ogni giorno.

Build incrementali e demo di fine sprint

Alla fine di ogni sprint, puntate ad avere una build incrementale dell’intero sistema che sia un miglioramento tangibile rispetto alla precedente. Idealmente, è qualcosa che potreste effettivamente eseguire su un dispositivo e in un ambiente cloud di test per vedere le nuove funzionalità in azione. Un buon mantra è “sii sempre pronto a fare demo” alla fine dello sprint. Per il nostro esempio di prodotto IoT, magari alla fine dello Sprint 1 abbiamo un flusso end-to-end di base funzionante: il dispositivo legge un valore fittizio del sensore (forse un numero fisso o un valore di test casuale) e lo invia al cloud, il cloud lo riceve e lo archivia, e possiamo verificare manualmente che i dati sono passati (anche se è solo guardando direttamente il database o i log). È già una milestone significativa – dimostra che tutta la tubatura dal dispositivo al cloud funziona!

Entro lo Sprint 2, potremmo sostituire il valore fittizio con una lettura reale del sensore dall’hardware e configurare una semplice pagina web che mostra l’ultima lettura. Ora possiamo davvero vedere dati in tempo reale. Entro lo Sprint 3, forse aggiungiamo una funzionalità di alert: se la temperatura supera una soglia, il dispositivo o il cloud attiva un alert (magari un LED lampeggia o viene inviata un’email). E così via. A ogni sprint, sovrapponiamo funzionalità aggiuntive: magari uno sprint aggiunge il supporto per il controllo remoto (inviare comandi dal cloud al dispositivo), un altro aggiunge un bel grafico UI dei dati storici, ecc. Dopo un certo numero di sprint, avrete un sistema completo nelle funzionalità. Ma fondamentalmente, a ogni fase, avevate un sistema funzionante a cui stavate aggiungendo. Questo significa che se qualcosa va storto, sapete che probabilmente ha a che fare con le aggiunte più recenti, il che rende il debug più facile. Significa anche che gli stakeholder (come il vostro capo, o il product manager, o persino un utente di test) possono vedere i progressi e dare feedback lungo tutto il percorso.

Le demo regolari sono un’ottima pratica. Alla fine di ogni sprint (o anche più frequentemente, come un rapido show-and-tell ogni settimana), riunite il team e gli stakeholder interessati e dimostrate cosa c’è di nuovo. Potrebbe essere semplice come “Il dispositivo ora invia dati reali del sensore – guardate mentre scaldo il sensore con la mano, e vedete il numero su questa dashboard salire in tempo reale!” Queste demo creano un senso di realizzazione e slancio. Forzano anche la disciplina di integrazione: non potete mostrare qualcosa che non funziona davvero insieme, quindi spingono il team ad appianare i problemi di integrazione man mano, anziché rimandare l’integrazione alla fine. Guardare il prodotto prendere vita pezzo dopo pezzo, sprint dopo sprint, è molto più motivante che lavorare nel vuoto per mesi.

Testing e criteri della Definition of Done

Abbiamo parlato dei test automatizzati durante lo sviluppo, ma vale la pena enfatizzare come il testing sia intrecciato nella definizione stessa di progresso in ogni sprint. Una funzionalità o user story è considerata “fatta” solo quando soddisfa certi criteri oltre al semplice “il codice è stato scritto.” I team spesso hanno una checklist Definition of Done (DoD) che potrebbe includere elementi come:

È una lista lunga, e a prima vista può sembrare un sacco di caselle da spuntare. Ma queste pratiche vi risparmiano un sacco di problemi dopo. È molto meglio intercettare e correggere un bug o un difetto di design ora, durante lo sprint corrente, che lasciarlo passare di soppiatto fino ai test finali o alla produzione. La Definition of Done significa fondamentalmente “questa funzionalità non è mezza cotta; è davvero integrata, testata e pronta.” Quando ogni parte del vostro sistema soddisfa questi criteri man mano, la fase finale di integrazione e testing (Fase 5) riserva poche sorprese – avete verificato per tutto il percorso.

Documentazione continua (tenere le mappe aggiornate)

Probabilmente avete creato alcuni diagrammi e documenti nella fase di architettura (Fase 4.1). Ora, man mano che lo sviluppo procede, è importante mantenere quei documenti di design aggiornati. Il software ha un modo curioso di evolvere – magari scoprite il bisogno di un nuovo task nell’RTOS per una funzione di watchdog, o l’architettura cloud cambia per aggiungere un livello di caching per le prestazioni. Se non aggiornate la documentazione dell’architettura, questa si allontanerà dalla realtà e alla fine nessuno si fiderà più di essa. Per evitare il classico problema della documentazione obsoleta, trattate i documenti come una parte viva del progetto. Alcuni team scrivono persino i loro diagrammi come codice usando strumenti come PlantUML o Mermaid. In questo modo, il “sorgente” del diagramma vive nel repository insieme al codice. Quando viene fatta una modifica che tocca l’architettura, il team aggiorna il codice del diagramma e rigenera l’immagine.

Un’opzione pratica (se il vostro team è disposto) è integrare la documentazione anche nel processo di CI. Per esempio, potreste avere un controllo per cui se certi file core sono cambiati (come uno schema di messaggio o un’interfaccia di componente) e il file di documentazione corrispondente non è stato aggiornato, la CI ve lo ricorda o fa fallire la build. Dà una spintarella agli sviluppatori: “Ehi, hai cambiato come funziona il sistema – aggiorna il diagramma così tutti lo sanno.” Può sembrare eccessivo, ma mantenere la documentazione in sincronia ripaga quando un nuovo sviluppatore si unisce o quando tornate sul progetto tra sei mesi e cercate di ricordare come le cose sono organizzate. Anche se non lo automatizzate, almeno programmate una rapida revisione dei diagrammi ogni pochi sprint per assicurarvi che riflettano ancora la realtà.

Il punto chiave: la documentazione aggiornata vale lo sforzo. Significa che chiunque nel team (o fuori dal team, come un auditor di conformità o un partner) può guardare i documenti e fidarsi. Evitate situazioni come “Non leggere la wiki, è vecchia – Joe ha le informazioni vere in testa (o in qualche commento nel codice).” Invece, la conoscenza è catturata e aggiornata man mano, il che è il marchio di un team maturo e orientato alla qualità.

Demo regolari e cicli di feedback

Abbiamo toccato le demo nel contesto degli sprint, ma enfatizziamo il valore di stabilire una cadenza regolare di dimostrazione dei progressi. In Agile, questa è spesso la riunione di Sprint Review: alla fine dello sprint, il team mostra ciò che ha costruito agli stakeholder (product manager, altri team, a volte persino utenti reali o dirigenti). Per un progetto IoT, una demo può essere letterale – mostrare il dispositivo e il sistema che lavorano insieme. Per esempio, potreste proiettare la dashboard web su uno schermo e avere il dispositivo fisico sul tavolo. Poi fare qualcosa come attivare il dispositivo (premere un pulsante o creare una condizione) e mostrare che il cloud si aggiorna in tempo reale. Questi momenti sono genuinamente validanti per il team e preziosi per gli stakeholder. Rendono il progetto reale e mantengono tutti coinvolti. È anche un’occasione per ottenere feedback: magari uno stakeholder dice, “Bello, ma potremmo mostrare anche i dati di umidità nella prossima versione?” o “L’alert ci ha messo un po’ ad arrivare, possiamo velocizzarlo?”. Un feedback precoce come questo è particolarmente prezioso, perché può essere immesso negli sprint successivi, molto prima che il prodotto sia finale, quando le modifiche sono molto più facili da fare.

Non preoccupatevi se nelle prime demo la funzionalità è molto basilare – è previsto. La chiave è che migliora iterativamente. La prima demo potrebbe essere solo dimostrare che un dispositivo riesce a parlare con un server. La demo successiva mostra una semplice UI. Le demo successive mostrano rifiniture e funzionalità aggiuntive. Se qualcosa non ha funzionato come previsto in una demo, va bene lo stesso, perché avete scoperto un problema di integrazione davanti a tutti e ora potete dare priorità alla sua risoluzione. (Infatti, alcuni team hanno un detto: “Demo presto, demo spesso – fallisci spesso, correggi più in fretta!”) La demo impone anche un po’ di disciplina: mette una pressione positiva sul team per finire il lavoro in uno stato rilasciabile a ogni sprint, anziché procrastinare sull’integrazione fino a dopo. È molto meglio scoprire ora che il JSON del dispositivo non fa parsing correttamente sul server, che scoprirlo durante una grande fase di test finale. Quindi, trattate le demo non solo come uno spettacolo, ma come parte del processo di sviluppo stesso.

Infine, celebrate quelle demo. Costruire un prodotto IoT è un lavoro duro, che coinvolge molti pezzi, e ogni pezzo funzionante – anche una piccola funzionalità – merita un momento di riconoscimento. Questo mantiene alto il morale e costruisce la fiducia del team: l’energia resta più alta quando tutti vedono regolarmente i risultati tangibili del proprio lavoro.


Per riassumere la Fase 4: lo sviluppo di firmware e software cloud è tutto incentrato su progresso iterativo e miglioramento continuo. Investendo tempo nell’architettura software all’inizio, date al vostro team un progetto solido (ed evitate di programmarvi in un angolo). Usando sprint di sviluppo, continuous integration e una forte definition of done, vi assicurate che la qualità sia integrata a ogni passo – i bug vengono intercettati presto, e il software è sempre a un passo da un prodotto funzionante. Alla fine della Fase 4 (che probabilmente comprende molteplici cicli di sprint), dovreste avere un firmware completo nelle funzionalità e testato su hardware reale o rappresentativo, e un sistema cloud completo nelle funzionalità e testato in un ambiente realistico. In altre parole, l’intero lato software del vostro prodotto è pronto a partire.

Grazie a questo approccio iterativo, avete potuto aggiustare e migliorare continuamente, anziché scommettere tutto su un unico big bang finale. Questo riduce il rischio e rende il processo di sviluppo più prevedibile. È anche semplicemente un modo migliore di lavorare: invece di un lungo tratto di incertezza, avete guardato il vostro prodotto IoT prendere vita passo dopo passo, da un LED lampeggiante a un dispositivo smart completamente connesso e integrato col cloud. Il lavoro pesante sul lato software è fatto, ed è stato fatto con rigore e agilità. Avanti verso la prossima fase, dove prendiamo questo hardware e software ben costruiti e ci prepariamo a rilasciarli nel mondo.