[Domotica IoT] Controllo relè da remoto (ESP8266 + WiFi + Android app)

Vi presento un piccolo progetto che ho realizzato per comandare 8 relè da remoto. La scheda monta un ESP8266 (ESP-01) e si collega alla wifi di casa. Tramite una semplice applicazione Android possiamo comandare in modo indipendente gli 8 relè via internet, da ovunque e in qualunque momento. E’ sufficiente che lo smartphone sia connesso (dati o wifi).

Cosa possiamo fare con questa scheda? Avendo 8 relè a disposizione, possiamo comandare fino a 8 carichi 220v 3A, come ad esempio:

  • cancelli di casa
  • porte garage
  • portone di casa
  • caldaia
  • aria condizionata
  • allarmi

In sostanza un pò tutto quello che è già comandato da un interruttore. Forni o stufe elettriche non possono essere collegati perchè la potenza massima gestibile dai relè che ho utilizzato è di 600W circa (220V 3A). A meno che ovviamente questi dispositivi non abbiano un segnale di controllo a bassa tensione oppure si utilizzi un relè di potenza in grado di gestire alte correnti.

Il comando che arriva al relè può essere configurato in diversi modi:

  • impulso monostabile (esempio: 1 secondo ON e poi di nuovo OFF automaticamente)
  • bistabile (ossia rimane nella posizione assegnata ON o OFF)
  • temporizzato (esempio: ON la mattina, OFF la sera)
  • treno di impulsi (se occorre inviare segnali particolari a dispositivi più sofisticati)

Ovviamente in commercio esistono numerose schede già fatte, ma richiedono software o applicazioni specifiche, a volte a pagamento e non sempre i relè solo configurabili a piacimento (vedi punti sopra).

Schema di base

Lo schema di base è abbastanza semplice: la board si connette ad internet tramite il router wifi di casa. Lo smartphone invia comandi alla board tramite la propria connessione dati. L’utente non deve essere necessariamente connesso direttamente in wifi.

Protocollo di comunicazione

Per l’invio dei comandi da smartphone a board ho deciso di implementare il protocollo MQTT, uno degli standard più famosi ed utilizzati per lo scambio di dati tra dispositivi IoT e non.

http://www.itdistribuzione.com/portale/html/NewsPage.html?idNews=2954

http://mqtt.org/

Il bello di questo protocollo sta nel fatto che è estremamente semplice da implementare, anche su piattaforma Arduino (e in modo particolare sull’ESP01) e non richiede l’apertura di porte lato router wifi: l’ESP01 si connette verso un server MQTT esterno, instaura una connessione persistente e resta in attesa di comandi. Lo smartphone invia comandi al server MQTT esterno che a sua volta recapita i messaggi all’ESP01. Ovviamente possiamo anche inviare messaggi MQTT in direzione opposta, dall’ESP01 verso lo smartphone, sebbene questo progetto non implementi tale funzionalità.

Grazie a questo protocollo non dobbiamo né aprire porte in NAT, né conoscere il nostro indirizzo IP, né tantomeno contattare periodicamente un server esterno in polling.

Esistono numerosi server MQTT gratuiti, tra i quali Maqiatto e HiveMQ. Il primo in particolare non ha alcuna restrizione di banda o di dispositivi, ed è accessibile 24h su 24h sulla porta 1883.

Componenti

La board IoT si compone di 3 elementi principali:

  • ESP01
  • board Arduino 8 relè
  • GPIO expander PCF8574

L’ESP01 è l’elemento principale: si connette in wifi e riceve comandi dall’esterno; traduce inoltre questi comandi in appositi segnali per pilotare la board relè. Siccome l’ESP01 non ha abbastanza GPIO per comandare direttamente la board (ne servirebbero 8, uno per relè), ho utilizzato un GPIO expander (PCF8574) che sostanzialmente permette di comandare 8 GPIO tramite I2C: l’ESP01 invia una richiesta I2C all’expander che ha sua volta aziona i GPIO opportuni.

ESP01

L’ESP01 è un microcontrollore a basso costo basato sul micro ESP8266 con wifi integrato, compatibile con Arduino. Costa poco più di un euro ed il primo di una lunga famiglia: esistono numerosi altri modelli, ESP-02, ESP-03 etc.. ognuno dei quali possiede caratteristiche e funzionalità via via più sofisticate. Ad oggi ne esistono ben 12 modelli, da ESP-01 a ESP-12.

Il pinout dell’ESP01 è veramente ridotto all’osso: 8 pin in totale:

  • VCC – alimentazione (3.3V)
  • GND – massa
  • RST – reset
  • CH_PD – chip enable
  • TX, RX – seriale per la programmazione
  • GPIO0, GPIO2 – gli unici due GPIO disponibili

La seriale TX e RX è riservata per la programmazione iniziale, sebbene poi possa essere riallocata come seriale generica durante il normale funzionamento. Il pin CH_PD è il chip enable: abilita l’ESP01 e va quindi sempre tenuto HIGH. Il pin RST è il reset, anch’esso va tenuto HIGH. Occhio che l’ESP01 lavora con i livelli di tensione 3.3V e 0V, quindi HIGH corrisponde a 3.3V, LOW corrisponde a 0V. Anche la seriale è a 3.3V quindi nel caso avessimo un programmatore a 5V occorrerà convertire opportunamente i livelli di tensione.

