Come realizzare un irrigatore automatico comandato da Telegram con il NodeMCU ESP8266

Oggigiorno l’informatica e l’elettronica pervadono e controllano quasi ogni aspetto della nostra vita in modo completamente automatico. Ci sono sistemi elettronici che hanno tante e diverse funzionalità, talvolta sono un po’ invasivi ma spesso sono molto utili e ci aiutano nelle nostre attività quotidiane. Uno dei campi dove certi dispositivi sono estremamente indicati è il giardinaggio, in particolare nel controllo automatico della irrigazione delle piante. Personalmente ho avuto l’esigenza di controllare a distanza l’irrigazione di alcune piante, monitorando alcuni parametri come la temperatura e l’umidità dell’aria e l’umidità del terreno. Avrei potuto tranquillamente comprare un dispositivo commerciale già pronto per questo scopo ma ho preferito provare a realizzarlo da solo perchè è più divertente ed istruttivo e perché ho potuto implementare le funzioni che mi servivano.
Ho quindi deciso di proporlo in questo blog.

Breve descrizione delle funzionalità implementate

Iniziamo a descrivere le varie funzionalità che verranno implementate in questo progetto.
Il dispositivo è in grado di comandare un’elettrovalvola in modo da chiuderla o aprirla per interrompere o meno il flusso d’acqua di un tubo da giardino. È in grado di rilevare la temperatura e l’umidità dell’aria attraverso un apposito sensore. Può misurare la quantità di umidità nel terreno in cui si trova la pianta. È anche in grado di connettersi a Internet tramite una connessione WiFi sul nostro modem di casa. A cosa serve la connessione Internet? Innanzitutto, il dispositivo è in grado di connettersi a un server NTP in modo da impostare automaticamente data e ora. Inoltre la connessione Internet viene utilizzata per monitorare e controllare il dispositivo tramite un bot Telegram appositamente creato. Attraverso questo bot possiamo leggere da remoto la temperatura e l’umidità dell’aria, l’umidità del suolo, lo stato dell’elettrovalvola (se è aperta o chiusa). Possiamo anche leggere la data e l’ora correnti e se il dispositivo è impostato sul funzionamento automatico o manuale. Possiamo però anche inviare dei comandi: possiamo farlo funzionare manualmente (accendendo o spegnendo l’elettrovalvola dando un comando su Telegram) o automaticamente (impostando, sempre da Telegram, gli orari di accensione e spegnimento dell’elettrovalvola).

Ovviamente, ci sono molte possibilità. L’utente può sviluppare nuove funzionalità o modificare quelle esistenti. Ad esempio, potrebbe aggiungere più timer in modo che il dispositivo possa attivarsi automaticamente più volte al giorno (o solo in determinati giorni della settimana). Oppure decidere di attivare l’irrigazione in automatico se l’umidità del terreno è inferiore ad un certo valore e così via.

Di che componenti abbiamo bisogno per l’irrigatore automatico?

La lista dei componenti non è particolarmente lunga:

  • una breadboard per connettere la NodeMCU ESP8266 agli altri componenti
  • alcuni fili DuPont (maschio – maschio, maschio – femmina, femmina – femmina)
  • un sensore DHT22
  • un resistore da 10kΩ e uno da 4.7kΩ
  • un modulo con doppio relè optoisolato
  • un connettore per batteria da 9V
  • una batteria da 9V
  • una elettrovalvola bistabile da 9V
  • e, ovviamente, una NodeMCU ESP8266 !

Un esempio di modulo con doppio relè optoisolato usato in questo progetto
Un esempio di modulo con doppio relè optoisolato usato in questo progetto

Un esempio di elettrovalvola bistabile a 9V usata in questo progetto
Un esempio di elettrovalvola bistabile a 9V usata in questo progetto
Un connettore per batteria da 9V
Un connettore per batteria da 9V

La batteria da 9V ci serve per alimentare l’elettrovalvola.

Vediamo brevemente come funziona un’elettrovalvola

Principalmente esistono due tipi di elettrovalvole, una monostabile e una bistabile.
L’elettrovalvola monostabile è alimentata con tensione alternata mentre la bistabile con tensione continua.
In questo progetto utilizzeremo un’elettrovalvola bistabile. L’elettrovalvola bistabile ha due stati stabili, uno per la valvola chiusa e uno per la valvola aperta. Alimentando il solenoide, la valvola si apre e rimane aperta anche togliendo l’alimentazione.
Per chiuderla dobbiamo alimentare il solenoide invertendo la polarità, anche in questo caso togliendo alimentazione la valvola rimane chiusa. Il vantaggio principale è un consumo elettrico minimo, dato che per aprire e chiudere la valvola è necessario alimentarla solo per pochi millisecondi, questo permette di alimentarla utilizzando una batteria.
L’elettrovalvola necessita di un doppio modulo relè optoisolato. I due relè sono opportunamente pilotati da due uscite digitali del NodeMCU ESP8266 per applicare una tensione diretta o inversa all’elettrovalvola, a seconda che si voglia aprirla o chiuderla.

Come è strutturato questo tutorial?

Per spiegare il progetto nel modo più semplice possibile, il tutorial procederà per fasi incrementali. Inizieremo con l’implementazione più semplice e aggiungeremo gradualmente nuove funzionalità:

  • come primo passo collegheremo l’elettrovalvola al NodeMCU ESP8266 e la comanderemo con un’alternanza di comandi ciclici di apertura e chiusura definiti nella funzione loop;
  • come secondo passaggio collegheremo il sensore di umidità e temperatura DHT22 e leggeremo ciclicamente i valori misurati tramite il Serial Monitor;
  • come terzo passaggio aggiungeremo la connettività wireless e le funzionalità di Telegram e vedremo come connettere il nostro dispositivo a Internet e come comandarlo/monitorarlo tramite il bot di Telegram;
  • poi implementeremo il sensore di umidità del suolo;
  • infine useremo la EEPROM del NodeMCU per poter salvare alcune impostazioni in modo che si mantengano anche in caso di assenza di alimentazione elettrica.

Connettiamo e testiamo l’elettrovalvola

Prima di tutto colleghiamo il NodeMCU ESP8266, il modulo relè e l’elettrovalvola utilizzando la breadboard e i fili seguendo lo schema Fritzing di seguito:

Schema Fritzing per il primo step
Schema Fritzing per il primo step

Attualmente il modulo doppio relè non è presente in Fritzing ma, se vuoi usarlo nei tuoi progetti, puoi scaricarlo da qui.

Come puoi vedere, questo schema è molto semplice: i due pin Vin e GND del NodeMCU ESP8266 vengono utilizzati per alimentare il modulo a doppio relè, collegando il pin Vin (lato NodeMCU) al pin VCC (lato modulo a doppio relè) e il due pin GND (massa).

