Nell’articolo precedente abbiamo parlato della parte hardware del progetto e dei tool a livello di sistema operativo (LIRC) necessari per il funzionamento del progetto.

Nell’articolo di oggi invece voglio parlarvi della parte puramente software, sia per quanto riguarda l’app effettivamente in esecuzione sul Raspberry, sia per quanto riguarda la parte web, necessaria per l’interfacciamento del Raspberry con Alexa e l’app per smartphone.

Le tecnologie scelte

Ho scelto di realizzare l’app per il Raspberry con NodeJs. Perché? Avevo bisogno di qualcosa di rapido da implementare, non eccessivamente pesante in fase di esecuzione e facilmente portabile dal Mac al Raspberry. Node corrisponde perfettamente all’identikit, anche se, come vedremo tra poco, qualche problemino di porting dal Mac al Raspberry Pi Zero c’è stato.

Per quanto riguarda invece la parte web, avevo bisogno di uno strumento che permettesse all’app Node sul Raspberry di “mettersi in ascolto” di tutti i comandi inviati (da Alexa e dall’app per smartphone). Ho scelto di utilizzare il realtime database di Firebase. Perché? Anche in questo caso volevo uno strumento rapido da implementare, che offrisse delle librerie pronte all’uso e che, ovviamente, non costasse nulla.

Come dicevo nel precedente articolo, altri progetti simili a questo che ho visto online utilizzano degli strumenti di tunnelling come ngrok, ma il risultato (se non si passa ad un piano a pagamento) è che ad ogni riavvio del Raspberry viene ricreato un tunnel con un URL diverso ogni volta, rendendo di fatto il progetto inutilizzabile se non per fare due test al volo di funzionamento.

Il funzionamento quindi del sistema è molto semplice:

  • All’avvio, l’app Node sul Raspberry effettua il login al realtime database di Firebase (al momento con una utenza fissa passata come configurazione all’app)
  • Se il login avviene con successo, l’app si mette in ascolto delle modifiche effettuate al database
  • Ad ogni evento di modifica ricevuto, l’app converte l’evento nei corrispondenti comandi da inviare a LIRC e, parallelamente, fa lampeggiare il LED di stato riattivando temporaneamente il circuito astable multivibrator di cui abbiamo parlato nel precedente articolo

Ecco un esempio di evento che viene ricevuto dall’app node sul Raspberry quando viene richiesto dall’utente di cambiare canale:

{
  "remote_controller_command" : {
    "timestamp" : "2021-09-27T12:19:54.844Z",
    "type" : "CHANNEL",
    "value" : 31
  }
}

Il comando viene convertito nei seguenti comando LIRC:

  • irsend SEND_ONCE tango_remote KEY_3
  • pausa
  • irsend SEND_ONCE tango_remote KEY_1

Se invece l’utente chiede di alzare il volume di 3 punti:

{
  "remote_controller_command" : {
    "timestamp" : "2021-09-27T12:19:54.844Z",
    "type" : "VOLUME",
    "value" : 3
  }
}

Il comando viene convertito nei seguenti comando LIRC:

  • irsend SEND_ONCE tango_remote KEY_VOLUMEUP
  • pausa
  • irsend SEND_ONCE tango_remote KEY_VOLUMEUP
  • pausa
  • irsend SEND_ONCE tango_remote KEY_VOLUMEUP

Se invece l’utente chiede di spegnere la TV:

{
  "remote_controller_command" : {
    "timestamp" : "2021-09-27T12:19:54.844Z",
    "type" : "POWER"
  }
}

Il comando viene convertito nei seguenti comando LIRC:

  • irsend SEND_ONCE tango_remote KEY_POWER

Questi al momento sono tutti i comandi che ho deciso di gestire, ovviamente nulla vieta di “mappare” tutti i possibili comandi che la TV è in grado di ricevere (es, navigazione nei menu ecc).

Come potete vedere nei comandi è sempre presente il campo “timestamp”, questo perchè se l’utente invia due volte lo stesso comando, senza il campo timestamp il database non verrebbe modificato e l’app sul Raspberry non verrebbe notificata della modifica. Questo è un limite dovuto al fatto che stiamo utilizzando uno strumento che non è pensato al 100% per questa tipologia di utilizzo.

