Easy battery powered Mailbox Door IoT sensor using ESP32

16 Apr 2018 | Easy battery powered Mailbox Door IoT sensor using ESP32 |

my old  mailbox at front door

This post is how I took my rusty (literally) old mailbox and made it into a battery powered smart mailbox (an IoT device) , that sends you an SMS  (text messagE) or MQTT alerts, and in the process  how I  learned about Arduino and particularly  the ESP32 chip which lets us relatively easily  setup with minimal costs and complexity for a battery powered mailbox  notification system (and many other IoT projects) .

Scouring Youtube and other sites , there are many examples of door/mailbox projects of this type online. But you will quickly discover most are not battery powered (which means  you need to run an AC cable to the box), and the few that are battery powered  generally tend to involve quite a bit of  work.

So my goal was to make for this build was to:

  • Make it complete battery powered
  • Make battery last months, using power sipping capabilities of  ESP32 –using deep sleep
  • Small and self-contained , doesn’t get in the ay of the mailbox
  • Real-time MQTT and Wifi via ESP32 chip
  • Just a small number of parts and Most importantly be easy to assemble

This project only requires a few items:

rear of mailbox showing rusted back and tapped reed sensor wire

While the door/mailbox sensor has been done many times, it serves as a very practical project that allows you to use it as a stepping stone into more complex IoT projects.

What originally brought me to playing with these micro-controllers is after having played with the Raspberry Pi and creating some simple projects like my LED scrolling ticker,

I started looking into how I can do a few IoT project and run things like the Pi Zero for long bits of time on a battery. But because Pi is a full-fledged OS even the PiZeroW  still consumes about 120/mA idling so its not practical for battery powered project and even solar powered ones require a  battery to backup a decent sized solar panel, unless you want to be switch batteries every few days. That led me to low powered micro-controllers.

Arduino and ESP8266 / ESP32 series of Micro-controllers

When I first started exploring the world of Arduino and micro controllers (for a nice video explaining an introduction into Arduino), it was pretty interesting, but while the typical Arduino Uno is ok, it  doesn’t provide too much capability besides reading GPIO pins, that is until you start adding shields (add on boards with things like sensors,  Wifi, log data  etc/). Arduino is also a large eco system , because it’s open hardware (i. the electronic schematics are published) and the popular Arduino IDE allows fairly easy development, many copies and variations of these boards exist. Arduino’s are used extensively by the DIY hobbyist and maker community for all sort of projects

What really got me excited with the Espressiff ESPxx series,which includes the ESP8266 the ESP32  of  Arduino compatible boards that already came with WiFi chips, and bluetooth communications as part of the chip, no add ons were necessary, plus they had deepSleep (power friendly modes) and were relatively inexpensive( typically less than $15 ). …

If you want to have any chance of doing long  lasting (duration of months or more on a single charge…)  battery powered projects, that are of the sensing and IoT  (internet of Things) variety , the most popular choice is the Arduino and ESPxx series chips . What makes these chips great is that they have a deepSleep mode, that sips micro Amps of  power when they are not in operation, which means even running on batteries of 2000Mah or less you can have project run possibly for many months or even years, depending on the actual power consumption while the device is in operation.

Naturally, you can’t run a large motor  or servo or make heavy continuous demands in between deep sleep, but for the vast majority of IoT sensing and reporting projects these chips work great.

Why I choose ESP32 over ESP8266 for this project

The ESP8266 and its many variants is a fantastic chip, but its successor the ESP32 is an even more powerful and feature rich chip. While you can definitely do this project using the ESP8266 , having it run as a battery powered application, the lack of of wake on a GPIO Interrupt feature makes things complicated, and you need to add a bunch more passive electronics. as outlined here,

The  ESP32 simplifies all that because it offers many Sleep modes, including the ability to do Timer and  GPIO interrupts wake up from deepsleep and plus preserve variables in RTC memory . This is all masterfully explained here, by the way do subscribe to Andreas Spiess (“Swiss accent guy”)  channel, he does an outstanding job of bringing  you the latest information and videos about microcontrollers and sensors.