Altre due connessioni sono utilizzate per controllare il modulo relè e sono il filo arancione e il filo marrone che collegano le due uscite digitali D1 e D2 del NodeMCU agli ingressi IN1 e IN2 del modulo relè.

Noterai che sul modulo relè è presente un ponticello (disegnato in azzurro sul connettore sinistro) che collega i morsetti JD-VCC e VCC. Questo ponticello viene utilizzato per alimentare il modulo relè attraverso i terminali VCC e GND sul connettore destro. Senza questo ponticello, siamo costretti ad alimentare il modulo con un alimentatore esterno.

Infine, collegate ai contatti del relè, abbiamo la batteria da 9V e l’elettrovalvola in modo da poterla alimentare con polarità opposta a seconda che la vogliamo aprire o chiudere.

Vediamo ora il software per il controllo dell’elettrovalvola. Useremo per questo passaggio il noto IDE PlatformIO. Se non sai come creare un progetto per NodeMCU ESP8266 con PlatformIO e come aggiungere librerie esterne al progetto, puoi leggere il tutorial Come creare un progetto per NodeMCU ESP8266 con PlatformIO in questo stesso blog.

Per scaricare il codice per questa prima fase del progetto, fai clic di seguito:

Al momento il codice è solo nel file main.cpp. Quindi, nel progetto appena creato, sostituisci il file main.cpp con quello che hai scaricato dal link sopra.
Ora vediamo come appare il codice:

#include <Arduino.h>

#define  RELAY_1  5
#define  RELAY_2  4

All’inizio abbiamo l’inclusione della libreria Arduino (questa libreria è stata inclusa automaticamente da PlatformIO) e la definizione dei pin digitali che useremo per pilotare il modulo relay. Questi pin sono GPIO4 e GPIO5 e corrispondono, sul NodeMCU, alle uscite D2 e ​​D1, come possiamo vedere dall’immagine del pinout qui sotto:

Il pinout del NodeMCU ESP8266
Il pinout del NodeMCU ESP8266

Di seguito abbiamo l’implementazione delle funzioni che aprono/chiudono l’elettrovalvola (open_valve e close_valve) e quelle che le tolgono l’alimentazione (reset_valve_high e reset_valve_low) mettendo i due pin allo stesso potenziale:

void reset_valve_high() {
  // Sends 0V to the valve setting HIGH both control pins.
  digitalWrite(RELAY_1, HIGH);
  digitalWrite(RELAY_2, HIGH);  
}

void reset_valve_low() {
  // Sends 0V to the valve setting LOW both control pins.
  digitalWrite(RELAY_1, LOW);
  digitalWrite(RELAY_2, LOW);  
}


void open_valve() {
  // Sends a 10ms impulse to open the valve.
  digitalWrite(RELAY_1, LOW);
  digitalWrite(RELAY_2, HIGH);
  delay(10); 
  Serial.println("Valve opened. Water Flowing\n");
}


void close_valve() {
  // Sends a 10ms impulse to open the valve.
  digitalWrite(RELAY_1, HIGH);
  digitalWrite(RELAY_2, LOW);
  delay(10);
  Serial.println("Valve closed. Water not Flowing\n");
}

L’elettrovalvola si apre ponendo il pin RELAY_2 su HIGH (cioè 1) e il pin RELAY_1 su LOW (cioè 0) per 10 ms (riga delay(10); ) e si chiude invertendo lo stato dei due pin.

A seconda del modello di elettrovalvola, potrebbe essere necessario modificare questo intervallo di tempo.

Nella funzione di setup inizializziamo la porta seriale, definiamo i pin RELAY_1 e RELAY_2 come uscite digitali e diamo il comando di chiusura all’elettrovalvola:

void setup() {
  Serial.begin(9600);
  pinMode(RELAY_1, OUTPUT);
  pinMode(RELAY_2, OUTPUT);

  close_valve();
  reset_valve_high();
}
void loop() {
  open_valve();
  reset_valve_low();
  delay(5000);
  

  close_valve();
  reset_valve_high();
  delay(5000);
}

Nella funzione loop apriamo e chiudiamo ciclicamente l’elettrovalvola (visto che siamo in un loop infinito). Ogni volta che apriamo o chiudiamo l’elettrovalvola, dobbiamo togliere la sua alimentazione (funzioni reset_valve_low e reset_valve_high) per evitare di scaricare inutilmente la batteria da 9V. Il cambio di stato avviene ogni 5 secondi (delay(5000);)

Ora collega la scheda al computer tramite il cavo USB e aggiorna il firmware utilizzando il pulsante Upload. Quindi apri il Serial Monitor per vedere i messaggi provenienti dalla scheda.

Pulsanti Upload e Serial Monitor su PlatformIO
Pulsanti Upload e Serial Monitor su PlatformIO

Se tutto funziona correttamente dovrai sentire i relè e l’elettrovalvola scattare ogni 5 secondi e vedere, sulla finestra del Serial Monitor, i messaggi provenienti dalla scheda:

Messaggi del Serial Monitor
Messaggi del Serial Monitor

Connettiamo e testiamo il sensore DHT22

Ora facciamo un ulteriore passo avanti aggiungendo il sensore DHT22 e leggendo la temperatura e l’umidità dell’ambiente. Diamo quindi un’occhiata al seguente schema elettrico:

Schematico Fritzing per il secondo step
Schematico Fritzing per il secondo step

Come puoi vedere, l’alimentazione per il DHT22 è presa dall’uscita 3.3V del NodeMCU (pin 3V3). E’ necessario alimentare il sensore con 3.3V in modo che anche la sua uscita sia 3.3V in quanto i pin digitali del NodeMCU non accettano tensioni superiori a 3.3V.

ATTENZIONE: nel NodeMCU ESP8266 la tensione massima tollerata dagli ingressi digitali è pari a 3.3V. Qualsiasi tensione superiore lo danneggerebbe irreparabilmente!!

Il pin di uscita è collegato all’alimentatore tramite una resistenza di pull-up da 4.7kΩ e poi, tramite il filo bianco, al pin D5 (che corrisponde a GPIO14).

Ora diamo un’occhiata alla parte del codice.
Innanzitutto aggiungiamo al progetto la libreria DHT sensor library for ESPx by Bernd Giesecke come abbiamo già fatto nell’articolo Come creare un progetto per NodeMCU ESP8266 con PlatformIO.

Quindi scarichiamo il progetto dal link sottostante:

Sostituisci il contenuto del file main.cpp del progetto con quello nel file main.cpp appena scaricato. Ora vediamo le modifiche.

#include <Arduino.h>
#include "DHTesp.h"

DHTesp dht;