A posteriori avrei potuto utilizzare un sistema di pub/sub che effettivamente sembra essere perfettamente aderente al nostro caso d’uso. Poco male, si impara sempre qualcosa!

Per quanto riguarda le librerie utilizzate dall’app Node, in realtà ci sono solo le librerie necessarie per l’interfacciamento con il RTDB di Firebase (supportate da rxfire, perché diciamocelo, le callback sanno di vecchio); per quanto riguarda invece l’invio dei comandi a LIRC e al controllo delle porte GPIO ho utilizzato delle semplici chiamate tramite il modulo child_process di node.

Esecuzione dell’app sul Raspberry

L’app per il Raspberry è pronta, ora è il momento di metterla in esecuzione sul Raspberry, facendo in modo che parta automaticamente all’avvio del dispositivo senza alcun intervento esterno. Ho scelto di creare un servizio da eseguire tramite systemd.

Il vantaggio di questo approccio è che possiamo decidere di far partire il nostro servizio solo dopo che lo strato di networking è pronto e che systemd ci permette anche di definire delle logiche di restart in caso di crash della nostra app (un po’ come forever). Tramite dei semplici comandi poi:

sudo systemctl start/stop/status tangotv.service

possiamo avviare o fermare manualmente il nostro servizio, oppure verificare lo stato e consultare i log.

Ecco il file di definizione del nostro servizio:

[Unit]
Description=TangoTv service
After=network.target

[Service]
Type=simple
ExecStart=node index.js
WorkingDirectory=/home/pi/tangotv-raspberry
Restart=always
RestartSec=1
User=pi

[Install]
WantedBy=multi-user.target

Problema: Raspberry Pi Zero

Fino a qui ho lavorato sul Raspberry PI 3 senza alcun problema. Ora è arrivato il momento di effettuare tutto il setup sul Raspberry Pi Zero e le cose non sono andate proprio lisce.

Per quanto riguarda LIRC l’installazione non ha dato nessun problema.

L’installazione invece di Node ha dato dei problemi perchè le ultime versioni di Node non sono disponibili sul Pi Zero, per cui ho dovuto installare una versione molto più vecchia.

Una volta poi messa in esecuzione l’app mi sono accorto che i tempi di startup erano davvero biblici: più di un minuto. Dopo un po’ di debugging mi sono accorto che il problema in realtà non era l’app in sé ma npm: inizialmente infatti lanciavo l’app tramite:

npm start

E’ bastato passare a 

node index.js

Per ridurre sensibilmente i tempi di avvio. Perchè? Non chiedetemelo!

Ora ci siamo davvero: abbiamo realizzato l’app Node e l’abbiamo messa in esecuzione sul Raspberry Pi Zero tramite un servizio gestito da systemd. Evviva! Ora è il momento di iniziare a parlare di una delle due interfacce di input che vogliamo realizzare: l’app per smartphone.

L’app Flutter

Il funzionamento dell’app è molto semplice, ovvero “simula” in tutto e per tutto il telecomando: 

  • permette all’utente di inserire tramite una interfaccia super semplice i comandi
  • i comandi inseriti dall’utente vengono convertiti nel formato JSON che abbiamo visto in precedenza (per farlo utilizziamo RxDart: ormai l’avrete capito, dove posso mi riconduco sempre ReactiveX per risolvere dei problemi di natura asincrona come l’input utente)
  • i comandi vengono “scritti” nel realtime database di Firebase
  • l’app Node sul Raspberry viene notificata della modifica al database e si innesca il flusso di cui abbiamo parlato sopra.

Ho deciso di realizzare l’app in Flutter, perché permette di realizzare app cross-platform ma che mantengono delle performance native. Il Dart poi è un linguaggio ad oggetti giovane e molto comodo da utilizzare. Anche in questo caso poi erano disponibili tutte le librerie del caso per interfacciarsi con il realtime database di Firebase in maniera agile e veloce.

Giusto per avere anche un feedback sui comandi inviati, l’app mostra una Toast notification  ad ogni comando inviato.

Credo che per oggi sia tutto, nel prossimo articolo chiuderemo il cerchio parlando della Skill che abbiamo realizzato per Alexa. Alla prossima!