For this project the ESP32 is definitely overkill in terms of capability but for the price from $7-$25 its reasonable and you can always re-purpose it for other projects that make better use of its powerful capabilities.

Hardware

ESP32 board Wemos Lolin

Here’s the list of hardware that you’ll need and the approximate prices as of early 2018. You can expect the ESP32 to come down in price as newer and more power varieties are released.

Hardware Cost Source Notes
Lolin lite ESP32  $5-$21 Amazon | eBay | AliExpress there are many other ESP32 boards-try to get one  with  with Li-Poly battery connector (JST-PH connector)
Door Windows Reed Switch $7 Amazon use either  N/O or N/C variations work
Li-Poly battery JST-PH Connector $10-$15 Amazon actual price depends on size of battery a 750maH + should work well
Project Case $7 Amazon
Total $20~ $50 could be less with smaller battery and cheaper esp32 board

Hardware: Wiring it up

breadboard wiring diagram :: your esp32 board may by different – JST-PH battery connection not shown

Here’s how I configured my ESP32 chip and the reed sensor.

  • I soldered a the pins to the ESP32 , so I could more easily connect the sensor wires, you could just solder the sensor wires directly to the GPIO holes, but then it makes harder to re-purpose this chip is you want to use it for something else.
  • standard magnetic /reed door sensor

    I attached one cable from the  reed sensor (doesn’t matter which one, since either one will close the circuit which is what triggers the action.) to the 3.3v power source pin.

  • I attached the other cable  to a 10K ? resistor , which went to the GND pin and GPIO 12 on my board) , which is where I read the value (HIGH or LOW)
  • Then I tested everything on a bread board, bringing the reed magnetic sensor close and verifying the value (HIGH or LOW),.
  • when it came putting everything into a project box, I drilled a few small holes ot feed the sensor wires through, Plugged them up with hot glue
  • project box – magnetically attached to bottom of mailbox

    Used some removable adhesive putty to affix everything inside my project case. I like putty better than hot glue here, since if I need to take the chip  out I can do it relatively easy.

  • I placed some putty on the bottom of the case and then placed my Lithium Polymer battery on top of that.. finally I attached the ESP32 board atop the batter and plugged it in. This is why its important to get a board with a JST-PH Li-poly connector.
  • Also on the outside of the project box (case) I attached a magnetic squares, so the case would snap to the underside of my mailbox. and also allow me to easily remove it.
  • Placing the plastic case outside the mailbox is preferable to inside as it’s easier to access, takes up less  space, and is less prone to WiFi signal loss due to metal mailbox.

Now onto the software and code sketch.

Arduino requirements setup

Code Sketch for this project is  located here: https://github.com/acbrandao/arduino_projects/tree/master/esp32_mailbox

Below I will  highlight some of the main sections of the code..

First make sure you get all the required libraries so you can you can build the sketch they include the following: Fire up the ARduino IDE and… First things first make sure you have the right board selected in the board manager and installed all the compulsory libraries.

  • For  your  ESP32 board Supporting libraries, steps to get these vary depending on what ESP board you have, Complete and detailed instructions here on GitHub ESP32  Arduino Core but for most boards the steps can be found here.

    in Arduino IDE – Include library PubSub

  • PubSub Client libraries, needed for MQTT support, this can easily be installed in Arduino by going to Sketch | Include Library | Search “PubSub” and install the matching library
  • Now plug in your board and confirm that its communicating properly.

At the top of script, make sure you reference these libraries and a few other standard ones like HTTPClient.h and Time.h

#include <WiFi.h>
#include <PubSubClient.h>
#include <HTTPClient.h>
#include <Time.h>

/*
 * wifi_info.h includs wifi ssid, passwords, mqtt broker info, customize included wifi_info_sample.h and
 * save as wifi_info.h in this same folder.
 */