#define  RELAY_1  5
#define  RELAY_2  4
#define  DHT22_PIN 14

È stata inclusa la libreria DHTesp.h che abbiamo appena aggiunto al progetto . È stato istanziato l’oggetto dht di tipo DHTesp e quindi è stato definito il GPIO per la lettura dei dati del sensore (GPIO14 che corrisponde al pin D5 della scheda, vedi la figura del pinout sopra).

void setup() {
  Serial.begin(9600);
  pinMode(RELAY_1, OUTPUT);
  pinMode(RELAY_2, OUTPUT);

  close_valve();
  reset_valve_high();

  dht.setup(DHT22_PIN, DHTesp::DHT22); // Connect DHT sensor to GPIO 14 (D5)
}

La funzione setup non è molto diversa dal caso precedente. Si differenzia per la presenza del comando di inizializzazione del DHT22 che collega il sensore al GPIO14 (pin D5).

void loop() {
  open_valve();
  reset_valve_low();
  delay(5000);
  

  close_valve();
  reset_valve_high();
  delay(5000);


  delay(dht.getMinimumSamplingPeriod());

  float humidity = dht.getHumidity();
  float temperature = dht.getTemperature();

  Serial.print("Status: ");
  Serial.print(dht.getStatusString());
  Serial.print("\thumidity: ");
  Serial.print(humidity, 1);
  Serial.print("%\t\t");
  Serial.print("temperature: ");
  Serial.print(temperature, 1);
  Serial.print("°C\n");
}

La funzione loop definisce le due variabili float temperature e humidity e le riempie con i valori letti dal sensore tramite le due funzioni getHumidity e getTemperature. Dopodiché stampa ciclicamente la lettura effettuata sul Serial Monitor.

Ora collega la scheda al computer tramite il cavo USB e aggiorna il firmware utilizzando il pulsante Upload. Quindi apri il Serial Monitor per vedere i messaggi provenienti dalla scheda:

I messaggi del Serial monitor
I messaggi del Serial monitor

Sempre più difficile!! Aggiungiamo la connessione ad Internet e a Telegram

Per questo passaggio dobbiamo aggiungere altre due librerie al nostro progetto: WiFiManager (che serve per gestire la connessione Internet via WiFi) e UniversalTelegramBot (che serve per interfacciare il nostro progetto ai bot di Telegram). Se hai seguito la procedura descritta nell’articolo Come creare un progetto per NodeMCU ESP8266 con PlatformIO dovresti aver già installato le librerie necessarie. Anche la libreria ArduinoJson è stata scaricata perché è una dipendenza della libreria UniversalTelegramBot.

Quindi, scarichiamo il codice dal link sottostante:

Anche in questo caso dobbiamo prendere il file main.cpp appena scaricato e sostituirlo con quello presente nel progetto. Dobbiamo anche copiare il file mylibrary.h che si trova nella cartella include nella cartella include del progetto.

Prima di commentare il codice e vedere come funziona, faremo due operazioni preliminari: collegare la scheda a Internet e creare il bot di Telegram con cui interfacciarla.

Come connettere la scheda ad Internet

Collega la scheda al computer tramite il cavo USB e aggiorna il firmware utilizzando il pulsante Upload. Quindi apri il Serial Monitor per vedere i messaggi provenienti dalla scheda.

Per prima cosa la scheda va in modalità Access Point e ci fornirà un indirizzo IP che useremo a breve. Questa operazione serve per connettere la scheda ad Internet senza dover inserire nel codice i parametri della rete WiFi (SSID e password).

La scheda ci fornisce il suo indirizzo IP
La scheda ci fornisce il suo indirizzo IP

In questo caso l’indirizzo IP è 192.168.4.1.

A questo punto il NodeMCU è in modalità Access Point (con SSID AutoConnectAP) e dobbiamo connettere il nostro computer alla rete AutoConnectAP. Se andiamo nel menu delle reti del nostro computer, dovremmo vedere anche la rete AutoConnectAP nell’elenco delle reti wireless.

Lista delle reti WiFi disponibili
Lista delle reti WiFi disponibili

Connetti il computer alla rete AutoConnectAP. Quindi vai sul tuo browser e inserisci l’IP precedentemente fornito dal NodeMCU: 192.168.4.1

Vedrai una schermata come questa:

La schermata del browser per scegliere la rete
La schermata del browser per scegliere la rete

Clicca il bottone ConfigureWiFi. Ti mostrerà le reti disponibili:

Lista delle reti disponibili
Lista delle reti disponibili

Scegli la SSID della tua rete:

Scegli la tua rete
Scegli la tua rete

Inserisci la password della tua rete e clicca il bottone save:

Inserisci la password
Inserisci la password

La risposta della scheda
La risposta della scheda

Se la scheda si connette correttamente, dovresti vedere un messaggio come questo sul Serial Monitor:

Messaggio di corretta connessione alla rete
Messaggio di corretta connessione alla rete

Il NodeMCU conserva memorizzati i parametri di accesso anche se lo spegni, li ricorderà al riavvio e si ricollegherà automaticamente senza dover ripetere questa procedura. Solo se lo resetti rimuovendo il commento da questa riga

// wm.resetSettings();

perderà i parametri di connessione.

Nota Bene: il dispositivo può memorizzare solo una rete. Se successivamente lo colleghi a un’altra rete, dimenticherà le impostazioni della rete precedente.

Come creare un bot Telegram

Telegram è un’applicazione di messaggistica istantanea e VoIP che può essere installata sul tuo smartphone (Android e iPhone) o computer (PC, Mac e Linux). Telegram ti consente di creare bot con cui il nostro dispositivo può interagire.

Creiamo ora il nostro bot!

Se non hai già Telegram, installalo e poi cerca il bot botFather. Fai clic sull’elemento visualizzato. Apparirà la seguente schermata:

Prima schermata del bot botFather
Prima schermata del bot botFather

Digita il comando /start per leggere le istruzioni:

Le istruzioni per la creazione del bot
Le istruzioni per la creazione del bot

Ora digita il comando /newbot per creare il tuo bot. Dagli un nome e uno username:

La creazione del nuovo bot
La creazione del nuovo bot

Se il tuo bot è stato creato con successo, riceverai un messaggio con un link per accedere al bot e al token del bot.
Salva il token del bot perché ti servirà in seguito affinché l’ESP8266 possa interagire con il bot.

Ecco come appare la schermata in cui è scritto il token del bot:

Il token del bot
Il token del bot

Chiunque conosca il nome utente del tuo bot può interagire con esso. Per filtrare i messaggi in modo da ignorare quelli che non provengono dal tuo account Telegram, devi utilizzare il tuo ID utente Telegram. Pertanto, quando il tuo bot Telegram riceve un messaggio, il nostro ESP8266 saprà se proviene da noi (e quindi lo elaborerà) o da altri (e quindi lo ignorerà). Ma…..come troviamo questo ID?

