Stazione meteo con Arduino ESP DHT11 e DS3231

L’idea alla base di questo progetto è di utilizzare l’orologio DS3231 pwe fornire un interrupt periodico ad arduino (precedentemenete messo in uno stato di deep sleep per risparmiare energia); A sua volta Arduino effettua un reset di ESP che apre una connessione TCP verso due webserver: il primo è domoticz e il secondo è una piccola stazione meteo che raccoglie i dati di vari sensori.
Dopo questa operazione ESP viene messo in Deep Sleep da Arduino il quale a sua volta va nello stato di deep sleep dal quale si risveglia solo se riceve un interrupt da DS3231.
Il codice di comunicazione ESP/ Arduino ha subito una successiva reingegnerizzazione costituendo una libreria che è stata utilizzata negli articoli successivi.
Inoltre questa stesso flusso e lo stato di deep sleep possono essere utilizzati per costruire tutta una serie di sensori che si “svegliano” grazie ad interrupt segnalano quanto devono e si rimettono in deep sleep.
Questa idea è stata seguita per gli articoli successivi nei quali si descriverà la realizzazione di un sensore PIR fatto proprio in questo modo.
Di seguito lo schema del circuito e il codice commentato.
Viene qui riportata e messa a disposizione per il download la libreria per il DS3231 delle quali esistono molte versioni. Per compilare correttamente il codice occorre utilizzare questa e non quella proposta dall’IDE.
L’alimentazione del sensore e di arduino è a 5V mentre quella dell’ESP è 3,3. Si è scelto di non alimentare ESP via arduino per assicurare al modulo wi fi una alimentazione stabile, essendo il modulo soggetto a reset in caso di instabilità. Il chip LM317 provvedere a abbassare la tensione da 5 a 3,3 volt per il modulo wi fi. Lo schema fa uso di apposite resistenze (R1 e R2) e di condensatore di filtro per farne un regolatore di tensione e al contempo ridurre il rumore in ingresso all’ESP.
Il segnale proveniente dal PIR sensor arriva sul pin D2 di arduino sul quale verrà letto come interrupt via software.
Il PIN D4 di Arduino è collegato al PIN RST di ESP e su di esso passerà il segnale di reset emesso da Arduino verso ESP, una volta che Arduino stesso sia stato svegliato dal sensore PIR.
I comandi da Arduino a ESP passano attraverso i canali seriali RX e TX di ESP rispettivamente collegati sui PIN D89 e D8 di Arduino. D2 su Arduino viene utilizzato per ricevere l’Interrupt.
#include<SoftwareSerial.h> #include <DHT.h> #include <Wire.h> #include <DS3231.h> #include <avr/sleep.h> DS3231 clock; RTCDateTime dt; const byte wakePin = 2; //Porta per il segnale di risveglio - pin used for alarm clock (interrupt 0) const byte wakePinESP = 4; //Porta per il segnale di risveglio di ESP byte temp_buffer = 0b11110111; // Byte to disable SQW pin of DS3231, not logical yes, but without that the Arduino doesn't go to sleep mode even the first time char print_date[16]; // memorizzazione della data ed ora fornita da ds3231 – Actual time repository SoftwareSerial Serial1(8,9); //RX, TX pin di comunicazione dei comandi da Arduino a ESP #define DEST_HOST "192.168.1.112" //IP domoticz #define DEST_IP "192.168.1.112" //IP meteo station #define TIMEOUT 5000 // mS #define CONTINUE false //variabili per la comunicazione tra Arduino e ESP #define HALT true //variabili per la comunicazione tra Arduino e ESP #define DHTPIN 3 // 3 è il pin di Arduino a cui collego il sensore di temperatura #define DHTTYPE DHT11 // dht11 sensore di temperatura DHT dht(DHTPIN, DHTTYPE); //variabili di lettura della temperatura int lasttemp=0; //Max failure before rebooting wifi const int maxfall=2; //Minutes looping connection to server const int minConn=1; //Variabili per il wifi init int i=0,k=0; int fall=0; //Failure counter unsigned long lastTimeMillis = 0; //Last time loop execution int ledState = HIGH; // ********************** Set DS3121 SQW control bytes ********************************** void modificabyte (byte control, bool which) // aggiorna il byte di controllo su ds3231 - Set // DS3121 RTC control bytes { Wire.beginTransmission(0x68); if (which) // which=false -> 0x0e, true->0x0f. Wire.write(0x0f); else Wire.write(0x0e); Wire.write(control); Wire.endTransmission(); } // *************wake routine, activated on interrupt from ds3231 **************************** void wakeUp() { sleep_disable(); // disabilita la modalita' sleep (sveglia il sistema) // wake up again Serial.println("Woke up this morning..."); } // ****************************** sleep routine ****************************************** void sleepNow() { //Serial.print("dormo - sleep.... "); dt = clock.getDateTime(); // chiede l'ora dall'orologio digitale DS3231 - get time form DS3231 sprintf(print_date, "%02d/%02d/%d %02d:%02d:%02d", dt.day, dt.month, dt.year, dt.hour, dt.minute, dt.second); //Serial.println(print_date); // visualizza sul monitor seriale il momento dell'addormentamento – //display sleeping time on serial monitor sleep_enable(); // abilita l'addormentamento del sistema delay(100); set_sleep_mode(SLEEP_MODE_PWR_DOWN); // Select "power down mode" to maximize energy saving cli(); // disable interrupts sleep_bod_disable(); // disabilita il controllo interno di alimentazione (brown out detection) sei(); // riabilita l'interrupt - enable interrupts sleep_cpu(); // "addormenta" la cpu - sleep mode on cpu // ****************************arduino wake up here ***************************************** clock.clearAlarm1(); // elimina l'allarme (per evitare ulteriori indesiderati interrupt) clock.clearAlarm2(); // elimina l'allarme (per evitare ulteriori indesiderati interrupt) //Serial.print("sveglio - working.. "); dt = clock.getDateTime(); // chiede l'ora dall'orologio digitale DS3231 - get time form DS3231 sprintf(print_date, "%02d/%02d/%d %02d:%02d:%02d", dt.day, dt.month, dt.year, dt.hour, dt.minute, dt.second); //Serial.println(print_date); // display wake time on serial monitor } //Send AT commands from Arduino Versus ESP void sendWIFIATCommands(String cmd, int t) { int temp=0,i=0; while(1) { //Serial.println(cmd); Serial1.println(cmd); while(Serial1.available()) { if(Serial1.find("OK")) i=8; } delay(t); if(i>5) break; i++; } if(i==8) Serial.println("OK"); else Serial.println("Error"); return; } // Connect to the specified wireless network. void wifi_init() { sendWIFIATCommands("AT",100); sendWIFIATCommands("AT+CWMODE=1",100); sendWIFIATCommands("AT+CWQAP",100); sendWIFIATCommands("AT+RST",5000); //Serial.println("Connecting Wifi...."); sendWIFIATCommands("AT+CWJAP=\"SSID\",\"SSIDPWD\"",7000); //provide your WiFi username and password here } // Print error message and loop stop. void errorHalt(String msg) { //Serial.println(msg); //Serial.println("HALT"); while(true){}; } // Read characters from WiFi module and echo to serial until keyword occurs or timeout. boolean echoFind(String keyword) { byte current_char = 0; byte keyword_length = keyword.length(); // Fail if the target string has not been sent by deadline. long deadline = millis() + TIMEOUT; while(millis() < deadline) { if (Serial1.available()) { char ch = Serial1.read(); Serial.write(ch); if (ch == keyword[current_char]) if (++current_char == keyword_length) { Serial.println(); return true; } } } return false; // Timed out } // Read and echo all available module output. // (Used when we're indifferent to "OK" vs. "no change" responses or to get around firmware bugs.) void echoFlush() {while(Serial1.available()) Serial.write(Serial1.read());} // Echo module output until 3 newlines encountered. // (Used when we're indifferent to "OK" vs. "no change" responses.) void echoSkip() { echoFind("\n"); // Search for nl at end of command echo echoFind("\n"); // Search for 2nd nl at end of response. echoFind("\n"); // Search for 3rd nl at end of blank line. } // Send a command to the module and wait for acknowledgement string // (or flush module output if no ack specified). // Echoes all data received to the serial monitor. boolean echoCommand(String cmd, String ack, boolean halt_on_fail) { Serial1.println(cmd); #ifdef ECHO_COMMANDS Serial.print("--"); Serial.println(cmd); #endif // If no ack response specified, skip all available module output. if (ack == "") echoSkip(); else // Otherwise wait for ack. if (!echoFind(ack)) // timed out waiting for ack string if (halt_on_fail) errorHalt(cmd+" failed");// Critical failure halt. else return false; // Let the caller handle it. return true; // ack blank or ack found } // ******** SETUP ******** void setup() { //Serial.begin(9600); // Communication with PC monitor via USB Serial1.begin(9600); // Communication with ESP8266 via 5V/3.3V level shifter Serial1.setTimeout(TIMEOUT); //Serial.println("ESP8266 Demo"); delay(2000); clock.begin(); modificabyte(temp_buffer, 0); //call DS3231 byte modify routine clock.armAlarm1(false); clock.armAlarm2(false); clock.clearAlarm1(); clock.clearAlarm2(); pinMode(wakePin, INPUT); //la porta 2 riceve l'allarme da DS3232 - pin 2 for alarm from ds3232 pinMode(wakePinESP, OUTPUT); attachInterrupt(0, wakeUp, FALLING); // attiva l’interrupts - enable intrrupts Il primo argomento 0 significa che interrupt settato sul 2 clock.setDateTime(2018, 06, 23, 11, 29, 00); // initializes ds3231 with a dummy date and hour. } // ******** LOOP ******** void loop() { clock.setAlarm1(0, 0, 30, 10, DS3231_MATCH_M_S); //clock.setAlarm2(0, 0, 1, DS3231_MATCH_M); delay (100); Serial.println("ESP8266 A nanna"); String cmd = "AT+GSLP=3900000"; echoCommand(cmd, "OK", CONTINUE); sleepNow(); // call sleep routine // ********** Arduino return in loop section here, after woke wakeUpESP(); wifi_init(); checkfall(fall,maxfall); if (!echoCommand("AT+CIPMUX=1", "OK", CONTINUE)){ //aggiorno contatore fallimenti ed esco dal loop fall=fall+1; return; } delay(1000); // Establish TCP connection with domoticz cmd = "AT+CIPSTART=2,\"TCP\",\""; cmd += DEST_IP; cmd += "\",8080"; if (!echoCommand(cmd, "OK", CONTINUE)){ //aggiorno contatore fallimenti ed esco dal loop fall=fall+1; return; } delay(1000); // Get connection status if (!echoCommand("AT+CIPSTATUS", "OK", CONTINUE)) { //aggiorno contatore fallimenti ed esco dal loop fall=fall+1; return; } int dataArr[10] = {0}; readsensor(dataArr); // Build HTTP request. cmd = "GET /json.htm?type=command¶m=udevice&idx=15&nvalue=0&svalue=" + String(dataArr[0])+";"+ String(dataArr[1])+";"+ String(dataArr[2])+" HTTP/1.1\r\nHost: "; cmd += DEST_HOST; cmd += ":8080\r\n\r\n"; // Ready the module to receive raw data if (!echoCommand("AT+CIPSEND=2,"+String(cmd.length()), ">", CONTINUE)) { //Serial.println("Connection timeout."); ++fall; }else{ // Send the raw HTTP request echoCommand(cmd, "OK", CONTINUE); // GET delay(2000); } //Connection close echoCommand("AT+CIPCLOSE=2", "OK", CONTINUE); wifi_init(); checkfall(fall,maxfall); if (!echoCommand("AT+CIPMUX=1", "OK", CONTINUE)){ //aggiorno contatore fallimenti ed esco dal loop fall=fall+1; return; } delay(1000); // Establish TCP connection with meteo station String cmd2 = "AT+CIPSTART=4,\"TCP\",\""; cmd2 += DEST_IP; cmd2 += "\",80"; if (!echoCommand(cmd2, "OK", CONTINUE)){ //aggiorno contatore fallimenti ed esco dal loop fall=fall+1; return; } delay(1000); // Get connection status if (!echoCommand("AT+CIPSTATUS", "OK", CONTINUE)) { //aggiorno contatore fallimenti ed esco dal loop fall=fall+1; return; } // Build HTTP request. cmd2 = "GET /meteostation/insertmeteostation.php?idx=15&tempdata=" + String(dataArr[0])+"&humdata="+ String(dataArr[1])+" HTTP/1.1\r\nHost: "; cmd2 += DEST_HOST; cmd2 += ":80\r\n\r\n"; // Ready the module to receive raw data if (!echoCommand("AT+CIPSEND=4,"+String(cmd2.length()), ">", CONTINUE)) { //Serial.println("Connection timeout."); ++fall; }else{ // Send the raw HTTP request echoCommand(cmd2, "OK", CONTINUE); // GET delay(2000); } //Connection close echoCommand("AT+CIPCLOSE=4", "OK", CONTINUE); } void checkfall(int fall,int maxfall){ if (fall==maxfall){ //Reinizializzo tutte le variabili del WIFI int i=0; k=0; //Reinizializzo WIFI dopo tre tentativi andati a vuoto wifi_init(); fall=0; } } void readsensor(int *data){ data[0] = dht.readTemperature(); data[1] = dht.readHumidity(); if(data[1]<40){ data[2] = 0; return; } if(data[1]>=40 && data[1]<=60){ data[2] = 1; return; } if(data[1]>60 && data[1]<=80){ data[2] = 2; return; } if(data[1]>80){ data[2] = 3; return; } } void wakeUpESP(){ delay(5000); sendWIFIATCommands("AT",100); Serial.println("ESP8266 provo a svegliarlo"); digitalWrite(wakePinESP, HIGH); delay(100); digitalWrite(wakePinESP, LOW); delay(100); digitalWrite(wakePinESP, HIGH); delay(5000); sendWIFIATCommands("AT",100); Serial.println("ESP8266 sveglio"); }