#include "wifi_info.h"  // contains ssid and access credentials,sms, mqtt broker configs separate file for GitHub

Next there’s a separate included file named wifi_info.h , which you need to customize  wifi_info_sample.h to fit your needs (edit with your own wifi ssid, password, mqtt broker etc) , then save it as wifi_info.h in the same folder as the sketch.

/* Rename this  "wifi_info_sample.h" file to "wifi_info.h"  
 *  customize below for your environment and save as your own wifi_info.h file in this same folder
 *  Inluded in a seperate file becasue of github upload
 * 
*/

#define wifi_ssid "YOUR_AP_NAME"  //replace with actual WIFI SSID
#define wifi_password "your_password"  //replace with your actual password
#define wifi_timeout 30*1000   //Milliseconds to go to sleep if not connected

#define mqtt_server "broker.hivemq.com"  //MQTT broker address, local or cloud address
#define mqtt_user ""    //leave empty if none
#define mqtt_password ""   //leave empty if none
#define mqtt_clientid "ESP32 LoLin"  //Client id string for Mqtt broker
#define mqtt_max_retries 10 //maximum number of times to retry mqtt server before aborting


/* OPTIONAL:  but you can define a messaging service SMS such as twilio, nexmo, plivio etc.
* TO USE THIS FEATURe you NEED TO SiGNUP TO A SMS Service  then complete the section below
* refer to their curl examplles
*/

#define sms_enabled false //enable this if you have setup an sms service like tiwlio or nexmo etc..

#define sms_post_url   "https://rest.nexmo.com/sms/json"
#define sms_to_number  "SMS TO NUMBER"   // must be in the list of approved numbers - service whitelist
#define sms_from       "VALID NEXMO NUMBER"   //some services require from their number like Twilio
#define sms_api_key    "API_KEY"
#define sms_api_secret "API_SECRET" 

Next back in the main file lets define our pins,  most of this is self-explanatory , check the code comments. Note in my sketch I used GPIO 12 as where I read the digital signal,  your case may be different, but virtually any pin on the ESP32  is usable.

#define LED_ENABLED false  //do we want to turn on the on-board LED when connected to WiFi? 
#define MAX_OPENDOOR_TIME 30000   //default 30s in milliseconds how long to wait while door is open to consider it stuck open 
#define MAX_STUCK_BOOT_COUNT 5  // If the door is stuck for more than x times let's switch to timer interrupt to save battery
#define TIMER_SLEEP_MICROSECS 1800 * 1000000  //when on timer interrupt how long to sleep in seconds * microseconds

// /sensor/door_status messages for open closed and stuck
#define  message_door_open "Mailbox Opened!"
#define  message_door_closed ""
#define  message_door_stuck "Mailbox_STUCK_OPEN!"


// Define the the MQTT and WiFI client variables
WiFiClient espClient;
PubSubClient client(espClient);
HTTPClient http;  //for posting 

//Define the door Sensor PIN and initial state (Depends on your reed sensor N/C  or N/O )
gpio_num_t  doorSensorPIN = GPIO_NUM_12;  // Reed GPIO PIN:
gpio_num_t GPIO_INPUT_IO_TRIGGER = doorSensorPIN;
int GPIO_DOOR_CLOSED_STATE= LOW ;  //Default state when the reed and magent are next to each other (depends on reed switch)
int GPIO_DOOR_OPEN_STATE = !GPIO_DOOR_CLOSED_STATE;  //Open state is oposite f closed

From there the code has a bunch of utility  functions ,such as connect to wifi  void setup_wifi() and connect to the mqtt broker void reconnect()  , in both these functions there is a check to see if it’s timing out, and taking too long, (like if the AP is down or unreachable) and it will just go to sleep.

if (millis()- start_time > wifi_timeout )  //did wifi fail to connect then time out
   {
        Serial.println("Wifi Failed to connect ..timeout..");
           esp32_sleep();
       }