Nel tuo account Telegram, cerca IDBot e avvia una conversazione con quel bot:

La prima schermata di IDBot
La prima schermata di IDBot

Quindi digita il comando /getid e lui ti risponderà col tuo ID:

Il risultato del comando /getid
Il risultato del comando /getid

A questo punto abbiamo creato il nostro bot e abbiamo tutti gli elementi per interfacciarlo con il nostro dispositivo: lo username, il token e lo userid.

Diamo ora un’occhiata al codice

Ora esamineremo le nuove parti del codice e le commenteremo passo dopo passo.

#include <Arduino.h>
#include "DHTesp.h"

#include <WiFiManager.h>
#include <WiFiClientSecure.h>

#include <UniversalTelegramBot.h>   // Universal Telegram Bot Library written by Brian Lough: https://github.com/witnessmenow/Universal-Arduino-Telegram-Bot

#include <ArduinoJson.h>

#include "time.h"

#include "mylibrary.h"

Inizialmente includiamo le librerie di cui abbiamo bisogno. Le usiamo per gestire il DHT22, la connessione WiFi e la connessione al bot di Telegram. La libreria ArduinoJson è una dipendenza della libreria UniversalTelegramBot. La libreria time.h viene utilizzato per gestire la data e l’ora correnti. Altre librerie le abbiamo viste nei passi precedenti. Abbiamo aggiunto la libreria mylibrary.h nella cartella include che contiene una funzione di supporto (splitString) che viene utilizzata per dividere le stringhe ad ogni occorrenza di un dato carattere separatore. La usiamo qui per dividere i comandi di Telegram sul carattere separatore “:”. La funzione prende tre parametri: la stringa data da dividere, il carattere separatore e l’indice della parola che vogliamo estrarre.

Per esempio:

  Serial.println(splitString("pippo:pluto:paperino", ':', 0));
  Serial.println(splitString("pippo:pluto:paperino", ':', 1));
  Serial.println(splitString("pippo:pluto:paperino", ':', 2));

Ad esempio: tutte queste istruzioni splittano la stringa “pippo:pluto:paperino” sul carattere separatore “:”. Il primo risultato sarà “pippo” in quanto nella funzione abbiamo dato l’indice 0, il secondo sarà “pluto” perché l’indice dato è 1, il terzo sarà “paperino” perché l’indice dato è 2.

// Initialize Telegram BOT
#define BOTtoken "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"  // your Bot Token (Get from Botfather)

// Use @myidbot to find out the chat ID of an individual or a group
// Also note that you need to click "start" on a bot before it can
// message you
#define CHAT_ID "1111111111"

Qui dobbiamo inserire i parametri token e Telegram User ID che abbiamo ottenuto nel passaggio precedente quando abbiamo creato il bot di Telegram.

#ifdef ESP8266
  X509List cert(TELEGRAM_CERTIFICATE_ROOT);
#endif

WiFiClientSecure client;
UniversalTelegramBot bot(BOTtoken, client);

Qui gestiamo il certificato per il bot di Telegram e definiamo il client WiFi e il bot di Telegram.

// Checks for new messages every 1 second.
int botRequestDelay = 1000;
unsigned long lastTimeBotRan;

int devid = 3;

int valveStatus, lastStatus, timerStatus = 0;

String dd = "0";
String mm = "0";
String yyyy = "0";
String hh = "0";
String mnts = "0";
String ss = "0";
String dweek = "0";

String hhon = "NaN";
String mmon = "NaN";
String hhoff = "NaN";
String mmoff = "NaN";

Qui inizializziamo alcune delle variabili utilizzate nel codice.

const char* ntpServer = "pool.ntp.org";

const long  gmtOffset_sec = 0;
const int   daylightOffset_sec = 3600;
int ntpStatus = 0;

Qui definiamo:

  • il nome del server NTP
  • la variabile gmtOffset_sec che regola l’offset UTC per la tua timezone (in secondi). Riferisciti alla lista degli offset UTC
  • la variabile daylightOffset_sec che cambia a seconda dell’ora solare o legale (in secondi). Mettila uguale a 3600 se il tuo paese osserva l’ora legale; altrimenti mettila a 0
  • la variabile ntpStatus per conservare lo stato del server NTP (se è disponibile o no).

Poi c’è la funzione

void handleNewMessages(int numNewMessages) 

che è usata per gestire i messaggi provenienti dal bot Telegram.

Questa parte

