Stazione meteo con Arduino ESP DHT11 e DS3231

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.

Arduino-DS3231-master

meteostation2_schem

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&param=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");
  }