L’ESP01 può essere avviato in due modi operativi differenti: modo programmazione e modo esecuzione. Nel modo programmazione, l’ESP01 si mette in ascolto sulla seriale (TX,RX,GND) in attesa di ricevere il programma da memorizzare nella flash interna. Nel modo esecuzione invece, l’ESP01 si avvia normalmente eseguendo il programma memorizzato nella flash.

Per avviare l’ESP01 in modo programmazione basta impostare i due GPIO nel seguente modo: GPIO0 = LOW e GPIO2 = HIGH. Per avviare l’ESP01 in modo esecuzione, occorre invece tenere HIGH entrambi i GPIO: GPIO0 = HIGH, GPIO2 = HIGH. Dopo l’avvio in una qualsiasi delle due modalità, non è più necessario tenere i GPIO impostati.

Board Arduino 8 relè

Questa board monta 8 relè pilotabili in modo indipendente grazie agli 8 pin di ingresso. Alimentazione a 5v (ma l’ho testata e funziona anche con 3.3V).  Il jumper permette di alimentare i relè con una alimentazione indipendente.

 

GPIO expander PCF8574

Si è reso necessario utilizzare un GPIO expander perchè l’ESP01 non ha abbastanza GPIO (soltanto 2 dei 8 necessari). Il PCF8574 è sicuramente il più adatto in questo caso. Questo expander riceve comandi tramite I2C e imposta i suoi GPIO in modo opportuno. Il comando I2C è un semplice pacchetto di 2 byte: il primo byte è l’indirizzo dell’expander, il secondo byte rappresenta lo stato dei GPIO (0 = HIGH, 1 = LOW). I GPIO possono anche essere impostati come ingressi, per leggerne lo stato è sufficiente inviare un comando I2C in modo lettura. Nel nostro caso non abbiamo implementato questa funzionalità.

Da notare i 3 jumper per impostare l’indirizzo dell’expander. Di default, l’indirizzo è 0x20.

Programmazione dell’ESP01 – Hardware

Per la programmazione ho utilizzato l’adattatore seriale FTDI 232RL USB

Questo adattatore si collega al PC tramite cavo USB e può essere configurato tramite jumper sia in modalità 5V che 3.3V (quella che ci serve per programmare l’ESP01).

Occorrerà inoltre realizzare una piccola board per collegare tra loro l’FTDI232RL all’ESP01 e per impostare GPIO0 e GPIO2 dell’ESP01 in modo programmazione (nonchè fornire l’alimentazione e polarizzare i pin CH_PD e RST). Ecco lo schema:

Sotto il programmatore completo, su basetta millefori. Sono necessari solo 4componenti esterni: 3 resistenze da 10 kohm e uno switch per il reset. Attenzione, impostate l’FTDI232BL in modalità 3.3V o rischiate di bruciare l’ESP01.

 

Programmazione dell’ESP01 – Configurare l’editor Arduino

Prima di poter programmare l’ESP01 occorre prima importare le configurazioni ESP8266 all’interno dell’editor Arduino. E’ sufficiente andare in “File” -> “preferences” e poi nella tab “settings” impostare nel campo “additional board manager URL” il seguente URL:

http://arduino.esp8266.com/stable/package_esp8266com_index.json

Date OK e riavviate l’editor Arduino. A questo punto occorre includere le librerie per ESP8266. Queste librerie vi daranno accesso al wifi per connettervi, creare un access point, e per gestire le comunicazioni via HTTP e TCP/UDP raw.

Per includere le librerie andate in “tools” -> “board: (la board corrente)” -> “board manager”. Cercate “ESP8266” e installate il pacchetto evidenziato (esp8266 by ESP8266 Community).

Riavviate Arduino IDE. Adesso, andando in “tools” -> “board:” dovreste vedere “Generic ESP8266 Module”. Selezionatelo. Inoltre impostate i seguenti parametri:

  • flash mode = DIO
  • flash frequency = 40MHz
  • CPU frequency = 80 MHz
  • Flash size = 512K (64K SPIFFS)
  • Debug port = disabled
  • Debug level = None
  • Reset Method = ck
  • Upload speed = 115200

Ed impostate la seriale COM opportuna. Il PC dovrebbe vedere l’FTDI232BL in modo automatico, se non dovesse dovrete scaricare i driver dal sito del produttore:

https://www.ftdichip.com/Drivers/VCP.htm

A questo punto siete pronti per programmare l’ESP01!

Circuito completo

Gli elementi base del circuito sono l’ESP01, la board Arduino 8 relè, e il GPIO expander PCF8574. L’ESP01 e l’expander richiedono una tensione di 3.3V, mentre la board relè 5V. Occorrerà quindi fornire queste due tensioni tramite appositi regolatori di tensione. l’ESP01 deve entrare in modo esecuzione (GPIO0 = HIGH e GPIO2 = HIGH). Fortunatamente, l’expander PCF8574 fornisce due resistenze di pull-up interne per le linee SDA e SCL

