Ott 132017
 

Oggi articolo un po’ particolare, partiamo da una delle numerose richieste di aiuto che mi sono arrivate e vediamo qual’è il problema e come lo abbiamo affrontato. Quando Alessio mi ha contattato, mi ha detto di avere un sistema abbastanza semplice con un ESP8266 collegato ad un relè ed un pulsante. Il pulsante agisce andando ad eccitare / diseccitare una bobina che a sua volta accende e spegne una luce. Inoltre il sistema ha la possibilità di rispondere ad una richiesta via WiFi per accendere e spegnere la luce da remoto. Apparentemente nulla di difficile, il problema che si verificava è legato al fatto che attivando un carico, che sia esso il forno, la lavastoviglie o il campanello, la “bobina” cambiava di stato per cui si verificava l’accensione o spegnimento della luce della cucina. Mi aveva chiesto di non ridere, ma ve lo immaginate la sera quando aspettate qualcuno, vi suonano il campanello e restate al buio?

Da dove cominciamo?

Allora, la prima cosa che ho fatto è farmi spedire il codice e lo schema di connessione. Vi allego qui sotto le foto originali che mi ha inviato così potete vedere esattamente cosa vedevo pure io. Prendetevi un attimo di pausa, guardate con attenzione e vediamo se avreste dato gli stessi consigli che ho dato io.

Innanzitutto possiamo notare che il sistema non è un semplice ESP8266 ma un NodeMCU Lua che a sua volta contiene il modulino ESP8266 montato onboard. La prima cosa che mi ha fatto storcere il naso è stato vedere che il pulsante è collegato da una parte alla 5V e dall’altra ad un ingresso digitale del LUA che però è a 3.3V. Diciamo pure che se non fosse stato per i resistori e la buona fattura dell’hardware ci sarebbero state delle belle fumate nere sin dall’inizio.
Andando poi a guardarmi la foto del LUA noto che non esiste nessun V5 out, infatti il pulsante era stato collegato al pin Vin. Ora, è possibile che la linea 5v dell’USB sia direttamente connessa a questo pin e quindi non succede nulla, ma in linea generale, non possiamo usare un pin Vin come se fosse un Vout. Nel caso specifico, essendo un Hardware OpenSource, ci sono vari modelli in commercio per cui è necessario valutare la specifica scheda per vedere se c’è un collegamento diretto oppiure no. Comunque ho fatto spostare il cavo da Vin alla 3.3V, inoltre facendo due semplici calcoli ho visto che i resistori già montati permettevano di restare al di sotto dei 12mA indicati nel datasheet dell’ESP come valore di corrente massimo. Come potete immaginare il funzionamento non è comunque cambiato, ma abbiamo ridotto il rischio di frittura del LUA.

La seconda cosa che non mi quadrava troppo era il modulo relè. Se studiate un po’, il relè montato a bordo riporta la scritta SRD05-VDC in quanto è un relè a 5V…o forse no. Andando a leggere il datasheet scoprirete che può operare anche a 3.3V anche se ovviamente con una corrente superiore. Per sicurezza ho fatto provare diversi alimentatori per verificare che non vi sia un problema di erogazione di corrente da parte dell’alimentatore, fatto poi escluso. Studiando i restanti componenti concludo che il modulo relè dovrebbe comunque funzionare anche con i 3.3V per cui dove sbattiamo la testa? Beh, non ci resta che provare a sopperire ai problemi via software.
Vediamo il software originale:

#include <ESP8266WiFi.h>
#define USE_SERIAL Serial
#include <ESP8266HTTPClient.h>

const char* ssid = "NOME_RETE";
const char* password = "PASSWORD";

int inPin = D2;
int relayPin = D0;
int val;
int timerConnection = 0;
String checkPress = "false";

WiFiServer server(8549);

void setup() {
 inizializza();
}