I have a stub in there for the future to actually send SMS messages directly from the script, for now its empty but feel free to try and fill it out..

/* TODO : Sends a SMS if you have a service like twiliio, or nexmo defined */
int send_SMS(String message)
{
  if (!sms_enabled) 
        return false; //no sms send service is available
    
      if(WiFi.status()== WL_CONNECTED) //Check WiFi connection status
      { 
        
        } 
      else
      {
       Serial.print("Error in Wifi connection");
        return false;
      }
        
}  //end send sms

I have a wrapper esp32_sleep()  , which basically updates some RTC variables and puts the whole thing to sleep.

/* Esp32 dep sleep function call and save state variables */
void esp32_sleep()
{
  //Go to sleep now
   time_awake_millis=time_awake_millis+(millis()-currentMillis  );
   last_doorState=digitalRead(doorSensorPIN) ;  //store the last door state generally should be closed
  Serial.printf("\n DoorState %d Last Door State  %d  Awake ms: %ld",doorState,last_doorState,time_awake_millis);
    Serial.println("\nGoing to sleep now");  
   esp_deep_sleep_start(); //Enter deep sleep
   Serial.println("This will never be printed");
}

Like most Arduino Sketches this sketch has a setup() function where start the main body of the code.  We setup the pinmodes, and assign a PULLUP to the GPIO pin that attaches to the reed sensor, finally we also tell ESP32 what the pullup_en (enabled ) setup should be.

/*  Start of the main setup  routine  */
void setup() {
  Serial.begin(115200);
  
  //start a timer to see how long we're awake
  currentMillis = millis();
  
// initialize digital pin LED_BUILTIN as an output.
  pinMode(LED_BUILTIN, OUTPUT);
  pinMode(GPIO_INPUT_IO_TRIGGER,INPUT_PULLUP);

 
//!< Keep power domain enabled in deep sleep, if it is needed by one of the wakeup options. Otherwise power it down.
//  esp_deep_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_AUTO); 
  gpio_pullup_en(GPIO_INPUT_IO_TRIGGER);    // use pulldown on NO door state
  gpio_pulldown_dis(GPIO_INPUT_IO_TRIGGER);       // not use pullup on GPIO

After that we check the doorState, is the door open (HIGH) or closed (LOW).

mailbox reed sensor magnets – open position

Note: the state of what is considered OPEN or CLOSE will depend on your particular magnetic sensor  (reed) switch, some are normally closed N/C) which means when the switch is by itself (ie. No magnet next to the sensor, door is opened) it is connected/closed and this will send the GPIO a HIGH signal.

If you have a N/O (Normally Open)  (less common) than the inverse , the sensor will read LOW (No connection) when the switch is by itself, you can even get N/O+N/C switches as one. Either way by adjusting the initial setting of GPIO_DOOR_CLOSED_STATE  variable to either low or high it will work with either type.

int GPIO_DOOR_CLOSED_STATE= LOW ;  //Default state when the reed and magnet are not next to each other (depends on reed switch)
int GPIO_DOOR_OPEN_STATE = !GPIO_DOOR_CLOSED_STATE;  //Open state is oposite f closed

Now lets see what state the door is in ?  use doorState = digitalRead(doorSensorPIN);  and also do an additional check if the door has been STUCk OPEN for a while we need to put the ESP32 into deep sleep in timer mode (as opposed to interrupt mode) , since a door stuck open will no longer trigger interrupts.

