Use this forum to chat about hardware specific topics for the ESP8266 (peripherals, memory, clocks, JTAG, programming)

User avatar
By RainerOchs
#41616 I wanted to build a flooding sensor to detect water on the floor - my automatic irrigation system sometimes fails and floods the floor.
I want an SMS notification of the Event within short time.

The solution I found can be used for all types of surveillance applications, where only sparse events have to be reported.
It senses one input (Water Sensor or any other digital signal like a switch) and sends an email when the event is detected.
The email can be converted by my mail-provider into an SMS to my mobile.

Mi first experiments showed that the power consumption of the ESP8266 is too high for battery powered applications - there are many threads covering this issue.
The only usable mode here is deep sleep mode - I measure about 15µA with my ESP-12E.

There are however two major obstacles:
1. when waking from sleep it lasts about 300ms until it can do something useful and be sent to sleep again. In this phase, even with WiFi disabled the device draws about 15mA. If we want to wake up the device frequently to poll some inputs, overall power consumption will still be too high for battery power.
2. While in deep sleep, there is no other means to wake the device than a device reset. The deep sleep mode can end automatically, but again only via a device reset issued from GPIO16. Maximum sleep time is about 1 hour (32bit in µs) - so we have to wake it in that frequency anyway.

In my solution, I let sleep the device as much as possible, only awaking it once an hour for a short period.
The alarm event - in my case a current through the water sensor - creates a reset. As there now are two reset sources (alarm and wakeup) I need some logic in between. The incoming alarm signal has to formed to a pulse to reset the device.

I solved this with a 74HC132 and some passive components:

Image

The first stage is the water sensor, if water forms a conductor between TP1 and TP2 the input gets low - this is the alarm condition.
The output signal leads to a GPIO so the program can determine after a reset if the cause was an alarm condition. The C-R makes a reset pulse out of the stable signal, the second stage inverts it. Stage 3 works as OR between the wake signal GPIO16 and the alarm. The last stage inverts the reset to proper polarity.

Edit: There should be a Pull-Down at GPIO16 to make sure that the input of the Gate is always well defined. Oherwise unintended resets may occur.

Power Consumption:
With this design I get a power consumption that allows operation for many years out of two AAA batteries:
- With the logic I measure 19µA standby current - that makes 166mAh/year
- Waking it every hour for 300ms at 15mA makes 12mAh/pear
- Sending an email lasts about 40s at 67mA - this results in 0,75mAh per mail - my software sends once a week a status-mail so I can see that the system is working - another 40mAh/year.
=> So the sum is about 220mAh/year. - the AAA as Alkaline has about 1000mAh.

Response Times:
Response of the ESP is almost immediate, but connecting to WiFi and sending the mail lasts about 40s,within one minute from the alarm the mail is in my in-box. The SMS relay takes another minute so the SMS alarm sounds within 2minutes.

Software
I used the arduino plug-in to create the software (1.6.5). A state machine handles the different wakeup situations:
- by writing information into the RTC RAM I can detect if a reset is from Power-on or from a reset
- by checking the IO I can decide if the reset was a wakeup from sleep or an alarm event
- by counting the wakeups in a variable inside the RTC RAM I can keep time and send a status-mail once a week to monitor system functionality
- by measuring the battery voltage I can send a low-Power mail to indicate battery replacement. The voltage is also sent in every status mail.

Code: Select all#include <ESP8266WiFi.h>
ADC_MODE(ADC_VCC); //vcc read-mode

// Include API-Headers
extern "C" {
#include "ets_sys.h"
#include "os_type.h"
#include "osapi.h"
#include "mem.h"
#include "user_interface.h"
#include "cont.h"
}

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

// state definietions
#define STATE_COLDSTART 0
#define STATE_SLEEP_WAKE 1
#define STATE_ALARM 2
#define STATE_CONNECT_WIFI 4

#define ALARM_PIN 14                                 // pin to test for alarm condition
#define ALARM_POLARITY 1                            // signal tepresenting alarm
#define SLEEP_TIME 60*60*1000000                    // sleep intervalls in us
#define SLEEP_COUNTS_FOR_LIVE_MAIL 24*2*7           // send live mail every 7 days
#define SLEEP_COUNTS_FOR_BATT_CHECK 2*24            // send low-Batt mail onece a day
#define BATT_WARNING_VOLTAGE 2.4                    // Voltage for Low-Bat warning
#define WIFI_CONNECT_TIMEOUT_S 20                   // max time for wifi connect to router, if exceeded restart

// RTC-MEM Adresses
#define RTC_BASE 65
#define RTC_STATE 66
#define RTC_WAKE_COUNT 67
#define RTC_MAIL_TYPE 68