Firmware

Lo sketch Arduino è abbastanza semplice: ci connettiamo alla rete tramite WiFi (nome rete e password sono nello sketch), successivamente ci connettiamo al server MQTT e rimaniamo in attesa di comandi. Ad ogni comando ricevuto, decodifichiamo la richiesta ed inviamo il comando I2C opportuno al GPIO expander, che a sua volta azionerà il GPIO richiesto. Da notare che occorre includere la libreria MQTT, io ho scelto l’ottima pubSubClient:

https://github.com/knolleary/pubsubclient

e la libreria Wire, per la comunicazione tramite I2C:

https://www.arduino.cc/en/reference/wire

Entrambe queste librerie vanno importate esattamente allo stesso modo visto in precedenza per la libreria ESP8266

 

Setup()

Nella fase di setup() impostiamo i pin GPIO0 e GPIO2 come output. In più, prepariamo questi due gpio per la comunicazione via I2C con l’expander. GPIO0 = SDA e  GPIO2 = SDL. Da notare che utilizziamo la libreria Wire (#include(“Wire.h”)) per comunicare tramite I2C.

Successivamente resettiamo l’expander inviando il pacchetto I2C di due byte [0x20,0xff]. 0x20 è l’indirizzo che abbiamo assegnato all’expander tramite i jumper, mentre 0xff rappresenta lo stato dei GPIO, ossia tutti e 8 LOW. (1 = LOW, 0 = HIGH). Ovviamente, se assegnate un indirizzo diverso, dovrete inviare il byte opportuno. E’ anche possibile collegare in catena più expander, fino a 8 per un totale di 64 GPIO.

Fatto ciò impostiamo il client MQTT passando l’indirizzo del server e la porta, nel nostro caso “maqiatto.com” e 1883. In più, passiamo il puntatore alla nostra funzione callback che verrà invocata dalla libreria ogniqualvolta riceveremo un pacchetto MQTT.


void setup()
{
   pinMode(BUILTINLED, OUTPUT);
   digitalWrite(BUILTINLED, HIGH);

   // setup PCF8574
   pinMode(0, OUTPUT);
   pinMode(2, OUTPUT);

   delay(5000);

   Wire.begin(0, 2); // SDA = GPIO0, SDL = GPIO2
   Wire.beginTransmission(0x20);
   Wire.write(0b11111111);
   Wire.endTransmission();

   mqttClient.setServer(mqtt_server, mqtt_port);
   mqttClient.setCallback(callback);
}

Loop()

Nel loop principale effettuiamo in sequenza ciclica queste operazioni:

  • test & connessione wifi
  • test & connessione a server MQTT
  • gestione della coda MQTT

void loop()
{
   connectWifiReliable(); // connect wifi
   connectMqttReliable(); // connect mqtt
   mqttClient.loop();
}

La prima riga lancia connectWifiReliable(), una funzione che o ritorna immediatamente se l’ESP01 è connesso alla WiFi, di fatto rendendosi totalmente trasparente al loop, oppure (se l’ESP01 non fosse connesso in WiFi) procede alla connessione (riprovando più volte non dovesse riuscirci al primo tentativo). Al ritorno della funzione è garantita una connessione WiFi. Dopo un certo numero di tentativi falliti (io ho messo 200 tentativi), la funzione esegue il reboot completo dell’ESP01. Mettendo la funzione di connessione nel loop principale e controllando ad ogni ciclo che l’ESP01 sia connesso, rendiamo la connessione WiFi estremamente robusta: qualsiasi problema tipo disservizio di rete, router in riavvio, mancanza momentanea della WiFi, viene prima o poi intercettato dalla connectWifiReliable() che provvede a ripristinare il collegamento. Viene correttamente gestito anche il primo avvio dell’ESP01 (durante il quale la board non è ovviamente connessa). Di solito, su molti altri sketch Arduino, vedo il codice di setup della WiFi messo direttamente nella funzione setup() ma non credo sia ottimale perchè non vengono gestiti eventuali problemi di connessione.

Una funzione analoga, connectMqttReliable(), connette invece l’ESP01 al server MQTT. Funziona con lo stesso principio: non si esce dalla funzione fintanto che non è stabilita una connessione col server remoto. Dopo alcuni tentativi falliti anche questa funzione invoca il reboot dell’ESP01.

L’ultima funzione, ossia mqttClient.loop(), gestisce il protocollo MQTT stesso. Gestisce la coda interna e provvede ad invocare la nostra callback di gestione dei pacchetti MQTT remoti. Deve essere invocata ad intervalli regolari, idealmente in loop infinito. Ed è proprio quello che facciamo noi piazzandola nel loop principale.

Callback gestione dei comandi MQTT

Veniamo ora alla parte più interessante ossia la callback che gestisce il pacchetto MQTT in arrivo da un client remoto. Questa funzione è invocata dalla libreria MQTT stessa all’arrivo del pacchetto MQTT. La firma della funzione è la seguente:

void callback(char* topic, byte* payload, unsigned int length)

topic è il “canale” MQTT di ricezione (MQTT funziona secondo il classico patter publisher/subscriber per cui il client ESP01 si mette in ascolto (subscribe) solo a determinati “canali”.

payload è il pacchetto stesso, un array di byte

length è la lunghezza del pacchetto, in byte

Nel mio caso, avendo solo un topic registrato, non controllo questa variabile. Più topic magari richiederebbero l’esecuzione di codice diversificato a seconda del canale. Payload e length invece vengono passati direttamente a processRequest()


void callback(char* topic, byte* payload, unsigned int length)
{
    if(length != 18) return;
    processRequest(payload);
}

Nel mio caso faccio un ulteriore controllo sulla lunghezza: se il pacchetto non è lungo 18 byte, la funzione scarta tutto ed esce senza fare nulla. Il pacchetto MQTT che ho studiato è lungo infatti esattamente 18 byte:

Il primo byte definisce il modo di attivazione del relè: può essere “P” per PULSE oppure “T” per TOGGLE. PULSE implica l’attivazione per un secondo del relè, dopodichè il relè torna automaticamente in posizione di riposo. TOGGLE invece cambia stato al relè, che in questo rimane indefinitivamente nello stato impostato (a parte ovviamente riavvii dell’ESP01 oppure blackout completi). Il secondo byte definisce quale relè attivare: “0” attiva il primo relè, “1” il secondo, “2” il terzo e cosi via fino a 7. Gli ultimi 16 byte costituiscono invece la password: non strettamente necessaria, la password impedisce a client non autorizzati di inviare comandi alla board. Solo i client che sono a conoscenza della password possono attivare i relè. E’ possibile specificare una password diversa per ogni relè.

Ad esempio il seguente pacchetto MQTT attiva il relè numero 4 (indice GPIO 3) in modalità PULSE (un secondo ON e poi di nuovo OFF), con password “ABCDEFGH12345678”.


P3ABCDEFGH12345678

 

Se dunque il pacchetto supera il controllo di lunghezza, viene inviato alla funzione processRequest() che decodifica il payload, controlla la password e poi invia i comandi I2C opportuni all’expander.


void processRequest(byte* payload)
{
   char cmd = payload[0];
   char chn = payload[1];
   char password[16];
   for(int p = 0; p < 16; p++) password[p] = payload[2 + p];

   uint8_t chnNum;
   if(chn == '0') { chnNum = 0b00000001; if(strncmp(password, PASSWORD_CH0, 16) != 0) return; }
   else if(chn == '1') { chnNum = 0b00000010; if(strncmp(password, PASSWORD_CH1, 16) != 0) return; }
   else if(chn == '2') { chnNum = 0b00000100; if(strncmp(password, PASSWORD_CH2, 16) != 0) return; }
   else if(chn == '3') { chnNum = 0b00001000; if(strncmp(password, PASSWORD_CH3, 16) != 0) return; }
   else if(chn == '4') { chnNum = 0b00010000; if(strncmp(password, PASSWORD_CH4, 16) != 0) return; }
   else if(chn == '5') { chnNum = 0b00100000; if(strncmp(password, PASSWORD_CH5, 16) != 0) return; }
   else if(chn == '6') { chnNum = 0b01000000; if(strncmp(password, PASSWORD_CH6, 16) != 0) return; }
   else if(chn == '7') { chnNum = 0b10000000; if(strncmp(password, PASSWORD_CH7, 16) != 0) return; }
   else return;

   if(cmd == 'P')
   {
      sendGpio(gpioOut & ~chnNum);
      delay(1000);
      sendGpio(gpioOut | chnNum);

      digitalWrite(BUILTINLED, HIGH);
      delay(100);
      digitalWrite(BUILTINLED, LOW);
      delay(100);
   }
   else if(cmd == 'T')
   {
      gpioOut = gpioOut ^ chnNum;
      sendGpio(gpioOut);

      digitalWrite(BUILTINLED, HIGH);
      delay(100);
      digitalWrite(BUILTINLED, LOW);
      delay(100);
   }
   else
   {
      return;
   }
}

Per il resto allego lo sketch completo

download sketch Arduino (2Kb)

 

Client

Come dispositivo client possiamo utilizzare qualsiasi software in grado di inviare comandi MQTT: eseguibile standalone Windows, pagina web in PHP, oppure una applicazione Android. Io ho optato per l’ultima opzione, una applicazione Cordova Javascript in grado di inviare pacchetti arbitrari ad un server MQTT.

In questo caso l’applicazione permette di definire una lista di pulsanti, nominarli a piacere, selezionare il modo (PULSE o TOGGLE) e il GPIO corrispondente.

Il server Maquiatto offre anche una comoda interfaccia web per testare i propri sistemi MQTT. E’ possibile inviare pacchetti MQTT arbitrari, definire o registrarsi a topic già creati in precedenza etc…

http://maqiatto.com/webclient/

Grazie a questa interfaccia web non è necessario implementare il client per testare il funzionamento della board ESP01

 

 

 

 

 

 

 

 

 

 

Lettura remota del fotovoltaico con NodeMCU

Vediamo come realizzare un semplice dispositivo per leggere la produzione di un impianto fotovoltaico e caricare i dati su un server online. La lettura manuale degli inverter di un impianto fotovoltaico è una procedura scomoda e tediosa ma è possibile automatizzare l’estrazione dei dati di produzione poichè molti inverter dispongono di interfaccia RS485. Grazie a tale interfaccia è possibile collegare un piccolo dispositivo elettronico in grado di interrogare gli inverter ad intervalli regolari e memorizzarli o inviarli ad un server remoto. I dati possono poi essere letti da smartphone o computer desktop da qualsiasi parte del mondo!

Un impianto fotovoltaico consiste di un insieme di pannelli, uno o più inverter, il contatore di produzione e quello di scambio:

 

 

L’inverter funziona autonomamente ma controllarne il corretto funzionamento è di primaria importanza per evitare cali di produzione e intervenire tempestivamente per guasti e malfunzionamenti. L’interfaccia RS485 mette a disposizione un numero di dati interessanti tra i quali:

  • Temperatura (C)
  • Produzione giornaliera (kWh)
  • Produzione totale (kWh)
  • Stato (OK o guasto)
  • Tensione, corrente e potenza istantanea

Schema di base

Lo schema di base del progetto è abbastanza semplice. Siccome la stragrande maggioranza degli inverter usa RS485 ho optato per NodeMCU, un minicomputer da 7 euro (7 euro!) che dispone di interfaccia seriale, un certo numeri di GPIO programmabili e un potente modulo WiFi.
NodeMCU può essere programmato per connettersi alla rete WiFi di casa e può ricevere o inviare dati ad un server remoto. Si programma esattamente come una scheda Arduino e non dispone di sistema operativo o programmi vari come un Raspberry Pi. Questa caratteristica lo rende perfetto al nostro scopo perchè l’imperativo in questo caso è l’affidabilità: un Raspberry Pi sarebbe stato altrettanto capace ma essendo un computer vero e proprio è soggetto ad aggiornamenti improvvisi, corruzioni del filesystem, blocchi inaspettati di sistema e chi più ne ha più ne metta. NodeMCU è più simile ad un microcontrollore che ad un vero PC: esegue un solo programma nella sua memoria flash, non ha una SD card che può deteriorarsi nel tempo e se viene a mancare la corrente si azzera tutto e riparte da capo senza problemi.

La scheda NodeMCU ha una seriale UART TTL che non è direttamente compatibile con RS485, è quindi necessario un secondo modulo in grado di convertire l’UART TTL del NodeMCU in un segnale RS485 adatto all’inverter. Queste schede di conversione si trovano a pochi centesimi su ebay o amazon.

NodeMCU dialoga con l’inverter tramite il modulo di conversione RS485, preleva i dati di produzione e li invia in wifi al server remoto. Un semplice script PHP nel server riceve i dati, li elabora e produce una pagina HTML consultabile da qualsiasi smartphone Android/iPhone o PC desktop

L’intervallo di interrogazione e di caricamento dei dati nel server è totalmente configurabile, 1 minuto è più che sufficiente. Lo script PHP può essere configurato per inviare email in caso di guasto o temperatura eccessiva degli inverter o semplicemente per notificare l’utente a fine giornata della produzione totale. La pagina HTML è estremamente semplice e mostra i dati cosi’ come vengono acquisiti dalla scheda NodeMCU:

Ogni blocco corrisponde ad un inverter e contiene i dati di produzione. Il blocco è verde quando tutto è ok, diventa rosso in caso di guasto o temperatura eccessiva.

NodeMCU

Il cervello del progetto è questa piccola scheda, NodeMCU, un minicomputer da 7 euro in grado di connettersi in WiFi alla rete di casa, implementa tutto lo stack TCP/IP per l’invio e ricezione di dati a server remoti, possiede 16 GPIO programmabili, una UART bidirezionale, un’altra UART solo in trasmissione, interfaccia pr SD card (opzionale) e interfaccia SPI:

Ecco i dettagli della piedinatura:

La scheda si programma tramite USB esattamente come Arduino, basta scaricare l’IDE ufficiale Arduino, installare la libreria ESP8266 (NodeMCU è essenzialmente una dev board per ESP8266) e configurare l’ambiente di sviluppo per la programmazione. Ecco un’ottima guida per iniziare. La seriale UART è mappata ai pin RX/TX e purtroppo non possono essere usati direttamente per dialogare con l’inverter che accetta RS485. Fortunatamente l’unica sostanziale differenza tra seriale UART e RS485 è soltanto nel livello fisico e non quello di protocollo percui un semplice convertitore è tutto quello che serve

Modulo RS485

Questo convertitore a basso costo trasforma il segnale UART del NodeMCU in un segnale RS485 e viceversa.

I pin A e B andranno collegati ai corrispondenti piedini A e B degli inverter nella classica configurazione in cascata mentre all’altro lato i piedini RO, DI, DE, RE andranno collegati alla scheda NodeMCU. I pin RO (Out) e DI (In) vanno collegati rispettivamente ai pin RX e TX della scheda NodeMCU. DE e RE sono piedini speciali utilizzati per abilitare la lettura o la scrittura sul doppino RS485. DE abilita il driver interno di scrittura e va impostato ad 1 quando si vuole transmettere un pacchetto dati verso gli inverter. RE abilita l’ascolto e va impostato a 0 quando si vuole ricevere un pacchetto di risposta da uno degli inverter. Siccome DE è attivo alto e RE attivo basso e siccome la comunicazione con gli inververter è half-duplex (botta e risposta con un solo master) è possibile collegare tra loro questi due pin e comandarli con un unico GPIO della scheda NodeMCU. Quando vogliamo inviare un pacchetto dati agli inverter basterà alzare il GPIO ad 1 , quando vogliamo ricevere lo impostiamo basso

Circuito

Il circuito è estremamente semplice, la scheda NodeMCU viene alimentata tramite USB da un normale alimentatore a 5v (assicuriamoci che riesca a fornire almeno 1000mA di corrente, altrimenti il driver RS485 non riuscirà a comunicare con gli inverter). I piedini TX/RX della scheda sono collegati a RO/DI del convertirore RS485. Il GPIO5 comanda la scrittura / ricezione dei dati ed è collegato ai piedini RE/DE del convertitore. Il convertitore è alimentato a 3.3V dalla scheda NodeMCU stessa. I pin A e B del convertitore vanno agli inverter

Il convertitore è alimentato a 3.3v per renderlo compatibile con i livelli elettrici dell’ UART del NodeMCU, che sono a 0-3.3v. La tensione massima prodotta dal convertitore RS485 sarà di 3.3v che è perfettamente sufficiente per dialogare con gli inverter (lo standard RS485 prevede un minimo di 1.5v, alcuni dispositivi accettano fino a 200mV):

Ecco una foto dei due moduli collegati tra loro, a sinistra la scheda NodeMCU, a destra il convertitore RS485 collegato al doppino che va agli inverter

 

Protocollo di comunicazione

Gli inverter utilizzano una variante del protocollo MODBUS in modalità half-duplex. Ciò significa che ad ogni istante di tempo c’è solo un dispositivo che “parla” sulla linea mentre gli altri “ascoltano”. Esiste un solo master ossia la scheda NodeMCU, tutti gli inverter sono slave. Il protocollo è di tipo “botta e risposta” cioè il master invia una richiesta e uno slave (un inverter) risponde: gli inverter non iniziano mai di loro iniziativa a dialogare con il master, è sempre e solo il master che interroga e riceve (eventualmente) una risposta.

Una trasmissione si compone di un singolo pacchetto MODBUS che include 7 campi:

  • header: un codice fisso 0xAA, 0xAA di preambolo che da tempo agli inverter di “mettersi in ascolto” sulla linea
  • sender address: l’indirizzo 16 bit del mittente (0x0100 è il master)
  • receiver address: indirizzo 16 bit del destinatario (0x0000 è l’indirizzo broadcast)
  • command: un codice a 16 bit che descrive uno tra 4 possibili richieste, poi vedremo quali nel dettaglio
  • payload length: 8 bit per specificare la lunghezza del prossimo campo (payload)
  • payload: il contenuto del pacchetto vero e proprio (dati di produzione degli inverter o altro)
  • CRC: il classico campo di controllo utile al ricevente per verificare la corretta ricezione del pacchetto

Esistono 4 tipi diversi di comandi:

  • comando RICHIESTA NUMERO SERIALE
  • comando ASSEGNAZIONE INDIRIZZO
  • comando RICHIESTA DATI DI PRODUZIONE
  • comando RESET

Richiesta dati di produzione

Il comando RICHIESTA DATI DI PRODUZIONE viene inviato dal master (NodeMCU) ad intervalli regolari e contiene l’indirizzo di destinazione dell’inverter che vogliamo interrogare. Il payload è vuoto. L’inverter risponderà con un pacchetto simile contenente i dati di produzione. Nel caso degli inverter Solartec si tratta di un payload di esattamente 90 byte che contiene temperatura, corrente, tensione, produzione giornaliera, produzione totale e molto altro. La corretta decodifica di questo payload va fatta caso per caso e dipende dal modello di inverter. Ogni inverter ha il suo indirizzo univoco (receiver address) ma come viene assegnato questo indirizzo? Lo vediamo nel prossimo comando.

Assegnazione indirizzo

Il comando ASSEGNAZIONE INDIRIZZO è inviato dal master in broadcast (non abbiamo ancora un indirizzo specifico al quale inviare il pacchetto!) e contiene nel payload il numero seriale dell’inverter e l’indirizzo che vogliamo assegnare, ad esempio:

1003BW0063, 0x0012

Ossia assegnamo l’indirizzo 0x0012 all’inverter con numero seriale 1003BW0063. L’inverter con questo seriale (e solo lui) risponderà  affermativamente con un ulteriore pacchetto modbus nel quale payload sarà di nuovo specificato l’indirizzo richiesto, a conferma dell’avvenuta associazione.

Il master non fa altro che inviare una richiesta di associazione per tutti gli inverter che conosce (la lista dei numeri seriali). Ma come fa il master a sapere la lista dei seriali? Ci sono due modi possibili. O la codifichiamo la lista nel firmware della scheda NodeMCU, soluzione non esattamente flessibile, oppure usiamo il prossimo comando.

Richesta numero seriale

Il comando RICHIESTA NUMERO SERIALE è inviato dal master ad intervalli regolari per “cercare” nuovi inverter sulla linea e richiederne il codice seriale. Siccome gli inverter possono accendersi e spegnersi continuamente  per via delle differenti condizioni atmosferiche o semplicemente alla sera, è necessario inviare questo pacchetto ad intervalli regolari, tipo ogni 10 secondi, e vedere quali nuovi inverter sono disponibili in linea. Il payload è vuoto e l’indirizzo di destinazione è broadcast. Non appena un inverter si accende (tipo al mattino) riceverà questo pacchetto e risponderà con il proprio seriale (nel payload della risposta ovviamente). Il master procederà quindi ad assegnare un indirizzo e poi a ciclare continuamente con le richieste di produzione.

Comando reset

Questo comando è inviato dal master al primo riavvio tre o quattro volte di fila per resettare gli inverter e annullare eventuali associazioni indirizzo precedenti. In questo modo tutti gli inverter saranno di nuovo disponibili a farsi assegnare un nuovo indirizzo. Se non inviassimo questo comando all’avvio del master, potremmo cadere nel caso sfortunato in cui il master si riavvia per qualche motivo (ad esempio una interruzione momentanea dell’alimentazione), perde memoria degli indirizzi degli inverter, ma gli inverter stessi non accettano ulteriori associazioni indirizzo perchè ne hanno già uno impostato.

 

Firmware NodeMCU

Andiamo ora a vedere il firmware della scheda NodeMCU. Il firmware è scritto in C e contiene tutto il codice per dialogare con gli inverter tramite protocollo MODBUS e per inviare i dati al backend remoto PHP.

Il cuore del firmware è questo ciclo infinito che colleziona i dati dagli inverter e poi li invia in HTTP al server remoto

void loop()
{
  int u = 0;
  
  delay(1000);
  
  // get data from inverters
  getdata();

  // upload data online
  upload();
}

La fase di setup imposta la seriale TX/RX del NodeMCU, il GPIO 2 per selezionare il modo trasmissione / ricezione dei dati, alloca la memoria e invia 3 comandi di reset per resettare le associazioni indizzo-inverter.

void setup()
{
  Serial.begin(9600);
  pinMode(gpiod2, OUTPUT);
  delay(1000);

  memset(inverters, 0, sizeof(inverters));
  
  sendpacket(0, 4, 0); // reset
  sendpacket(0, 4, 0); // reset
  sendpacket(0, 4, 0); // reset
}

La funzione GetData() non fa altro che ciclare su tutti gli inverter attuamente conosciuti e ne richiede i dati di produzione:

void getdata()
{
  int discover = 1;
  for(int slot = 0; slot < MAX_INVERTERS; slot++) { if(discover) { discovernew(); discover = 0; } if(inverters[slot] != 0) { discover = 1; t_inverter* ptr = inverters[slot]; sendpacket(ptr->address, 0x0102, 0); // data request
      int res = readpacket();

      if(res > 0)
      {
        // copy payload
        ptr->lastdatalen = res;
        for(int p = 0; p < res; p++) ptr->lastdata[p] = payload[p];
      }
      else if(res == -1) // timeout
      {
        ptr->retry++;
        if(ptr->retry == MAX_RETRY)
        {
          free(ptr);
          inverters[slot] = 0;
        }
      }
    }     
  }
}

La funzione GetNew() invece si inframezza alle richieste di produzione e prova a cercare nuovi inverter da aggiungere alla lista di quelli già conosciuti:

int discovernew()
{  
  int slot = nextempty();
    
  if(slot < 0) return -1; // no more space left sendpacket(0, 0x0000, 0); // serial request int res = readpacket(); if(res > 0)
  {
    // choose address
    int address = slot + 1;
    char name[1024];

    // copy name
    for(int n = 0; n < res; n++) name[n] = payload[n]; name[res] = 0; sendhttp("found:" + String(name)); // build payload payload[res] = address & 0xff; sendpacket(0, 0x0001, res + 1); // pairing request res = readpacket(); if(res == 1) // payload must be 1 byte long { // confirmed t_inverter* ptr = (t_inverter*)malloc(sizeof(t_inverter)); ptr->retry = 0;
      ptr->address = address;
      strcpy(ptr->name, name);
      inverters[slot] = ptr;

      return 1;
    }
  }
  return -1;
}

Le funzioni accessorie sendpacket(), readpacket() e sendhttp() sono usate rispettivamente per inviare un pacchetto MODBUS sulla linea, riceverne uno di risposta, e per inviare i dati di produzione al server remoto tramite protocollo standard HTTP. Username e password della rete WiFi sono codificati nel firmware cosi come l’indirizzo remoto della pagina PHP; una soluzione più sofisticata potrebbe prevedere una modalità di configurazione via USB nella quale specificare tutti questi parametri.

Server e funzionamento finale

La scheda NodeMCU completa del convertitore RS485 può essere alimentata direttamente dalla porta USB di uno degli inverter. Se gli inverter non hanno una porta USB basta usare un normale caricatore del telefono da almeno 1000mA.

Ovviamente deve essere presente un segnale WiFi. Se la rete non fosse presente in prossimità degli inverter è necessario portare il doppino RS485 all’interno dell’abitazione o in prossimità, nessun problema considerando che è possibile coprire un chilometro e mezzo con RS485 a 9600 baud. Il trasmettitore funziona in completa autonomia, si collega automaticamente alla rete WiFi e si ricollega in caso di connessione internet temporaneamente assente. Nel server una semplice pagina PHP riceve i dati di produzione e ripropone gli stessi dati nel browser:

La pagina HTML può essere aperta da smartphone, tablet o PC desktop da qualsiasi parte del mondo senza essere nelle vicinanze degli inverter. Inoltre è possibile configurare il server in modo tale da inviare email a fine giornata con il totale della produzione oppure email di notifica in caso di guasti o temperatura eccessiva.

EDIT

Dato che me lo avete chiesto in tantissimi, ho deciso di pubblicare tutti i sorgenti (firmware NodeMCU e server PHP) sulla mia pagina GitHub. Ovviamente il progetto è aperto a pull request per miglioramenti (es. supportare più tipi di inverter, altri protocolli, pagina HTML più moderna, etc..)

https://github.com/gianlucag/Solarfi

Invio automatico di email con Raspberry Pi

pi2modb1gb_-comp

 

Il bello del Raspberry Pi è che può essere programmato per svolgere determinate attività ed essere lasciato acceso a fare il suo lavoro senza monitor e tastiera collegati, anche in posti remoti e non immediatamente raggiungibili. L’ideale sarebbe quindi poter tenere sotto controllo il Raspberry Pi in qualche modo: una idea interessante è quella di inviare email automaticamente ad una casella di posta prestabilita. Le email possono essere generate al verificarsi di eventi particolari oppure in maniera periodica per la trasmissione di log di sistema o altri file importanti che vogliamo tenere d’occhio.

Ad esempio, un sensore di temperatura collegato ai GPIO di sistema fa scattare l’invio di una email nel caso la temperatura salga oltre un certo limite impostato. Altro esempio: notificare al destinatario l’indirizzo IP del raspberry, con cadenza regolare oppure in maniera più efficiente solo nel caso in cui cambi (molti provider assegnano un indirizzo IP dinamico che cambia continuamente). Altro esempio ancora l’invio periodico dei dati prelevati da una stazione meteo.

Utilizziamo i pacchetti ssmtp e mutt rispettivamente per l’invio delle email e di eventuali allegati

Useremo i server smtp di gmail, quindi occorrerà avere un account gmail dedicato (ad esempio una email del tipo raspberrypi@gmail.com)

Per prima cosa aggiorniamo il repository locale:


apt-get update

Installiamo poi smmtp e mailutils


apt-get install ssmtp
apt-get install mailutils

A questo punto apriamo il file di configurazione di ssmtp


nano /etc/ssmtp/ssmtp.conf

cancelliamo tutto ed inseriamo i nostri dati di login gmail, in questo caso email “email@gmail.com” e password “123456”. Il resto rimane inviariato:

root=postmaster
mailhub=smtp.gmail.com:587
hostname=raspberrypi
AuthUser=email@gmail.com
AuthPass=123456
UseSTARTTLS=YES

A questo punto editiamo il file revaliases

nano /etc/ssmtp/revaliases

ed inseriamo questa riga

root:root@raspberrypi:smtp.gmail.com:587

fatto ciò salviamo e settiamo i giusti permessi per il file di configurazione

chmod 774 /etc/ssmtp/ssmtp.conf

Invio di una email

L’invio della email è semplicissimo. Dalla console digitiamo:

echo "testo della email" | mail -s "oggetto" destinatario@dest.com

e dopo qualche istante l’email sarà inviata. Ovviamente il comando può essere impartito da uno script apposito o da una regola cron.

Se vogliamo aggiungere un file in allegato (log, immagini o quant’altro) utilizzamo questo altro comando:

echo "testo della email" | mutt -a /file/da/allegare.dat -s "oggetto" -- destinatario@dest.com

Nel caso dovesse verificarsi un errore è possibile che gmail stia bloccando il client ssmtp perchè considerato non sicuro (vecchia versione affetta dal bug heartbleed ad esempio). In questo caso occorre prima andare nelle impostazione della casella gmail e abilitare l’accesso alle app meno sicure, come spiegato qui

screen-shot-2016-12-15-at-00-32-40