Nella fase di sviluppo di un’applicazione IoT, è di fondamentale importanza la progettazione dell’architettura software.

Un programmatore embedded, infatti, deve essere in grado non soltanto di codificare le informazioni nella piattaforma IoT, ma di occuparsi dei molteplici aspetti dello sviluppo, dalla scalabilità all’evoluzione del sistema durante le iterazioni o alla previsione degli aggiornamenti futuri.

Zerynth, infatti, è stata tra le prime aziende ad utilizzare Python per lo sviluppo di applicazioni IoT, maturando una certa esperienza in questo settore.

Di seguito, ecco alcune delle best practiceda conoscere per lo sviluppo di soluzioni IoT sicure e scalabili.

1. Utilizzo di un Design multi-threaded

Supponiamo di voler sviluppare un’applicazione IoT in grado di misurare i dati da un sensore industriale e inviarli a un servizio cloud.

Sono possibili due approcci: eseguire continuamente ogni operazione in sequenza oppure dividere la stessa funzione dell’applicazione in più thread.

In questo caso, è consigliabile utilizzare un design multi-thread seguendo due principali passaggi:

  • Il primo thread si occupa di comunicare con i sensori e salvare i dati nella memoria flash.
  • Il secondo thread legge la memoria flash e manda i contenuti al servizio cloud.

Questo approccio assicura che:

  • Ogni processo non deve ostacolare quello successivo. Se si verifica un errore con una lettura del sensore, l’altro thread non deve essere bloccato in attesa di nuovi dati.
  • I dati non devono mai essere persi anche nei casi in cui il secondo thread presentasse errori con il servizio cloud. L’altro thread ottiene dati del sensore e li salva nella flash per l’uso successivo.

D’altra parte, se si utilizza un approccio in superloop, ciascuna di queste due operazioni bloccherebbe l’altra in caso di errore.

In generale, progettare un firmware robusto e affidabile nelle condizioni di lavoro difficili di un dispositivo IoT non è un compito facile. La perdita di dati deve essere ridotta al minimo in caso di possibile assenza di alimentazione o di connettività.

Zerynth fornisce un modulo dedicato alla registrazione sicura dei dati, garantendo la resistenza a possibili danni.

2. Sviluppo di codice con meno errori possibili

Indipendentemente dal proprio livello di esperienza, sono spesso frequenti errori e bug nei codici. Per questo motivo, il nostro approccio è quello di cercare di eliminare la sintassi e gli errori logici durante lo sviluppo, così da rilevare invece gli errori di runtime prima che comportino un ripristino completo del sistema.

Gestione degli errori

Ad esempio, si prende in esame il caso di una funzione di supporto, che divide un numero per un altro. Dividendo per zero, la restituzione di None può sembrare giusta perché il risultato non è definito.

Copy to Clipboard

Si tratta però di un errore comune nel codice in Python proprio perché None ha un significato speciale e la sua restituzione da una funzione implica quasi sicuramente un errore del codice.

Il modo migliore per evitare questo problema è utilizzare le eccezioni, in modo tale da rilevare e gestire immediatamente l’errore, proteggendo il codice da eventuali bug.

Copy to Clipboard

La funzione Assert

Assert è un’altra funzione utile per rilevare i bug nel codice durante il runtime. È in grado, infatti, di verificare che varianti, precondizioni o postcondizioni siano soddisfatte in fase di esecuzione, assicurando che il programma sia sempre valido e localizzare il prima possibile il bug.

Come brillantemente discusso da Tyler Hoffman nel suo paper, i vantaggi della funzione assert sono:

  • Gli asserzioni generalmente si verificano mentre il sistema ha ancora il controllo (ad es. non in un HardFault, ecc.), per cui il sistema può registrare in modo sicuro le informazioni di debug.
  • Gli sviluppatori collegati a un debugger trovano molto rapidamente gli errori logici del programma e l’uso non corretto dell’API.
  • Gli argomenti della funzione vengono dichiarati, per cui non c’è  necessità di verificare la validità degli argomenti e il codice di gestione degli errori nei livelli superiori, riducendo così la dimensione del codice.
  • Un buon posizionamento degli asserzioni può ridurre le possibilità di eccezioni rilevando accessi fuori limite, puntatori non validi e operazioni senza senso.