//What state is door in
  doorState = digitalRead(doorSensorPIN);

  ++bootCount;
  Serial.println("Boot counter  : " + String(bootCount) );
  Serial.println("Boot Stuck cnt: " + String(stuckbootCount) );
  Serial.println("Last Door    :"+String(last_doorState) );
  Serial.println("DoorState    :"+String(doorState) );
  
  if (last_doorState == doorState )  // is the door stuck open triggering Interrupt
    {
      stuckbootCount++;

    if (stuckbootCount > MAX_STUCK_BOOT_COUNT)  //door is still stuck open we need to sleep on a timer
      {
        Serial.println("Max stuck open count door reached ");
        Serial.printf("\n Putting ESP into a TIMER WAKEUP of %d secs. \n",(TIMER_SLEEP_MICROSECS/1000000) );
        esp_sleep_enable_timer_wakeup(TIMER_SLEEP_MICROSECS); //lets go to TIMER Interrupt sleep instead of GPIO wakeup
        esp32_sleep();
      }
     else 
     {
      delay(5000);  //pause maybe stuck lid will close down    
      //Wake up when it goes high - may be inverted for your reed door sensors
      esp_sleep_enable_ext0_wakeup(GPIO_INPUT_IO_TRIGGER,GPIO_DOOR_OPEN_STATE); //1 = High, 0 = Low  wake door OPEN (magnet away sensor)
      esp32_sleep();
     }
    }


  //was the door stuck open but now is closed.. re-set the door status send a message to indicate closed
   if ( stuckbootCount > MAX_STUCK_BOOT_COUNT )
     {
        if (  connect_WIFI_MQTT() )
         {
           client.publish("/sensor/door", String(doorState).c_str(), false);  //false means don't retain messages
           client.publish("/sensor/door_status",message_door_closed, false);  //text version of door
          }
        
        stuckbootCount=0; //reset the stuckboot counter each time we get a clean GPIO wakeup

     }

The next line is the heart of this sketch and why the ESP32 is used for this mailbox.. It’s the 
esp_sleep_enable_ext0_wakeup(GPIO_INPUT_IO_TRIGGER,GPIO_DOOR_OPEN_STATE);

This is the esp32 deep sleep wake on an GPIO trigger, which in our case means when the door is open , the sensor sends 1 = High, and starts this sketch.

From there its straight forward logic, when enter a while loop the door is oopened and if so then we :

  • connect to the Wifi connect_WIFI_MQTT();
  • send the status messages  to the MQTT broker  client.publish(…)
  • send an SMS message  send_SMS(“You have mail”);  If  we defined the SMS function
  • then keep checking the door state until it is closed :
    while (doorState==GPIO_DOOR_OPEN_STATE)  {…}
  • Inside the While loop we check if the door has been “Stuck Open” and if so send the appropriate message.  if (elapsed_time > MAX_OPENDOOR_TIME) {…} .
    This is necessary because in my case sometimes the mail  carrier stuffs large envelopes or other mail and the mailbox door just doesn’t close. by having this check in the code we prevent the ESP32 staying awake and checking on the door status, since it wont change till the next time we remove the mail.