for (int i=0; i<numNewMessages; i++) {
    // Chat id of the requester
    String chat_id = String(bot.messages[i].chat_id);
  //  Serial.println(chat_id);
    if (chat_id != CHAT_ID){
      bot.sendMessage(chat_id, "Unauthorized user", "");
      continue;
    }

filtra l’utente. Se non è autorizzato, il messaggio verrà scartato.

Da qui in poi c’è la vera e propria gestione dei comandi Telegram. Vediamolo più nel dettaglio.

Il comando /help fa sì che il bot di Telegram visualizzi l’elenco dei comandi disponibili e una breve spiegazione di ciascun comando:

if (text == "/help") {

      String welcome = "Welcome, " + from_name + ".\n";
      welcome += "Use the following commands to control your outputs:\n\n";
      welcome += "/devid to request the ID of the device\n\n";
      welcome += "/von to turn valve ON\n\n";
      welcome += "/voff to turn valve OFF\n\n";      
      welcome += "/vstate to request the current valve state\n\n";
      welcome += "/tstate to request the current timer state\n\n";
      welcome += "/ton to turn timer ON\n\n";
      welcome += "/toff to turn timer OFF\n\n";
      welcome += "/onoffstate to request the current ON or OFF state\n\n";
      welcome += "/date to request the current date and hour\n\n";
      welcome += "/th to request the temperature and humidity\n\n";
      // welcome += "/sdh to set the current date and hour\n";
      // welcome += "format /sdh:dd:mm:yyyy:hh:mm:ss\n\n";
      welcome += "/seton to set the ON hour and minutes\n";
      welcome += "format /seton:hh:mm\n\n";
      welcome += "/setoff to set the OFF hour and minutes\n";
      welcome += "format /setoff:hh:mm\n\n";

      bot.sendMessage(chat_id, welcome, "");
    } 

Se digiti /help su Telegram otterrai questa risposta:

La risposta al comando /help
La risposta al comando /help

Vediamo ora più nel dettaglio i comandi:

  • /devid restituisce l’identificativo del nostro dispositivo preventivamente definito dall’utente nella variabile devid
  • /von attiva l’elettrovalvola quando il dispositivo funziona in modalità manuale (quando non è impostato alcun timer)
  • /voff spegne l’elettrovalvola quando il dispositivo funziona in modalità manuale (quando non è impostato alcun timer)
  • /vstate restituisce lo stato corrente (aperto o chiuso) dell’elettrovalvola
  • /tstate restituisce lo stato del timer (se il dispositivo è in funzione in modalità manuale o automatica)
  • /ton imposta la modalità operativa su automatico. In questo caso i comandi /von e /voff non sono operativi e l’elettrovalvola viene aperta o chiusa dal timer impostato dall’utente
  • /toff imposta la modalità operativa su manuale. In questo caso i comandi /von e /voff sono operativi e l’apertura o la chiusura dell’elettrovalvola è fatta manualmente da parte dell’utente, non dal timer
  • /onoffstate restituisce gli orari di apertura e chiusura impostati dall’utente, attivi quando il dispositivo è impostato in modalità automatica. Inizialmente le ore e i minuti sono impostati al valore Nan, il che significa che l’utente deve impostarli prima di impostare la modalità automatica
  • /date restituisce la data e l’ora correnti (ricevute dal server NTP)
  • /th restituisce la temperatura e l’umidità dell’aria
  • /seton imposta l’ora e i minuti in cui il dispositivo attiverà l’elettrovalvola (quando impostato in modalità automatica). Il formato è /seton:hh:mm (ad esempio /seton:16:30)
  • /setoff imposta l’ora e i minuti in cui il dispositivo spegnerà l’elettrovalvola (quando impostato in modalità automatica). Il formato è /setoff:hh:mm (ad esempio /setoff:18:15)

Diamo ora un’occhiata al codice:

else if (text == "/devid") {
         bot.sendMessage(chat_id, "The device ID is: " + (String) devid, "");
    } 

Semplicemente manda un messaggio contenente l’ID del dispositivo al bot.

else if (text == "/von") {
      if(timerStatus == 0) {
        bot.sendMessage(chat_id, "Valve state set to OPEN", "");
        valveStatus = 1;
        Serial.println(valveStatus);
      } else {
        bot.sendMessage(chat_id, "The timer is active, I'm working in authomatic mode.", "");
      }
      
    }    

Controlla se il dispositivo è impostato in modalità manuale o automatica. Nel primo caso imposta valveStatus a 1 (true). In entrambi i casi invia un messaggio appropriato al bot.

else if (text == "/voff") {
      if(timerStatus == 0) {
        bot.sendMessage(chat_id, "Valve state set to CLOSED", "");
        valveStatus = 0;
        Serial.println(valveStatus);
      } else {
        bot.sendMessage(chat_id, "The timer is active, I'm working in authomatic mode.", "");
      }
      
    } 

Controlla se il dispositivo è impostato in modalità manuale o automatica. Nel primo caso imposta valveStatus su 0 (false). In entrambi i casi invia un messaggio appropriato al bot.

else if (text == "/vstate") {
      Serial.println(valveStatus);
      if(valveStatus == 0) {
        bot.sendMessage(chat_id, "The valve is CLOSED", "");
      } else if(valveStatus == 1) {
        bot.sendMessage(chat_id, "The valve is OPEN", "");
      }         
    } 

Verifica la variabile valveStatus: se uguale a 0 la valvola è CHIUSA, se uguale a 1 la valvola è APERTA. Quindi invia il messaggio di stato della valvola al bot.

else if(text == "/tstate") {
      if(timerStatus == 0) {
        bot.sendMessage(chat_id, "The timer is set to OFF (manual commands).", "");
      } else {
        bot.sendMessage(chat_id, "The timer is set to ON.", "");
      }
    }

Verifica la variabile timerStatus: se uguale a 0 il timer è impostato su OFF (il dispositivo funziona in modalità manuale), se uguale a 1 il timer è impostato su ON (il dispositivo funziona in modalità automatica). Quindi invia il messaggio di stato del timer al bot.

else if (text == "/ton") {                       // sets the timer to ON
        if(hhon == "NaN" or mmon == "NaN" or hhoff == "NaN" or mmoff == "NaN"){
          bot.sendMessage(chat_id, "The hours and minutes of the timer for automatic operation have not yet been set. Set them first using the /seton and /setoff commands.", "");
        } else {
          timerStatus = 1;
          bot.sendMessage(chat_id, "Timer set to ON (authomatic mode).", "");
        }        
    } 

Imposta il timer su ON (il dispositivo funziona in modalità automatica). Se non sono impostati ora e minuti dello stato ON o di quello OFF (valori NaN) invia un messaggio di avviso al bot, altrimenti imposta timerStatus a 1 (per operare in modalità automatica).

else if (text == "/toff") {                       // sets the timer to OFF
        timerStatus = 0;
        bot.sendMessage(chat_id, "Timer set to OFF (manual mode).", "");
    } 

Imposta la modalità operativa su manuale impostando timerStatus a 0 e invia un messaggio di stato al bot.

else if(text == "/onoffstate") {
      bot.sendMessage(chat_id, "ON: " + hhon + ":" + mmon + "  OFF: " + hhoff + ":" + mmoff, "");
    }

Invia un messaggio al bot contenente l’ora e i minuti dell’accensione e l’ora e i minuti dello spegnimento.

else if (text == "/date") {

          // Print current date

          char dateHour[29];
        
 //          = "Date: %02d/%02d/%4d %02d:%02d:%02d\n", day(t_unix_date), month(t_unix_date), year(t_unix_date), hour(t_unix_date), minute(t_unix_date), second(t_unix_date);

   //       snprintf_P(dateHour, sizeof(dateHour), PSTR("%02d/%02d/%4d %02d:%02d:%02d"), dd, mm, yyyy, hh, mnts, ss);
   
            snprintf_P(dateHour, sizeof(dateHour), PSTR("%4s %02s/%02s/%4s   %02s:%02s:%02s"), dweek, dd, mm, yyyy, hh, mnts, ss);
        
//          printf("Date: %02d/%02d/%4d %02d:%02d:%02d\n", day(t_unix_date), month(t_unix_date), year(t_unix_date), hour(t_unix_date), minute(t_unix_date), second(t_unix_date));
      
        if(ntpStatus == 1) {
          bot.sendMessage(chat_id, "Today is: " + (String) dateHour, "");
        } else {
          bot.sendMessage(chat_id, "Cannot connect to NTP time server", "");
        }
         
    }

Se il server NTP è disponibile (variabile ntpStatus impostata a 1) restituisce la data e l’ora correnti, altrimenti invia un messaggio di avviso al bot.

else if (text == "/th") {
      bot.sendMessage(chat_id, "The temperature is: " + (String) temperature + "°C, the humidity is: " + humidity + "%", "");
    }

Invia un messaggio contenente la temperatura e l’umidità dell’aria al bot.

else if (splitString(text, ':', 0) == "/seton") {                        // sets the ON hour in format hh:mm  for example  /sdh:hh:mm
        hhon = splitString(text, ':', 1);
        mmon = splitString(text, ':', 2);
        bot.sendMessage(chat_id, "Time set ON: " + hhon + ":" + mmon, "");
    } 

Imposta l’ora e i minuti in cui il dispositivo attiverà l’elettrovalvola (quando impostato in modalità automatica). Il formato è /seton:hh:mm. Estrae l’ora e i minuti dal comando, suddividendoli utilizzando la funzione splitString sul carattere separatore “:”.

else if (splitString(text, ':', 0) == "/setoff") {                       // sets the OFF hour in format hh:mm  for example  /sdh:hh:mm
        hhoff = splitString(text, ':', 1);
        mmoff = splitString(text, ':', 2);
        bot.sendMessage(chat_id, "Time set OFF: " + hhoff + ":" + mmoff, "");
    } 

Imposta l’ora e i minuti in cui il dispositivo spegnerà l’elettrovalvola (quando impostato in modalità automatica). Il formato è /setoff:hh:mm. Estrae l’ora e i minuti dal comando, suddividendoli utilizzando la funzione splitString sul carattere separatore “:”.

} else {
       bot.sendMessage(chat_id, "Unrecognized message. Please retry...", "");
    }

