Nel precedente articolo abbiamo introdotto Nodered come piattaforma per realizzare applicazioni basate su flussi di dati. Abbiamo visto come l’editor grafico permette di collegare al volo dati provenienti da diverse origini per creare task complessi.
Oggi approfondiamo il tutto creando un’applicazione per la visualizzazione real time di dati da un topic MQTT, verso cui possono pubblicare uno o più nodi. Questa può costituire la base per app complesse multi-topic simili a quella esposta nella nostra rubrica sull’utilizzo di schede esp e python/flask.
Ogni modulo dell’applicazione verrà realizzato in un contenitore Docker ed il tutto verrà messo in esecuzione tramite docker-compose. Ho parlato ampiamente di entrambi gli argomenti in una recente rubrica; per maggiori informazioni leggete prima i seguenti articoli:
- Installazione ed introduzione a Docker
- Creare container personalizzati: I Dockerfile
- Organizzazione container: docker-compose
Flussi e generazione grafici
Per realizzare quanto introdotto utilizzeremo il broker MQTT Mosquitto, Nodered e una piccola Webapp scritta in React.js. Ci sono tre componenti di cui abbiamo bisogno all’interno del flusso Nodered:
- L’interfaccia MQTT per ottenere i messaggi in arrivo;
- Un modo per potere inoltrare i dati in arrivo tramite uno stream verso la Webapp, utilizzando WebSocket;
- Un metodo per salvare i dati in arrivo, per potere recuperarli e manipolarli, ad esempio tramite semplici file testuali.
L’idea è la seguente: ogni volta che il nodo mqtt in riceverà un messaggio, il payload contenuto al suo interno verrà smistato in tre diversi nodi: uno che lo stamperà all’interno della sezione debug, uno che lo inoltrerà tramite l’interfaccia websocket ed uno che lo salverà all’interno di un file.
Per visualizzare o cancellare i dati raccolti, utilizzeremo lo stesso approccio visto precedentemente, ossia quello di creare endpoint http tramite i nodi http in/response.
Acquisizione e smistamento dati
Partiamo con l’analizzare la prima parte dell’applicazione Nodered, che vede interessata la parte che va dai sensori ad i tre blocchi arancioni dello schema sopra riportato.
I sensori ed il broker sono elementi esterni a Nodered, che analizzeremo più in là: il punto di ingresso dei dati è un nodo mqtt in. Il nodo andrà configurato con le informazioni del broker, del qos e del topic come nell’articolo precedente.
Per implementare lo smistamento sui tre nodi, utilizziamo un nodo debug ed uno file, esattamente nelle stesse configurazioni viste nello scorso articolo, dando un nome al nostro file, assieme alla posizione in cui poterlo trovare. Colleghiamo poi un terzo nodo, del tipo websocket out (dal menù network) e, nella configurazione visualizzabile tramite doppio click, selezioniamo Listen on nella sezione Type e / nella sezione Path.
Accedere e cancellare i dati salvati
La fase di cancellazione del file è piuttosto semplice e diretta, utilizziamo sempre i soliti nodi http in/response ma questa volta tramite il metodo http DELETE.
Per quanto riguarda l’accesso ai dati, l’idea è quella di creare un endpoint http interrogabile tramite GET, che restituisca un array json con i dati letti fino ad ora. Per fare ciò, è necessario usare un paio di nodi addizionali per costruire l’array. Lo schema descrittivo di quanto vogliamo ottenere è esplicitato nel seguente grafico.
Possiamo ottenere questo risultato tramite l’utilizzo dei nodi split e join. Nel primo, viene specificato che vogliamo effettuare uno split con il carattere di newline (/n) come delimitatore, per poi passare ogni elemento al nodo join per creare un array. All’output del nodo join, colleghiamo un nodo function per eliminare l’ultimo elemento dello split, una stringa vuota, contenente il seguente codice:
msg.payload.pop(); return msg;
Ultimiamo il tutto con un nodo json ed avremo qualcosa di simile a quanto riportato qui sotto.
Dettagli finali sul flusso Nodered
Ci sono due aspetti finali da considerare in questa applicazione: l’inizializzazione del file testuale ad inizio esecuzione/post-cancellazione ed il problema dei Cross-Origin Resource Sharing (CORS).
Il primo problema consiste nel fatto che allo startup, il file in cui conservare i dati ricevuti dai sensori non esiste ancora, come non esiste dopo averlo cancellato tramite il metodo DELETE. Una soluzione molto semplice consiste nell’usare un nodo exec per eseguire un comando da terminale, preceduto da un nodo inject, eseguito ad inizio flusso. Il nodo exec viene poi anche invocato ad ogni DELETE eseguita.
Il comando che ho deciso di utilizzare nel nodo exec è compatibile con la shell Bash Unix:
[[ -f data_dump ]] || touch data_dump
Il risultato del comando sarà quello di controllare se il file chiamato data_dump esiste e, se ciò non fosse vero, crearne uno vuoto. Una volta creato, possiamo connettere un nodo inject come ingresso, specificando in basso, nella sua configurazione, che vogliamo l’inject sia eseguito una volta dopo 0.1 secondi, senza poi essere ripetuto.
Per quanto riguarda il secondo problema, il CORS va affrontato per far sì che le nostre API siano utilizzabili dalla WebApp, e consiste nel settare alcuni header specifici nella nostra risposta http. Qui sotto ho allegato come impostare il tutto, ricordandovi che l’intero flusso sarà condiviso a fine articolo.
Eseguire la piattaforma: il docker-compose
Nodered ci ha permesso di organizzare la logica applicativa in maniera semplice e veloce. Oltre a ciò, la piattaforma ci permette anche di eseguire il flusso dentro un container docker, esportando la descrizione json del flusso stesso.
Questa possibilità è molto interessante, in quanto permette di potere unire i vari elementi dell’applicazione mantenendoli isolati. Per fare ciò, definiamo un compose articolato che faccia al caso nostro:
version: "3" services: broker: image: eclipse-mosquitto restart: always networks: - mqtt ports: - 1883:1883 command: ["mosquitto", "-v"] backend: image: nodered/node-red restart: always environment: - FLOWS=backend_flow.json volumes: - ./backend_flow.json:/data/backend_flow.json ports: - 1880:1880 networks: - mqtt depends_on: - broker webapp: build: ./webapp restart: always ports: - 3000:3000 depends_on: - backend networks: mqtt:
Come si può evincere dal codice, abbiamo tre servizi ed una rete (mqtt), dove backend identifica l’app Nodered. Mosquitto viene eseguito in un container chiamato broker, che sarà il nome da inserire nel flusso nodered per identificare l’host a cui connettere il nodo mqtt in.
Notiamo che all’interno della descrizione del servizio backend, troviamo la definizione della variabile d’ambiente FLOWS, con il nome del file che contiene la descrizione json del flusso. Inoltre, montiamo il file json tramite un volume, all’interno della cartella /data, per far sì che il container possa vederlo ed eseguirlo.
Conclusioni
Il tutorial non entra nel dettaglio della realizzazione della Webapp, ma vi lascio l’intero sorgente del progetto accessibile tramite il nostro account GitHub. Clonatelo e testatelo pure, semplicemente muovendovi nella cartella clonata ed eseguendo:
docker-compose up --build -d
Dopodiché, potrete accedere al pannello di Nodered andando al solito indirizzo http://localhost:1880, ed al grafico che visualizza i dati ricevuti via MQTT/websocket tramite http://localhost:3000. Di default, il topic che ho impostato per ricevere i messaggi è temp.
Per questo articolo è tutto, per eventuali approfondimenti o richieste di chiarimenti vi invito come al solito a commentare nella sezione qui sotto!