// mail Types
#define MAIL_NO_MAIL 0   
#define MAIL_WELCOME 1     // mail at startup
#define MAIL_LIVE_SIGNAL 2
#define MAIL_ALARM 3
#define MAIL_LOW_BAT 4

//#define SERIAL_DEBUG
//#define DEBUG_NO_SEND

// global variables
byte buf[10];
byte state;   // state variable
byte event = 0;
uint32_t sleepCount;
uint32_t time1, time2;

// Temporary buffer
uint32_t b = 0;

int i;

WiFiClient client;

void setup() {
   WiFi.forceSleepBegin();  // send wifi to sleep to reduce power consumption
   yield();
   system_rtc_mem_read(RTC_BASE,buf,2); // read 2 bytes from RTC-MEMORY

// if serial is not initialized all following calls to serial end dead.
#ifdef SERIAL_DEBUG
    Serial.begin(115200);
    delay(10);
    Serial.println();
    Serial.println();
    Serial.println(F("Started from reset"));
#endif   

   if ((buf[0] != 0x55) || (buf[1] != 0xaa))  // cold start, magic number is nor present
   { state = STATE_COLDSTART;
     buf[0] = 0x55; buf[1]=0xaa;
     system_rtc_mem_write(RTC_BASE,buf,2);
   }
   else // reset was due to sleep-wake or external event
   {  system_rtc_mem_read(RTC_STATE,buf,1);
      state = buf[0];
      if (state == STATE_SLEEP_WAKE) // could be a sleep wake or an alarm
      {  pinMode(ALARM_PIN, INPUT);
         bool pinState = digitalRead(ALARM_PIN);  // read the alarm pin to find out whether an alarm is teh reset cause
         Serial.printf("GPIO %d read: %d\r\n",ALARM_PIN,pinState);
         if (pinState == ALARM_POLARITY)
         {  // this is an alarm!
            state = STATE_ALARM;
         }
      }
   }

   // now the restart cause is clear, handle the different states
   Serial.printf("State: %d\r\n",state);

   switch (state)
   {  case STATE_COLDSTART:   // first run after power on - initializes
         sleepCount = 0;
         system_rtc_mem_write(RTC_WAKE_COUNT,&sleepCount,4);  // initialize counter
         buf[0] = MAIL_WELCOME;
         system_rtc_mem_write(RTC_MAIL_TYPE,buf,1); // set a welcome-mail when wifi is on
         // prepare to activate wifi
         buf[0] = STATE_CONNECT_WIFI;  // one more sleep required to to wake with wifi on
         WiFi.forceSleepWake();
         WiFi.mode(WIFI_STA);
         system_rtc_mem_write(RTC_STATE,buf,1); // set state for next wakeUp
         ESP.deepSleep(10,WAKE_RFCAL);
         yield();

      break;
      case STATE_SLEEP_WAKE:
          system_rtc_mem_read(RTC_WAKE_COUNT,&sleepCount,4);  // read counter
          sleepCount++;
          if (sleepCount > SLEEP_COUNTS_FOR_LIVE_MAIL)
          {  // its time to send mail as live signal
             buf[0] = MAIL_LIVE_SIGNAL;
             system_rtc_mem_write(RTC_MAIL_TYPE,buf,1); // set mail type to send
             // prepare to activate wifi
             buf[0] = STATE_CONNECT_WIFI;  // one more sleep required to to wake with wifi on
             WiFi.forceSleepWake();
             WiFi.mode(WIFI_STA);
             system_rtc_mem_write(RTC_STATE,buf,1); // set state for next wakeUp
             ESP.deepSleep(10,WAKE_RFCAL);
             yield();
          }
          // check battery
          if (sleepCount > SLEEP_COUNTS_FOR_BATT_CHECK)
          {  if (ESP.getVcc() < BATT_WARNING_VOLTAGE)
             {  sleepCount = 0;  // start from beginning so batt-warning is not sent every wakeup
                buf[0] = MAIL_LOW_BAT;
                system_rtc_mem_write(RTC_MAIL_TYPE,buf,1); // set mail type to send
                system_rtc_mem_write(RTC_WAKE_COUNT,&sleepCount,4);  // write counter
                // prepare to activate wifi
                buf[0] = STATE_CONNECT_WIFI;  // one more sleep required to to wake with wifi on
                WiFi.forceSleepWake();
                WiFi.mode(WIFI_STA);
                system_rtc_mem_write(RTC_STATE,buf,1); // set state for next wakeUp
                ESP.deepSleep(10,WAKE_RFCAL);
                yield();
             }
          }

          // no special event, go to sleep again
          system_rtc_mem_write(RTC_WAKE_COUNT,&sleepCount,4);  // write counter
          buf[0] = STATE_SLEEP_WAKE;
          system_rtc_mem_write(RTC_STATE,buf,1);              // set state for next wakeUp
          ESP.deepSleep(SLEEP_TIME,WAKE_RF_DISABLED);         
          yield();                                            // pass control back to background processes to prepate sleep
      break;

      case STATE_ALARM:
             buf[0] = MAIL_ALARM;
             system_rtc_mem_write(RTC_MAIL_TYPE,buf,1); // set mail type to send
             // prepare to activate wifi
             buf[0] = STATE_CONNECT_WIFI;  // one more sleep required to to wake with wifi on
             WiFi.forceSleepWake();
             WiFi.mode(WIFI_STA);
             system_rtc_mem_write(RTC_STATE,buf,1); // set state for next wakeUp
             ESP.deepSleep(10,WAKE_RFCAL);
             yield();
      break;
      case STATE_CONNECT_WIFI:
          WiFi.forceSleepWake();
          delay(500);
          wifi_set_sleep_type(MODEM_SLEEP_T);
          WiFi.mode(WIFI_STA);
          yield();

          time1 = system_get_time();
         
          // Connect to WiFi network
          Serial.println();
          Serial.println();
          Serial.print("Connecting to ");
          Serial.println(ssid);
          WiFi.begin(ssid, password);
          while (WiFi.status() != WL_CONNECTED) {
            delay(500);
            Serial.print(".");
            time2 = system_get_time();
            if (((time2 - time1) / 1000000) > WIFI_CONNECT_TIMEOUT_S)  // wifi connection lasts too ling, retry
            {   ESP.deepSleep(10,WAKE_RFCAL);
                yield();
            }
          }
          Serial.println("");
          Serial.println("WiFi connected");

          system_rtc_mem_read(RTC_MAIL_TYPE,buf,1);

          Serial.printf("Send email %d\r\n",buf[0]);

          sendEmail(buf[0]);
          // now re-initialize
          sleepCount = 0;
          system_rtc_mem_write(RTC_WAKE_COUNT,&sleepCount,4);  // initialize counter
          buf[0] = MAIL_NO_MAIL;
          system_rtc_mem_write(RTC_MAIL_TYPE,buf,1); // no mail pending
          buf[0] = STATE_SLEEP_WAKE; 
          system_rtc_mem_write(RTC_STATE,buf,1); // set state for next wakeUp
          ESP.deepSleep(SLEEP_TIME,WAKE_RF_DISABLED);         
          yield();                                            // pass control back to background processes to prepate sleep
     
      break;
   
  }
  delay(1000);
 //
 
}