Se il comando ricevuto non viene riconosciuto (non è nessuno dei comandi precedenti), il dispositivo invia un messaggio di errore al bot.

bool getLocalTime(struct tm * info, uint32_t ms)
{
    uint32_t start = millis();
    time_t now;
    while((millis()-start) <= ms) {
        time(&now);
        localtime_r(&now, info);
        if(info->tm_year > (2016 - 1900)){
            return true;
        }
        delay(10);
    }
    return false;
}

void updateLocalTime()
{
  struct tm timeinfo;
  if(!getLocalTime(&timeinfo)){
    Serial.println("Failed to obtain time");
    ntpStatus = 0;
    return;
  }

  ntpStatus = 1;

  dd = String(timeinfo.tm_mday);
  mm = String(timeinfo.tm_mon + 1);
  yyyy = String(timeinfo.tm_year + 1900);
  hh = String(timeinfo.tm_hour);
  if(hh.length() == 1) {
    hh = "0" + hh;
  }
  mnts = String(timeinfo.tm_min);
  if(mnts.length() == 1) {
    mnts = "0" + mnts;
  }
  ss = String(timeinfo.tm_sec);
  if(ss.length() == 1) {
    ss = "0" + ss;
  }

  switch(timeinfo.tm_wday){

    case 1:
        dweek = "Mon";
        break;

    case 2:
        dweek = "Tue";
        break;

    case 3:
        dweek = "Wed";
        break;

    case 4:
        dweek = "Thu";
        break;

    case 5:
        dweek = "Fri";
        break;
    
    case 6:
        dweek = "Sat";
        break;

    case 7:
        dweek = "Sun";
        break;

  }

} 

Le funzioni getLocalTime e updateLocalTime vengono utilizzate per ottenere la data e l’ora correnti dal server NTP.

WiFi.mode(WIFI_STA); // explicitly set mode, esp defaults to STA+AP
  // it is a good practice to make sure your code sets wifi mode how you want it.

  Serial.begin(9600);

  //WiFiManager, Local intialization. Once its business is done, there is no need to keep it around
  WiFiManager wm;

  // reset settings - wipe stored credentials for testing
  // these are stored by the esp library
  // wm.resetSettings();

  // Automatically connect using saved credentials,
  // if connection fails, it starts an access point with the specified name ( "AutoConnectAP"),
  // if empty will auto generate SSID, if password is blank it will be anonymous AP (wm.autoConnect())
  // then goes into a blocking loop awaiting configuration and will return success result

  bool res;
  // res = wm.autoConnect(); // auto generated AP name from chipid
  // res = wm.autoConnect("AutoConnectAP"); // anonymous ap
  res = wm.autoConnect("AutoConnectAP","password"); // password protected ap

  if(!res) {
      Serial.println("Failed to connect");
      // ESP.restart();
  } 
  else {
      //if you get here you have connected to the WiFi    
      Serial.println("Connected...yeey :)");
  }

  #ifdef ESP8266
    configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);      // get UTC time via NTP
    client.setTrustAnchors(&cert); // Add root certificate for api.telegram.org
  #endif

  bot.sendMessage(CHAT_ID, "Hi! I'm online!", "");

La funzione setup contiene, oltre alle varie impostazioni viste nei passi precedenti (ad esempio l’impostazione del Serial Monitor e del DHT22), le varie impostazioni relative al server WiFi per configurare prima il dispositivo come Access Point e poi, una volta inserite le credenziali di rete, per collegarlo alla nostra rete wireless.

Infine vediamo cosa è cambiato nella funzione loop:

if(timerStatus == 0) {              // timer deactivated
    switch(valveStatus)
    {
      case 0:             // if valve state is CLOSED, closes the valve
        if(lastStatus != valveStatus) {
          close_valve();
          reset_valve_high();
        }      
        lastStatus = 0;
        break;
    
      case 1:             // if valve state is OPEN, opens the valve
        if(lastStatus != valveStatus) {
          open_valve();
          reset_valve_low();
        }
        lastStatus = 1;
        break;
    }
  } else {                            // timer activated
      updateLocalTime();
      if((hh == hhon) and (mnts == mmon)) {
        valveStatus = 1;
        if(lastStatus != valveStatus) {
          open_valve();
          reset_valve_low();
          Serial.println("Valve authomatically opened at " + hh + ":" + mnts + ".");
          bot.sendMessage(CHAT_ID, "Valve authomatically opened at " + hh + ":" + mnts + ".", "");
        }      
        lastStatus = 1;
      } else {
          if((hh == hhoff) and (mnts == mmoff)) {
            if(lastStatus != valveStatus) {
              close_valve();
              reset_valve_high();
              Serial.println("Valve authomatically closed at " + hh + ":" + mnts + ".");
              bot.sendMessage(CHAT_ID, "Valve authomatically closed at " + hh + ":" + mnts + ".", "");
            }   
            valveStatus = 0;         
          }
          lastStatus = 0;
        }
  }

Il blocco if controlla se il dispositivo sta funzionando in modalità manuale o automatica (verificando la variabile timerStatus). Se in modalità manuale, chiude la valvola se la variabile valveStatus è uguale a 0 o apre la valvola se la variabile valveStatus è uguale a 1.

Se in modalità automatica, l’ora corrente viene aggiornata e confrontata con l’ora impostata per il timer automatico. Se è uguale all’ora di accensione precedentemente impostata, la valvola verrà aperta; se è uguale all’ora di spegnimento precedentemente impostata, la valvola verrà chiusa.