Nella piattaforma Zerynth, la funzione asserts viene utilizzata per sollevare un flag di errore critico e riavviare il sistema.

Usa i Watchdogs

WatchDogs è un ulteriore esempio di programma risorsa molto utile nei casi in cui l’MCU si blocchi  in uno stato irrecuperabile.

Garantisce che il software funzioni come previsto e, in caso contrario, avvierà un ripristino hardware.

In generale, un timer watchdog si basa su un contatore che conta alla rovescia da un valore iniziale a zero. Il software integrato seleziona il valore iniziale del contatore e, se questo raggiunge lo zero prima del software, lo riavvia.

3. Evitare la trappola copia-incolla-modifica

Sebbene i concetti di astrazione e modularità del codice non  siano completamente inerenti agli argomenti trattati in questo articolo, si considerano come esempi alcuni codici tratti dal libro di Brian Amos  Hands on RTOS with micro-controllers.

Analizziamo il suo codice “algorithm.c” da utilizzare per la realizzazione di un nuovo progetto. In che modo bisogna procedere? È sufficiente limitarsi a copiare il progetto di lavoro e iniziare ad apportare modifiche? Il problema non è copiare e modificare il codice, ma essere capaci di gestire correttamente tutte le copie.

Supponiamo di avere algoritmo.c che viene copiato e incollato in 3 progetti. Sebbene al momento dello sviluppo del progetto, ogni applicazione sembra funzionare come previsto, si notano alcuni errori :

  • Se Algorithm.c ha un bug, tutte e tre le copie dovranno essere corrette indistintamente.
  • Ciascuna correzione dovrà poi essere convalidata separatamente per ciascun progetto. L’unico modo per sapere se il bug è stato eliminato è testando l’hardware “in-real-system”; si tratta di un compito che richiede molto tempo ed è tecnicamente difficile raggiungere tutti i casi possibili.
  • La funzione Algo probabilmente si trasformerà nel tempo (anche inavvertitamente), per cui diventerà più complicato gestire  la manutenzione ed esaminare le differenze tra le implementazioni
  • I bug sono più difficili da individuare e correggere a causa delle lievi differenze tra le sei copie venutasi a creare.
  • Il progetto 4 può comportare un alto grado di incertezza (è difficile predire esattamente quali funzionalità di Algo verranno introdotte, quali complessità/bug seguiranno dai driver SPI o ADC e così via).
  • Se MCU1 diventa obsoleto, il porting di algoritmo.c deve essere eseguito tre volte separatamente.

Un approccio migliore sarebbe quello di sviluppare un livello di astrazione hardware tra l’algoritmo e i livelli MCU, SPI e creare un livello di interfaccia tra l’algoritmo e il file dell’applicazione del codice main.c.

Questo ci darà i seguenti vantaggi:

  • Se Algo ha un bug, sarà necessario risolverlo solo in un punto.
  • Algo ha soltanto una copia, per cui tenderà a non cambiare nel tempo, rendendo facile notare se ci sono differenze tra gli algoritmi
  • I bug sono più facili da individuare e correggere a causa delle ridotte interdipendenze delle variabili. Se c’è un bug in Algo, si troverà anche in tutti e sei i progetti (poiché esiste solo una copia) e testare Algo durante lo sviluppo è più semplice, grazie all’interfaccia.
  • È molto più probabile che la creazione del progetto 4 sia rapida ed efficiente grazie alla coerenza tra gli altri tre progetti.
  • Se MCU1 diventa obsoleto, l’ algoritmo.c non è necessario poiché non ha alcuna dipendenza diretta da un MCU, ma solo dall’interfaccia ADC. Sarà invece necessario selezionare/sviluppare un BSP diverso.

Hai dato uno sguardo alle nostre  Demos and Tutorials che usano Zerynth OS?

Per qualsiasi domanda, non esitare a contattarci sui nostri canali!

Share This Story, Choose Your Platform!

About the Author: Karim Hamdy

Karim si è laureato in Progettazione elettronica e Sistemi di comunicazione digitale, con interessi che spaziano dai robot autonomi alla programmazione baremetal. Ora passa la maggior parte del suo tempo a codificare e a mangiare pasta.

Follow Zerynth on

Latest Posts