void loop()
{
  delay(10);
 
     
}


Sending an email from the device needs WiFi connectivity. As the device is always woken with WiFi off there is an other short sleep-Cycle involved that wakes the device with WiFi on.

For sending the email I use a smtp2go account as SMTP relay. In theory it is possible to deliver a mail to any mail provider directly, but due to spam defense functions at the mail provider the mail may be blocked. Smtp2go offers a free and simple working solution.

The mail sender I have adapted from Erni`s code (viewtopic.php?f=32&t=6139&start=4)
Code: Select allchar server[] = "mail.smtpcorp.com";

#define HEADER_WELCOME "ESP8266 System powered on"
#define HEADER_ALARM "ESP8266 ALARM!"
#define HEADER_LIVE "ESP8266 System is working ok"
#define HEADER_LOW_BAT "ESP8266 System has low battery"

#define MAIL_USER "*******************************" // smtp2go mail adress in base64 encoding
#define MAIL_PASS "****************"  // password in base64 encoding
#define MAIL_FROM "ESP8266.Alarm@my.com" 
#define MAIL_TO "myMail@gmail.com"


// my Chip IDs to give it a cleartype name
#define WA1 14117293
#define WA2 12612352


byte sendEmail(byte MailType)
{
  byte thisByte = 0;
  byte respCode;

#ifdef DEBUG_NO_SEND
  if (MailType == MAIL_WELCOME)
    Serial.println(F(HEADER_WELCOME));
  if (MailType == MAIL_ALARM)
    Serial.println(F(HEADER_ALARM));
  if (MailType == MAIL_LIVE_SIGNAL)
    Serial.println(F(HEADER_LIVE));
  if (MailType == MAIL_LOW_BAT)
    Serial.println(F(HEADER_LOW_BAT));

  return 1;
#endif

  if (client.connect(server, 2525) == 1) {
    Serial.println(F("connected"));
  } else {
    Serial.println(F("connection failed"));
    return 0;
  }
  if (!eRcv()) return 0;

  Serial.println(F("Sending EHLO"));
  client.println("EHLO www.example.com");
  if (!eRcv()) return 0;
  Serial.println(F("Sending auth login"));
  client.println("auth login");
  if (!eRcv()) return 0;
  Serial.println(F("Sending User"));
  // Change to your base64, ASCII encoded user
   client.println(MAIL_USER);
//  client.println("xxxxxx"); //<---------User
  if (!eRcv()) return 0;
  Serial.println(F("Sending Password"));
  // change to your base64, ASCII encoded password
  client.println(MAIL_PASS);
//  client.println("yyyyyyyy");//<---------Passw
  if (!eRcv()) return 0;
  Serial.println(F("Sending From"));
  // change to your email address (sender)
  client.print(F("MAIL From: "));
  client.println(F(MAIL_FROM));
  if (!eRcv()) return 0;
  // change to recipient address
  Serial.println(F("Sending To"));
  client.print(F("RCPT To: "));
  client.println(F(MAIL_TO));
  if (!eRcv()) return 0;
  Serial.println(F("Sending DATA"));
  client.println(F("DATA"));
  if (!eRcv()) return 0;
  Serial.println(F("Sending email"));
  // change to recipient address
  client.print(F("To:  "));
  client.println(F(MAIL_TO));
  // change to your address
  client.print(F("From: "));
  client.println(F(MAIL_FROM));
  client.print(F("Subject: "));
  if (MailType == MAIL_WELCOME)
    client.println(F(HEADER_WELCOME));
  if (MailType == MAIL_ALARM)
    client.println(F(HEADER_ALARM));
  if (MailType == MAIL_LIVE_SIGNAL)
    client.println(F(HEADER_LIVE));
  if (MailType == MAIL_LOW_BAT)
    client.println(F(HEADER_LOW_BAT));

  client.println(F("This is from my ESP8266\n"));
  client.print(F("Power is: "));
  client.print(ESP.getVcc());
  client.println(F("mV"));
  int32 ChipID = ESP.getChipId();
  if (ChipID == WA1)
    client.println("Device: Water-Alarm 1");
  else if (ChipID == WA2)
    client.println("Device: Water-Alarm 2");
  else
  {  client.print(F("Device Chip ID: "));
     client.println(ChipID);
  }
 
 
  client.println(F("."));
  if (!eRcv()) return 0;
  Serial.println(F("Sending QUIT"));
  client.println(F("QUIT"));
  if (!eRcv()) return 0;
  client.stop();
  Serial.println(F("disconnected"));
  return 1;
}

byte eRcv()
{
  byte respCode;
  byte thisByte;
  int loopCount = 0;

  while (!client.available()) {
    delay(1);
    loopCount++;
    // if nothing received for 10 seconds, timeout
    if (loopCount > 10000) {
      client.stop();
      Serial.println(F("\r\nTimeout"));
      return 0;
    }
  }

  respCode = client.peek();
  while (client.available())
  {
    thisByte = client.read();
    Serial.write(thisByte);
  }

  if (respCode >= '4')
  {
    //  efail();
    return 0;
  }
  return 1;
}


There is also a function to read the Chip ID - I store the devices I have within the software and give it clear-type names to identify my devices.

Board
I have created schematic and board in eagle. I used my cnc to mill the boards. The board is about the size of the 2-AAA battery holder - I have fixed the board with foam-tape on the back side of the battery holder and put it into a small case.

Image
You do not have the required permissions to view the files attached to this post.
Last edited by RainerOchs on Wed Apr 13, 2016 3:41 pm, edited 1 time in total.
User avatar
By AdamWu
#41621 Cool design! But I have a question:
If the sole purpose of ESP8266 is to be able to send an email when flood is detected, then why not just simply power it down, until flood is detected? It seems there is no need for periodic wakeup.

Unless, of course there are other periodic tasks, such as reading temperature and/or humidity, etc.
User avatar
By PuceBaboon
#41638 I have to admit that I missed the connection to GPIO14 on my first scan through and was left scratching my head for a while, but a second, closer look at the schematic soon fixed that. :oops: Nice idea!

Cross-referencing your Chip-IDs to useful names is a pretty good idea, too.

Many thanks for sharing your code and schematics; it's a very nice project.
User avatar
By RainerOchs
#41674
AdamWu wrote:Cool design! But I have a question:
If the sole purpose of ESP8266 is to be able to send an email when flood is detected, then why not just simply power it down, until flood is detected? It seems there is no need for periodic wakeup.

Powering up would be an option, but then again I need some extra circuitry for doing that.
But having it running allows for functions like battery-monitoring and the periodic status mail. Systems that are made for standby need to be monitored - otherwise function is not guaranteed in case it is needed.