void inizializza()
{
 Serial.begin(115200);
 delay(10);

pinMode(inPin, INPUT);
 pinMode(relayPin, OUTPUT);

digitalWrite(relayPin, HIGH);

// Connect to WiFi network
 Serial.println();
 Serial.println();
 Serial.print("Connecting to ");
 Serial.println(ssid);

WiFi.begin(ssid, password);

while (WiFi.status() != WL_CONNECTED && timerConnection <= 20) {
 delay(500);
 timerConnection += 1;
 Serial.print(".");
 }
 if (timerConnection <= 20) {
 timerConnection = 0;
 Serial.println("");
 Serial.println("WiFi connected");

// Start the server
 server.begin();
 Serial.println("Server started");

// Print the IP address
 Serial.print("Use this URL : ");
 Serial.print("http://");
 Serial.print(WiFi.localIP());
 Serial.println("/");
 } else {
 timerConnection = 0;
 Serial.println("");
 Serial.println("WiFi not connected");

// Start the server
 server.begin();
 Serial.println("Server not started");

}
}


void loop() {

val = digitalRead(inPin); // read the input pin
 if (val == HIGH) {
 if (checkPress == "false") {
 checkPress = "true";
 digitalWrite(relayPin, LOW);
 Serial.println("Acceso");
 delay(1000);
 } else {
 checkPress = "false";
 digitalWrite(relayPin, HIGH);
 Serial.println("Spento");
 delay(1000);
 }
 }


// Check if a client has connected
 WiFiClient client = server.available();
 if (!client) {
 return;
 }

// Wait until the client sends some data
 Serial.println("new client");
 while (!client.available()) {
 val = digitalRead(inPin); // read the input pin
 if (val == HIGH) {
 if (checkPress == "false") {
 checkPress = "true";
 digitalWrite(relayPin, LOW);
 Serial.println("Acceso");
 delay(1000);
 } else {
 checkPress = "false";
 digitalWrite(relayPin, HIGH);
 Serial.println("Spento");
 delay(1000);
 }
 }

}

// Read the first line of the request
 String request = client.readStringUntil('\r');
 Serial.println(request);
 client.flush();

// Match the request

if (request.indexOf("/lucecucina=on") != -1) {
 checkPress = "true";
 digitalWrite(relayPin, LOW);
 Serial.println("Acceso");
 client.println("HTTP/1.1 200 OK");
 client.println("Content-Type: text/html");
 client.println(""); // do not forget this one
 client.println("<!DOCTYPE HTML>");
 client.println("<html>");
 client.print("ACCESA");
 client.println("</html>");
 delay(1000);
 }
 if (request.indexOf("/lucecucina=off") != -1) {
 checkPress = "false";
 digitalWrite(relayPin, HIGH);
 Serial.println("Spento");
 client.println("HTTP/1.1 200 OK");
 client.println("Content-Type: text/html");
 client.println(""); // do not forget this one
 client.println("<!DOCTYPE HTML>");
 client.println("<html>");
 client.print("SPENTA");
 client.println("</html>");
 delay(1000);
 }

}

Una prima cosa che mi fa inorridire è leggere:
String checkPress = “false”;
Qui per mantenere in memoria un valore di tipo 0 / 1, che è di tipo binario, il più semplice in assoluto, viene usata la classe String a cui è assegnata una stringa “false”, che fanno 6bytes più tutti i dati accessori che non vediamo direttamente in quanto gestiti dalla classe senza contare tutto il codice che serve a copiare i dati nella stringa, confrontarli e quant’altro. Questa cosa NON va bene anche perché ad ogni variazione di valore dobbiamo modificare la stringa e non possiamo nemmeno passarla come valore per settare un pin digitale.
String checkPress = “false”; diventerà perciò qualcosa del tipo bool checkPress=0;
Poi, a parte le porzioni di codice che gestiscono il WiFi e che inizialmente ho quasi del tutto ignorato, il codice è molto semplice e si riduce in un :