//Wake up when it goes high - may be inverted for your reed door sensors
  esp_sleep_enable_ext0_wakeup(GPIO_INPUT_IO_TRIGGER,GPIO_DOOR_OPEN_STATE); //1 = High, 0 = Low  wake door OPEN (magnet away sensor)
 
  
  //Print the wakeup reason for ESP32
  print_wakeup_reason();
 
 if(doorState != GPIO_DOOR_CLOSED_STATE) //is the door NOT closed?
  {
   if (LED_ENABLED)
   digitalWrite(LED_BUILTIN, HIGH); // Turn the LED on
  
   connect_WIFI_MQTT(); //connect to wifi and MQTT Broker
   
   //Now send the MQTT message  
   client.publish("/sensor/door", String(doorState).c_str(), false);  //false means don't retain messages
   client.publish("/sensor/door_status",message_door_open, false);  //text version of door
   client.publish("/sensor/bootcount",String(bootCount).c_str(), false); 
   client.publish("/sensor/name", mqtt_clientid , false);  //client id
   client.publish("/sensor/rssi",String(rssi).c_str() , false);   // wifi rssi = signal strenght 0 - 100 , <-80 is poor

   //if you have SMS enabled.. send out a message
   send_SMS("You have mail"); //optional: send sms message - requires SMS service
    
    long  n=0; //loop counter while door is opened
    while (doorState==GPIO_DOOR_OPEN_STATE)  //while where open
    {
      n++;
      client.loop();  //call regularly to keep the connection to mqtt broker open
       
      doorState = digitalRead(doorSensorPIN); //Keep reading the door state
      
      // print out the state of the button:
      Serial.print(doorState);
      
      long elapsed_time=(millis()-start_time); 
      
      if (n%40==0)      //new line so it doens't scroll of the screen
       Serial.printf(  "%ld ms \n",elapsed_time );  //print the time in every 40 cyclels 
      delay(50);        // delay in between reads for stability


      //If the door is still OPEN  after MAX_OPENDOOR_TIME time, assume its stuck open and just send a message
      if (elapsed_time > MAX_OPENDOOR_TIME)
        {
         
          Serial.printf("\n Door STUCK OPEN for %ld ms > %d ms .. ending loop. ", elapsed_time,MAX_OPENDOOR_TIME );
          //send_SMS("Mailbox stuffed- lid is open.");  //optional send message when mailbox is stuck open
          client.publish("/sensor/door_status",message_door_stuck, false);  //text version of door
              
          break;
          //code here not executed
        }
    }

If the door isn’t stuck open then we detect a closed state, update a few counters and timers, send the final messages to the MQTT broker, close the connection and go to sleep.  That’s it.

 //calculate total_time_awake  how long the esp32 board was running
      runtime(millis()-currentMillis +time_awake_millis ) ; 
      Serial.println("ESp32 total awaketime ::"+(String)total_time_awake);     
       
       if (doorState==LOW)  //only if the door is really closed , check in case we fell through while loop
       client.publish("/sensor/door_status",message_door_closed,false);  //text version of /sensor/door
       client.publish("/sensor/total_time_awake",total_time_awake,false);
      int result= client.publish("/sensor/door", String(doorState).c_str(), false);  //door status
    
      delay(250); //give MQTT broker a chance to get message -  before disconnecting wifi
   
      client.disconnect();   //disconnect Mqtt broker
      WiFi.disconnect();    //disconnect WiFi
      Serial.println("\n Wifi Disconnected from : "+(String)wifi_ssid);
  
   if (LED_ENABLED)  
    digitalWrite(LED_BUILTIN, LOW); // :: Turn the LED oFF   
  }
  else
  Serial.println("\n Door State Unchanged not triggering wifi");

  //Go to sleep now
  esp32_sleep();
  Serial.println("This will never be printed");
 
}  //end of setup

Conclusion

There you have it a relatively easy to put together ESP32 , ARduino IoT enabled mailbox sensor . Because we’re just measuring the state  of a door, you can re-purpose this exact code and setup as a door-open alarm or a window-open alarm, or really anything where the magnetic sensor detects a change .

There are many other improvements we can do now that its up and running such as using MQTT subscribe to update Sketch defaults, or complete the SMS code stub and possibly add other sensors for more details about mail delivery. Also how about adding an FOTA (Firmware over the Air update capability)  .

But for now I just made my rusty old mailbox smart , welcome to the 21st century of IoT.

5 (100%) 1 vote

5 thoughts on “Easy battery powered Mailbox Door IoT sensor using ESP32

  1. Reply westPhil Apr 17,2018 6:19 am

    yep, I agree easiest mailbox sensor example, and simplest wiring too

  2. Reply Jen Banner Apr 27,2018 3:41 am

    can I use an ESP32 that already has the battery holder? or do I need a Lipo

  3. Reply georg Jun 2,2018 10:29 am

    Does this work with the built-in magnetic hall sensor of the esp32 chip, too?

    • Reply Tony B. Jun 4,2018 9:48 am

      Yes, it will work with the hall sensor, just assign the interrupt pin to the signal pin of the hall sensor. But keep in mind that hall sensor may (it really depends on how the circuit is set up ) draw a small amount of current continuously, unlike the reed switch which is typically open when not in use

Leave a Reply