if (millis() > lastTimeBotRan + botRequestDelay)  {
    int numNewMessages = bot.getUpdates(bot.last_message_received + 1);

    while(numNewMessages) {
      Serial.println("got response");
      handleNewMessages(numNewMessages);
      numNewMessages = bot.getUpdates(bot.last_message_received + 1);
    }
    lastTimeBotRan = millis();
  }

L’ultima parte serve per gestire i nuovi messaggi provenienti dal bot di Telegram.

Analizziamo ora il misuratore di umidità del terreno

Non poteva mancare una funzionalità che ci permettesse di verificare se il terreno in cui è piantata la nostra pianta è abbastanza umido o secco e necessita di essere annaffiato.

Ma come si misura il valore di questa umidità? Quale parametro fisico possiamo usare? Il parametro che utilizziamo è la conducibilità elettrica del terreno. In realtà ciò che realmente misuriamo è la sua resistività elettrica, che è l’inverso della conduttività.

Il suolo, quando è bagnato, è un buon conduttore di elettricità. Ciò significa che la sua resistenza elettrica è bassa. Quando si asciuga, però, diventa un cattivo conduttore di elettricità, quindi la sua resistenza elettrica aumenta.

Per misurarne la resistenza, lo inseriamo in un circuito molto semplice chiamato partitore di tensione.

Un partitore di tensione
Un partitore di tensione

Come puoi vedere, il partitore di tensione è costituito da due resistori: R1 e R2. Nel nostro caso, R1 rappresenta la resistenza elettrica del terreno, R2 è una normale resistenza da 10kΩ. Una tensione, che chiamiamo Vin, viene applicata al partitore. All’uscita del partitore abbiamo una tensione che chiamiamo Vout. La tensione Vout sarà una frazione della Vin (da cui il nome partitore di tensione) data dalla formula:

La formula del partitore di tensione
La formula del partitore di tensione

La tensione Vin è la nostra tensione di riferimento e deve essere costante. La prendiamo dal pin 3V3 del NodeMCU che fornisce una tensione costante a 3.3V e che abbiamo già utilizzato per alimentare il sensore DHT22.

ATTENZIONE: nel NodeMCU ESP8266 la tensione massima tollerata dall’ingresso analogico è pari a 3.3V. Qualsiasi tensione superiore lo danneggerebbe irreparabilmente!!

La resistenza R2 è fissa (la impostiamo a 10kΩ) mentre la resistenza R1 (quella del terreno) varia in base all’umidità. Pertanto la tensione Vout varierà in funzione della resistenza R1 (e quindi dell’umidità del terreno).

Non ci resta che misurare questa tensione Vout con l’ingresso analogico del NodeMCU.

Colleghiamo quindi i nuovi componenti seguendo lo schema seguente:

Lo schema elettrico completo dell'irrigatore automatico
Lo schema elettrico completo dell’irrigatore automatico

I due fili verde e giallo vanno collegati a due bastoncini metallici che vanno poi conficcati nel terreno vicino alla pianta, distanziandoli di qualche centimetro l’uno dall’altro.

Come puoi vedere, la tensione Vout viene misurata collegando il filo blu tra il punto centrale del partitore di tensione e il pin A0 del NodeMCU.

A questo punto possiamo scaricare il nuovo firmware dal link sottostante:

Aggiorniamo il file main.cpp e ricordiamoci di reinserire il token del bot e il Telegram User ID qui:

// Initialize Telegram BOT
#define BOTtoken "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"  // your Bot Token (Get from Botfather)

// Use @myidbot to find out the chat ID of an individual or a group
// Also note that you need to click "start" on a bot before it can
// message you
#define CHAT_ID "111111111"

Vediamo ora il codice relativo a questa nuova funzionalità:

#define ANALOGPIN0 A0  // ESP8266 Analog Pin ADC0 = A0
#define MAXADCRESOLUTION 1024
int soilMoistureAcquired = 0;  // value read from the pot
float soilMoistureVoltage = 0.0;
float soilMoistureThreshold = 1.0;
float powerSupply = 3.3;

Qui definiamo l’ingresso del pin analogico (A0) e la risoluzione massima del convertitore analogico-digitale (ADC) del NodeMCU. Definiamo inoltre alcune variabili e il valore dell’alimentatore che utilizziamo come riferimento di tensione (variabile powerSupply).

Nella funzione handleNewMessages abbiamo un nuovo comando:

welcome += "/sm to request the soil moisture value\n\n";

Se digitiamo /sm nel bot di Telegram, il dispositivo risponderà con il valore di tensione corrispondente al valore della resistenza elettrica del terreno:

else if (text == "/sm") {
      bot.sendMessage(chat_id, "The voltage associated to the soil moisture value is: " + (String) soilMoistureVoltage + "V", "");
    }

Nella funzione loop è presente il codice che gestisce la misura analogica dell’umidità del suolo:

soilMoistureAcquired = analogRead(ANALOGPIN0);
 
  // print the readings in the Serial Monitor
  Serial.print("sensor = ");
  Serial.print(soilMoistureAcquired);
  Serial.print("\n");
  soilMoistureVoltage = (powerSupply / MAXADCRESOLUTION) * soilMoistureAcquired;
  Serial.print("voltage = ");
  Serial.print(soilMoistureVoltage);
  Serial.print("\n");


  if (soilMoistureVoltage < soilMoistureThreshold) {
      bot.sendMessage(CHAT_ID, "The soil moisture value is too LOW! The voltage associated to the soil moisture value is: " + (String) soilMoistureVoltage + "V", "");
  }

Qui il codice acquisisce il valore dall’ADC e lo memorizza nella variabile soilMoistureAcquired. Il valore proveniente dall’ADC è compreso nell’intervallo [0 – 1024]. Questo valore viene convertito in un valore di tensione e memorizzato nella variabile soilMoistureVoltage.

Il blocco if è un allarme: se il valore misurato è inferiore alla variabile soilMoistureThreshold invierà un messaggio di allarme al bot. Ovviamente puoi impostare il valore di questa soglia in modo diverso per soddisfare le tue esigenze.

Suggerimento: è anche possibile modificare la funzionalità in modo che se il valore misurato è inferiore alla soglia, l’elettrovalvola si apre.

Infine, vediamo come salvare alcune impostazioni sulla memoria EEPROM

A cosa serve, come funziona e come si usa una memoria EEPROM?