void loop() 
 {
 val = digitalRead(inPin); // read the input pin
 if (val == HIGH) {
 if (checkPress == "false") {
 checkPress = "true";
 digitalWrite(relayPin, LOW);
 Serial.println("Acceso");
 delay(1000);
 } else {
 checkPress = "false";
 digitalWrite(relayPin, HIGH);
 Serial.println("Spento");
 delay(1000);
 }
 }

La prima cosa che balza all’occhio è che manca del tutto un meccanismo di debounce. Eh? Si dai, ne ho parlato qualche volta. Quando premi un pulsante non è che lo stato dell’ingresso passa immediatamente dallo stato 0 a 1. Le superfici del metallo non sono perfettamente liscie e gli elettroni non sono li in fila che aspettano di passare tutti insieme. No, quello che succede è che per un breve periodo ci sarà un rapido susseguirsi di 0 e 1 fino allo stabilizzarsi del segnale, ma in quel lasso di tempo Arduino, il NodeMCU o qualunque altro sistema, è in grado di “leggere” queste repentine variazioni. Nella figura a lato che ho preso in rete potete vedere il fenomeno come viene visualizzato da un oscilloscopio. In questo esempio il segnale è dapprima alto, ossia il contrario del nostro sistema, e poi diventa basso, ma in tutta la fase intermedia vedete quanti repentini sbalzi di segnale si verificano. Se voi scrivete il codice come quello di sopra, vi basterà un “falso on” che la luce si accenderà, quindi un qualunque disturbo elettromagnetico, fluttazioni della linea di alimentazione e quant’altro, sono in grado di innescare il vostro pulsante anche se non l’avete toccato e, viceversa, quando lo usate possono essere lette multiple variazioni di stato che impediscono di avere un funzionamento stabile, perlomeno non quello che vi aspettate voi. Quindi cosa facciamo? Senza scomodare modifiche hardware, peraltro semplici ma che su una scheda già pronta non sono l’ideale, agiamo sul software in modo molto semplice. Chiediamoci: per quanto tempo abbiamo fenomeni di fluttuazione del segnale prima che si stabilizzi? Chiamiamo questo tempo X. Bene, ora leggiamo continuamente l’ingresso del pulsante finché non vediamo uno stato alto, ossia la presunta pressione del pulsante stesso. Ottimo, abbiamo letto con bool ButtonState=digitalRead(inPin); e siamo arrivati a if(ButtonState==HIGH). A questo punto come discriminiamo un segnale vero da uno falso. Semplice, facciamo passare il tempo X che nel listato ho chiamato debounceDelay e facciamo una nuova lettura. Se lo stato è ancora alto, significa che il segnale è stabile a livello alto e quindi stiamo premendo il pulsante. Ovviamente il sistema non è infallibile, potremmo avere ancora dei segnali spuri al di fuori del tempo da noi preso in considerazione, l’allungamento del tempo riduce la possibilità di incappare in un falso segnale ma riduce la reattività del sistema e vi assicuro che premere il pulsante della luce e vedere la stessa accendersi con ritardo risulta molto fastidioso. A questo punto se lo stato è ancora HIGH facciamo un semplice OutStatus=!OutStatus; ossia invertiamo il valore contenuto nella variabile OutStatus che ho usato in sostituzione dell’iniziale stringa checkPress. Si noti che non essendo più una stringa ma un più semplice valore booleano, una singola istruzione NOT (!) sostituisce tutti i passaggi di controllo e riassegnazione della stringa presenti nel codice originario. Per completezza vi dico che questo metodo di debouncing non è certo l’unico, anzi, è forse l’implementazione più semplice ma il mio scopo era trovare qualcosa che funzionasse e fosse facile da capire e modificare se necessario.
Alla fine del loop la digitalWrite(relayPin, OutStatus); scrive l’attuale stato nella porta di uscita per cui se c’è stata una variazione ci sarà la variazione anche sull’uscita, in caso contrario viene riscritto lo stesso valore di prima il che non porta ad alcuna variazione pratica. Vediamo come ho trasformato il codice:

void loop() 
 {
 bool ButtonState=digitalRead(inPin);
 if(ButtonState==HIGH) 
 {
   delay(debounceDelay);
   ButtonState=digitalRead(inPin);
   if(ButtonState==HIGH) OutStatus=!OutStatus; 
 }
 digitalWrite(relayPin, OutStatus);
 }

La restante parte, quella della connessione WiFi non presentava particolari criticità se non la presenza di codice inutile e ridondante che ho snellito. Questa sezione dovrebbe preoccuparsi solamente di intercettare i comandi via web e modificare la variabile che tiene a mente lo stato di attivazione della bobina, in questo modo non appena si ripassa dal loop principale ci pensa quest’ultimo ad accendere o spegnere il relè con la digitalWrite vista poco fa.

Il codice finale è:
 #include <ESP8266WiFi.h>
 #define USE_SERIAL Serial
 #include <ESP8266HTTPClient.h>

 const char* ssid = "NOME_RETE";
 const char* password = "PASSWORD";

 int inPin = D2;
 int relayPin = D0;
 int timerConnection = 0;
 bool OutStatus = true;
 bool ButtonState;
 unsigned long debounceDelay = 140; //ms

 WiFiServer server(8549);

void setup() 
 {
 Serial.begin(115200);
 delay(10);

 pinMode(inPin, INPUT);
 pinMode(relayPin, OUTPUT);

 digitalWrite(relayPin, OutStatus);

// Connect to WiFi network
 Serial.println();
 Serial.println();
 Serial.print("Connecting to ");
 Serial.println(ssid);

 WiFi.begin(ssid, password);

 while (WiFi.status() != WL_CONNECTED && timerConnection <= 20) 
 {
  delay(500);
  timerConnection += 1;
  Serial.print(".");
 }
 if (timerConnection <= 20) {
 timerConnection = 0;
 Serial.println("");
 Serial.println("WiFi connected");

// Start the server
 server.begin();
 Serial.println("Server started");

// Print the IP address
 Serial.print("Use this URL : ");
 Serial.print("http://");
 Serial.print(WiFi.localIP());
 Serial.println("/");
 } else {
 timerConnection = 0;
 Serial.println("");
 Serial.println("WiFi not connected");

// Start the server
 server.begin();
 Serial.println("Server not started");

 }
 }



void loop() 
{
 ButtonState=digitalRead(inPin);
 if(ButtonState==HIGH) 
 {
 delay(debounceDelay);
 ButtonState=digitalRead(inPin);
 if(ButtonState==HIGH) OutStatus=!OutStatus; 
 }
 
 digitalWrite(relayPin, OutStatus);


// Check if a client has connected
 WiFiClient client = server.available();
 if (client) 
 {

Serial.println("new client");

String request = client.readStringUntil('\r');
 Serial.println(request);
 client.flush();

// Match the request
 client.println("HTTP/1.1 200 OK");
 client.println("Content-Type: text/html");
 client.println(""); // do not forget this one
 client.println("<!DOCTYPE HTML>");
 client.println("<html>");
 
 if (request.indexOf("/lucecucina=on") != -1) 
 {
 OutStatus = true;
 Serial.println("Acceso");
 client.print("ACCESA");

}
 else if (request.indexOf("/lucecucina=off") != -1) {
 OutStatus = false;
 Serial.println("Spento");
 client.print("SPENTA");
 }
 client.println("</html>"); 
 delay(100); 
 } // client connected

} //loop

Bene, non ci crederete ma è tutto qui. Quello che poteva sembrare un problema hardware è stato corretto con semplici modifiche del software. Ovviamente il percorso di individuazione del problema non è stato così lineare come nell’articolo, vivendo a 500Km di distanza il tutto è stato fatto online nel tempo libero di entrambi e cercare di modificare qualcosa che non si ha fra le mani è sempre molto difficile. Uno dei problemi più grossi che incontro quando qualcuno mi chiede aiuto è cercare di capire per prima cosa il grado di competenze della controparte e le disponibilità tecnologiche a sua disposizione. Ora resta comunque un problema, infatti benchè il software funzioni come previsto ed il campanello non sia più in grado di accendere e spegnere la luce, la bobina non sempre si eccita anche se il pin del NodeMCU gli dice di farlo. Questo è sicuramente un problema legato all’insufficiente tensione / corrente.