Lo puoi vedere leggendo il nostro tutorial Come usare la memoria EEPROM sul NodeMCU ESP8266. In questo progetto la usiamo per memorizzare gli orari di accensione e spegnimento dell’elettrovalvola e lo stato del timer (cioè se il dispositivo è impostato sul funzionamento automatico o manuale). È importante salvare queste impostazioni sulla EEPROM perchè così facendo esse rimangono memorizzate anche in caso di mancanza di alimentazione. Se non facessimo così, ogni volta che si interrompe l’alimentazione dovremmo reimpostare questi valori.

Come al solito puoi scaricare il codice completo dal link qui sotto e seguire la solita procedura col file main.cpp. Ricordati sempre di ripristinare il token del bot e il Telegram User ID come già fatto nel passo precedente.

Come al solito includiamo la libreria EEPROM:

#include <EEPROM.h>                // EEPROM

In seguito inizializziamo alcune variabili:

// ---------------------- Initialization variables related to the EEPROM
String hhonEE = "";
String mmonEE = "";
String hhoffEE = "";
String mmoffEE = "";

int hhonEE_address = 2;
int mmonEE_address = 4;
int hhoffEE_address = 6;
int mmoffEE_address = 8;

String timerStatusEE = "";
int timerStatusEE_address = 10;

// ---------------------------------------------------------------------

Quindi vengono definite e inizializzate le variabili che conterranno i valori letti dalla EEPROM (sia per quanto riguarda gli orari di accensione e spegnimento dell’elettrovalvola che per quanto riguarda lo stato del timer). Vengono anche definite e settate le variabili che contengono gli indirizzi dei valori memorizzati nella EEPROM.

Abbiamo poi modificato le funzioni (attivate dal bot Telegram) che settano questi parametri:

else if (splitString(text, ':', 0) == "/seton") {                        // sets the ON hour in format hh:mm  for example  /sdh:hh:mm
        hhon = splitString(text, ':', 1);
        mmon = splitString(text, ':', 2);

        // ------------------------ writes data on EEPROM
        for(int i = 0; i < hhon.length(); i++) {
          EEPROM.write(hhonEE_address + i, hhon[i]);
        }

        for(int i = 0; i < mmon.length(); i++) {
          EEPROM.write(mmonEE_address + i, mmon[i]);
        }  

        EEPROM.commit();  
        // ----------------------------------------------

        bot.sendMessage(chat_id, "Time set ON: " + hhon + ":" + mmon, "");
    } 
    
    else if (splitString(text, ':', 0) == "/setoff") {                       // sets the OFF hour in format hh:mm  for example  /sdh:hh:mm
        hhoff = splitString(text, ':', 1);
        mmoff = splitString(text, ':', 2);

        // ------------------------ writes data on EEPROM
        for(int i = 0; i < hhoff.length(); i++) {
          EEPROM.write(hhoffEE_address + i, hhoff[i]);
        }

        for(int i = 0; i < mmoff.length(); i++) {
          EEPROM.write(mmoffEE_address + i, mmoff[i]);
        }

        EEPROM.commit();
        // ----------------------------------------------


        bot.sendMessage(chat_id, "Time set OFF: " + hhoff + ":" + mmoff, "");
    }

La /seton e la /setoff non si limitano più a valorizzare delle variabili ma scrivono i parametri nella EEPROM.

È ciò che fanno anche le funzioni /ton e /toff, come visibile sotto:

else if (text == "/ton") {                       // sets the timer to ON
        if(hhon == "NaN" or mmon == "NaN" or hhoff == "NaN" or mmoff == "NaN"){
          bot.sendMessage(chat_id, "The hours and minutes of the timer for automatic operation have not yet been set. Set them first using the /seton and /setoff commands.", "");
        } else {
          timerStatus = 1;
          
          // ------------------------ writes data on EEPROM
          timerStatusEE = "1";
          EEPROM.write(timerStatusEE_address, timerStatusEE[0]);
          EEPROM.commit();
          // ----------------------------------------------

          bot.sendMessage(chat_id, "Timer set to ON (authomatic mode).", "");
        }        
    } 

    else if (text == "/toff") {                       // sets the timer to OFF
        timerStatus = 0;

        // ------------------------ writes data on EEPROM
        timerStatusEE = "0";
        EEPROM.write(timerStatusEE_address, timerStatusEE[0]);
        EEPROM.commit();
        // ----------------------------------------------

        bot.sendMessage(chat_id, "Timer set to OFF (manual mode).", "");
    } 

Nella funzione setup abbiamo l’inizializzazione della EEPROM:

EEPROM.begin(512);  //Initializes EEPROM

e la lettura dei dati dalla EEPROM all’avvio del dispositivo:

// -------------  Reading data saved in EEPROM
  hhonEE = char(EEPROM.read(hhonEE_address));
  hhonEE += char(EEPROM.read(hhonEE_address + 1)); 
  
  mmonEE = char(EEPROM.read(mmonEE_address));
  mmonEE += char(EEPROM.read(mmonEE_address + 1));

  hhoffEE = char(EEPROM.read(hhoffEE_address)); 
  hhoffEE += char(EEPROM.read(hhoffEE_address + 1));

  mmoffEE = char(EEPROM.read(mmoffEE_address));
  mmoffEE += char(EEPROM.read(mmoffEE_address + 1)); 


  timerStatusEE = char(EEPROM.read(timerStatusEE_address));

  hhon = hhonEE;
  mmon = mmonEE;
  hhoff = hhoffEE;
  mmoff = mmoffEE;
  timerStatus = timerStatusEE.toInt();


  Serial.println("EEPROM read!");
  // --------------------------------------------

Come abbiamo visto, con poche modifiche al codice abbiamo aggiunto la possibilità di salvare le impostazioni in modo da conservarle anche in caso di mancanza di alimentazione.

Realizza il PCB del progetto

Per questo articolo è disponibile anche il progetto creato usando KiCad 6 in modo da poterlo realizzare anche su PCB.

Schermata dello schematico completo
Schermata dello schematico completo

Schermata del PCB
Schermata del PCB

Schermata del PCB in 3D
Schermata del PCB in 3D

Puoi scaricare il progetto realizzato con KiCad 6 da qui:

Considerazioni finali

Come si può vedere, il progetto è abbastanza semplice. Tuttavia ha già varie funzioni di base che gli consentono di funzionare come irrigatore automatico e che consentono all’utente di monitorarlo/controllarlo da remoto tramite Telegram.
Ovviamente è migliorabile: si possono aggiungere/modificare funzioni agendo unicamente sul software oppure si possono aggiungere altri sensori (ed implementare via software le funzionalità che li gestiscono). Non c’è limite alla fantasia.
Un miglioramento utile consiste nell’aggiunta di un modulo RTC (Real Time Clock) che consentirebbe al dispositivo di mantenere aggiornate la data e l’ora anche in assenza di connessione al server NTP (nel caso, per esempio, dovesse mancare la connessione a Internet).

Torna in alto