Getting digital mail when you have analog mail
Posted on 04 August 2024 by spectracoder — 19 min
Living in an apartment has a lot of benefits, but there are also some downsides. One of which is that you have no idea when the postman has put something in your mailbox. Especially when you are expecting a letter or a small parcel, you have to walk down the stairs (by lack of an elevator), check your mailbox, and walk up again, even if there wasn't any mail.
So, I thought: there must be an easier way to know if I have mail.
A few months ago, I bought some Devolo Magic 2 powerline adapters. These devices allow you to connect different rooms in your house to the internet through your mains sockets. They came in a pack of three. I only needed two of them to make a connection to another room in my apartment, so I had a spare one.
In the hallway of the apartment building, I have a storage room. It's pretty close to the mailboxes of all apartments. In the storage room, there is a wall socket. I was curious. Is it actually connected to the power lines of my apartment? (Would be weird if it wasn't: free electricity?) Does this mean I can create a Wi-Fi network in my storage room? I plugged the spare powerline adapter in the socket, and sure enough: it worked!
That's when I knew this idea had a good chance of succeeding.
For a previous project I already had experience with a ESP32 NodeMCU Module, so I started with that. I would only need to order a reed sensor, and some barrel connectors to make the wiring easy to connect and disconnect when needed. So I ordered those online.
The biggest benefit of the ESP32 chip is that is has a deep sleep mode, which -according to the specs- only consumes about 10 microamperes. I haven't measured it myself, but I assumed that would be more than enough for a battery powered device.
The idea is that the device starts up, goes to deep sleep, and then when the magnetic reed sensor is being opened (the mailman opens the lid of the mailbox), the ESP32 wakes up, connects to WiFi, sends me an email, and goes to sleep again.
In theory not very hard to program, as there are some nice libraries for the WiFi connection and sending emails from the ESP32.
The hardest part was yet to come.
The ESP32 dev board is powered via USB, so 5 volt. To connect a battery to it you would need something that can deliver 5 volt over USB. I had an old powerbank of 5000 mAh lying around, so I thought it would be perfect. With an ESP32 in deep sleep only drawing 10 microamperes, that would mean over 57 years of battery life. Or at least, in theory.
Well, to put it shortly: This wasn't going to work. As soon as the ESP32 went into deep sleep mode, the powerbank turned itself off. The problem with these things is that they are designed to charge phones or other battery powered devices. As soon as the power draw comes under a certain threshold they assume the connected device is charged up, and the powerbank shuts off. Online I found that there are ways to circumvent this, but I didn't feel like tinkering with the circuitry of a powerbank. That would be a whole project on its own, and something I don't feel comfortable with.
Another way to power the ESP32 is via its 3.3 volt pin. On the dev board is a pin that says "3.3v", so I assumed that is where I needed to connect a battery to. But what battery would deliver 3.3 volt? Li-ion batteries normally deliver 3.7 volt, but 4.2 volt when fully charged, so too high for the ESP32. You would need some kind of converter board for that to make that work. I didn't really like the idea of adding an extra board to the project. I did some research, and came across a 'new' battery technology called LiFePo4. These cells normally deliver 3.2 volt. The specs of the ESP32 chip say that it can handle a voltage between 3.0v and 3.6v. A fully charged LiFePo4 battery delivers 3.6 volts, so it is on the upper limit of that range.
I decided to try that, and ordered 2 of these LiFePo4 batteries (1 for spare), and a special charger for these particular batteries online. When they arrived the next day, I tried wiring one of the LiFePo4 batteries up to the 3.3v pin and the ground pin of the board. The blue LED on the dev board started flashing, but not the kind of flashing I was used to. It didn't seem to actually power on. I tried the other battery I ordered, but the same thing happened. Even after charging the batteries fully, it still did not work. The documentation of this particular ESP32 dev board doesn't actually mention the 3.3v pin to be a voltage-in port. So I came to the conclusion that this also wasn't going to work.
I must admit that at this point I became pretty frustrated with this project.
I was frustrated, but also determined. I decided to look online for some inspiration. What else was on the market in ESP32 world? Well, a lot, as I found out. I stumbled upon the LILYGO T8-C3, an ESP32-C3 powered development board with a battery connector. Great! I can just connect a 4.2 volt Li-ion battery to it, and even charge it using the USB connector on the board. It also features a "3D" WiFi antenna, as well as a socket to connect an external WiFi antenna to. My mailbox is made of metal, so I might have needed a way to connect an external antenna if the internal "3D" antenna was not up to par. The LILYGO T8-C3 costed only €8,-, so I decided to go with that.
The board was delivered the next day. It came in a nice little plastic box with foam, which also contained two header rows, and a lead with a JST connector for a battery.
There is not a lot of documentation about this board. In fact, the image above is about the only documentation available. The manufacturer refers to a GitHub page that should hold all the information you need, but the only actual in-depth information is a pdf with a schematic drawing of the board. So I had some things to figure out on my own.
The first thing to figure out was how to actually get something running on the device. After some tinkering, here are some of my findings with this board:
First make sure you've added this link to "File" > "Preferences" > "Additional board manager URLS": https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json This downloads all needed libraries to your Arduino IDE.
In the Arduino IDE select the "ESP32C3 Dev Module" board. "Tools" > "Board" > "esp32" > "ESP32C3 Dev Module"
To enable the serial interface, in the Arduino IDE menu under "Tools" set "USB CDC On Boot" to Enabled.
To upload a sketch to the device, switch to download mode first:
press and hold the reset button, press the boot button, and then
release the reset button. To return to normal mode press the reset
button. If it doesn't show anything in the serial monitor or the COM port isn't available, there is something wrong with the sketch.In Deep Sleep mode, the USB device becomes offline. To upload something to it, it needs to be in download mode.
If no COM port appears when programming, the USB-C cable may need to be reversed.
To charge the battery connected to the JST-connector, set the battery switch in the "off" position. This allows the USB-C connection to charge the battery. The "on" position of the battery switch powers the board by either the USB-C cable or the connected battery. It is possible to have both connected at the same time, although it is not clear to me which of both is used.
The green LED on the board is directly connected to GPIO_NUM_3 on the board (IO03)
And now for the programming itself.
I first created a header file which contains all important details, called 'config.h':
/// Change the variables in ths file to your needs.
#define WIFI_SSID "The name of your WiFi network"
#define WIFI_PASSWORD "The password of your WiFi network"
#define SENSOR_PIN GPIO_NUM_1 /// The sensor pin that will wake the ESP32 from deep sleep
#define STATUS_LED_PIN GPIO_NUM_3 /// The built in green LED of the Lilygo T8-C3 is connected to GPIO_NUM_3
#define BATTERY_STATUS_PIN GPIO_NUM_0
#define TIMER_SLEEP_MICROSECS 1 * 60 * 1000000 /// When on timer interrupt how long to sleep in minutes * seconds * microseconds
/// The smtp host name e.g. smtp.gmail.com for GMail or smtp.office365.com for Outlook or smtp.mail.yahoo.com
#define SMTP_HOST "smtp.example.com"
#define SMTP_PORT 999
/// The sign in credentials
#define AUTHOR_NAME "Name"
#define AUTHOR_EMAIL "email@example.com"
#define AUTHOR_PASSWORD "password"
#define MAIL_SUBJECT "You've got mail!"
/// Recipient's email
#define RECIPIENT_NAME "Name"
#define RECIPIENT_EMAIL "emal@example.com"
In the "ESP32_Mail_Notifier.ino" script I made sure to add a reference to the header file, as well as a couple of libraries:
#include "config.h"
#include <WiFi.h> /// From ESP32 library
#include <ESP_Mail_Client.h> /// By Mobizt https://github.com/mobizt/ESP-Mail-Client
Because I'm using the deep sleep function of the ESP32C3 chip, the whole script only runs once before it goes to sleep. Therefore everything the script does only takes place inside of the setup() function. Having a loop() function is mandatory though, so I left that empty.
void setup()
{
Serial.begin(115200);
pinMode(SENSOR_PIN, INPUT);
pinMode(STATUS_LED_PIN, OUTPUT);
/// Get the battery voltage and percentage values
float batteryVoltage = getBatteryVoltage(BATTERY_STATUS_PIN);
uint8_t batteryPercentage = getBatteryPercentage(batteryVoltage);
/// Enable the status led dimly when turning on or waking up.
analogWrite(STATUS_LED_PIN, 10);
/// I had to add some delays in the code during development to be able to get it to print something before going into deep sleep mode again.
delay(1000);
Serial.println("Hello");
/// When booting for the first time, this prevents sending an email.
if(IsWokenUpBySensorOrTimer())
{
int tries = 0;
int maxTries = 10;
while(digitalRead(SENSOR_PIN) == LOW)
{
/// When the sensor is stuck open, it tries to check for a closed state for 10 times.
/// If it is still not closed, it sets a timer to wake up at a later moment and try it again.
if(tries > maxTries)
{
Serial.println("Door stuck open!");
Serial.printf("Going to sleep for %d seconds. \n", (TIMER_SLEEP_MICROSECS/1000000));
esp_sleep_enable_timer_wakeup(TIMER_SLEEP_MICROSECS);
StartDeepSleep();
}
Serial.printf("Waiting for sensor to close %d/%d \n", tries, maxTries);
delay(1000);
tries++;
}
Serial.println("-----------------| SENDING EMAIL |-----------------");
ConnectWifi();
/// In the body of the email that gets sent when there is mail,
/// we can show some WiFi signal information, als well as battery information.
int rssi = WiFi.RSSI();
int signalStrength = map(rssi, -100, -50, 0, 100);
signalStrength = constrain(signalStrength, 0, 100);
String body = R"(
Battery voltage: )" + String(batteryVoltage) + R"(v
Battery percentage: )" + String(batteryPercentage) + R"(%
WiFi Signal Strength: )" + String(signalStrength) + R"(% (RSSI )" + String(rssi) + R"())";
SendEmail(MAIL_SUBJECT, body);
Serial.println("-----------------| EMAIL SENT |-----------------");
}
/// Wake up the ESP32 when SENSOR_PIN is pulled low.
/// For ESP32 Dev Module use this:
//esp_sleep_enable_ext0_wakeup(SENSOR_PIN, 0); //1 = High, 0 = Low
/// For ESP32-C3 Dev Module use this:
esp_deep_sleep_enable_gpio_wakeup(BIT(SENSOR_PIN), ESP_GPIO_WAKEUP_GPIO_LOW);
StartDeepSleep();
}
void loop()
{
/// Mandatory empty loop function.
}
void StartDeepSleep()
{
Serial.println("Going to sleep now");
delay(1000);
analogWrite(STATUS_LED_PIN, 0); /// Disable the status led when going into deep sleep mode
esp_deep_sleep_start();
}
There is a difference between de ESP32 chip and the ESP32-C3 chip in regards to the deep sleep mode. The ESP32 chip supports only one GPIO pin to wake up from deep sleep, and the ESP32-C3 chip supports multiple GPIO pins to wake up from.
That's also the reason why you have to use a bit mask for the ESP32-C3 to tell it which pins you want to use to wake it up. Since I will only use one pin (GPIO_NUM_1) I just told it to do so using the BIT() macro.
The IsWokenUpBySensorOrTimer() function checks the cause of the wake up of the ESP32. If it is woken up by a GPIO-trigger (the reed sensor in this case) it prints the reason to the serial interface, and returns 'true'. This makes sure the email notification is only sent when the wakeup is caused by the reed sensor, and not when you first boot up the device.
bool IsWokenUpBySensorOrTimer()
{
esp_sleep_wakeup_cause_t wakeup_reason;
wakeup_reason = esp_sleep_get_wakeup_cause();
switch(wakeup_reason)
{
case ESP_SLEEP_WAKEUP_EXT0 :
Serial.println("Wakeup caused by external signal using RTC_IO");
return false; /// <--- Set this to true if you're not using an ESP32C3
break;
case ESP_SLEEP_WAKEUP_GPIO :
Serial.println("Wakeup caused by external signal using GPIO");
return true; /// <--- Set this to false if you're not using an ESP32C3
break;
case ESP_SLEEP_WAKEUP_EXT1 :
Serial.println("Wakeup caused by external signal using RTC_CNTL");
return false;
break;
case ESP_SLEEP_WAKEUP_TIMER :
Serial.println("Wakeup caused by timer");
return true;
break;
case ESP_SLEEP_WAKEUP_TOUCHPAD :
Serial.println("Wakeup caused by touchpad");
return false;
break;
case ESP_SLEEP_WAKEUP_ULP :
Serial.println("Wakeup caused by ULP program");
return false;
break;
default :
Serial.printf("Wakeup was not caused by deep sleep: %d\n",wakeup_reason);
return false;
break;
}
}
void ConnectWifi()
{
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
Serial.println("Connecting to Wi-Fi");
if (WiFi.waitForConnectResult() == WL_CONNECTED)
{
Serial.print("Connected with IP: ");
Serial.println(WiFi.localIP());
}
else
{
Serial.println("Not connected");
}
}
The email library.
This code was mostly just copied from the GitHub page of the ESP_Mail_Client library. I only added some parameters to the function to be able to fill in the email subject and email body text from where you call the function.
void SendEmail(String emailSubject, String emailText)
{
/* Set the network reconnection option */
MailClient.networkReconnect(true);
/** Enable the debug via Serial port
* 0 for no debugging
* 1 for basic level debugging
*
* Debug port can be changed via ESP_MAIL_DEFAULT_DEBUG_PORT in ESP_Mail_FS.h
*/
smtp.debug(1);
/* Set the callback function to get the sending results */
smtp.callback(smtpCallback);
/* Declare the Session_Config for user defined session credentials */
Session_Config config;
/* Set the session config */
config.server.host_name = F(SMTP_HOST);
config.server.port = SMTP_PORT;
config.login.email = F(AUTHOR_EMAIL);
config.login.password = F(AUTHOR_PASSWORD);
config.login.user_domain = F("");
/*
Set the NTP config time
For times east of the Prime Meridian use 0-12
For times west of the Prime Meridian add 12 to the offset.
Ex. American/Denver GMT would be -6. 6 + 12 = 18
See https://en.wikipedia.org/wiki/Time_zone for a list of the GMT/UTC timezone offsets
*/
//config.time.ntp_server = F("pool.ntp.org,time.nist.gov");
//config.time.gmt_offset = 1;
//config.time.day_light_offset = 1;
config.time.ntp_server = F("ntppool1.time.nl");
config.time.gmt_offset = 1;
config.time.day_light_offset = 1;
config.time.timezone_env_string = "CET-1CEST,M3.5.0,M10.5.0/3";
/* Declare the message class */
SMTP_Message message;
/* Set the message headers */
message.sender.name = F(AUTHOR_NAME);
message.sender.email = F(AUTHOR_EMAIL);
message.subject = emailSubject;
message.addRecipient(F(RECIPIENT_NAME), (RECIPIENT_EMAIL));
/*Send HTML message*/
/*String htmlMsg = "<div style=\"color:#2f4468;\"><h1>Hello World!</h1><p>- Sent from ESP board</p></div>";
message.html.content = htmlMsg.c_str();
message.html.content = htmlMsg.c_str();
message.text.charSet = "us-ascii";
message.html.transfer_encoding = Content_Transfer_Encoding::enc_7bit;*/
//Send raw text message
message.text.content = emailText.c_str();
message.text.charSet = "us-ascii";
message.text.transfer_encoding = Content_Transfer_Encoding::enc_7bit;
message.priority = esp_mail_smtp_priority::esp_mail_smtp_priority_normal;
message.response.notify = esp_mail_smtp_notify_success | esp_mail_smtp_notify_failure | esp_mail_smtp_notify_delay;
/* Connect to the server */
if (!smtp.connect(&config)){
ESP_MAIL_PRINTF("Connection error, Status Code: %d, Error Code: %d, Reason: %s", smtp.statusCode(), smtp.errorCode(), smtp.errorReason().c_str());
return;
}
if (!smtp.isLoggedIn()){
Serial.println("\nNot yet logged in.");
}
else{
if (smtp.isAuthenticated())
Serial.println("\nSuccessfully logged in.");
else
Serial.println("\nConnected with no Auth.");
}
/* Start sending Email and close the session */
if (!MailClient.sendMail(&smtp, &message))
{
ESP_MAIL_PRINTF("Error, Status Code: %d, Error Code: %d, Reason: %s", smtp.statusCode(), smtp.errorCode(), smtp.errorReason().c_str());
}
}
/* Callback function to get the Email sending status */
void smtpCallback(SMTP_Status status){
/* Print the current status */
Serial.println(status.info());
/* Print the sending result */
if (status.success()){
// ESP_MAIL_PRINTF used in the examples is for format printing via debug Serial port
// that works for all supported Arduino platform SDKs e.g. AVR, SAMD, ESP32 and ESP8266.
// In ESP8266 and ESP32, you can use Serial.printf directly.
Serial.println("----------------");
ESP_MAIL_PRINTF("Message sent success: %d\n", status.completedCount());
ESP_MAIL_PRINTF("Message sent failed: %d\n", status.failedCount());
Serial.println("----------------\n");
for (size_t i = 0; i < smtp.sendingResult.size(); i++)
{
/* Get the result item */
SMTP_Result result = smtp.sendingResult.getItem(i);
// In case, ESP32, ESP8266 and SAMD device, the timestamp get from result.timestamp should be valid if
// your device time was synched with NTP server.
// Other devices may show invalid timestamp as the device time was not set i.e. it will show Jan 1, 1970.
// You can call smtp.setSystemTime(xxx) to set device time manually. Where xxx is timestamp (seconds since Jan 1, 1970)
ESP_MAIL_PRINTF("Message No: %d\n", i + 1);
ESP_MAIL_PRINTF("Status: %s\n", result.completed ? "success" : "failed");
ESP_MAIL_PRINTF("Date/Time: %s\n", MailClient.Time.getDateTimeString(result.timestamp, "%B %d, %Y %H:%M:%S").c_str());
ESP_MAIL_PRINTF("Recipient: %s\n", result.recipients.c_str());
ESP_MAIL_PRINTF("Subject: %s\n", result.subject.c_str());
}
Serial.println("----------------\n");
// You need to clear sending result as the memory usage will grow up.
smtp.sendingResult.clear();
}
}
I thought it would also be nice to be able to know the battery status. So I searched the web to see if I could easily add such a feature. I must admit that I'm not good at figuring this out on my own, so I'm glad David Bird did. (YouTube video of his findings)
In the schematics of the LILYGO T8-C3 board I found that the battery seems connected via a voltage divider to GPIO_NUM_0, so I used that port for the battery reading.
While I don't fully understand how it works, I managed to get it to read some convincible battery values. I adjusted the code a bit to be able to print the values out nicely in the body of the email.
/// https://www.youtube.com/watch?v=qKUrXwkr3cc
/// https://github.com/G6EJD/LiPo_Battery_Capacity_Estimator/blob/master/ReadBatteryCapacity_LIPO.ino
/* An improved battery estimation function
This software, the ideas and concepts is Copyright (c) David Bird 2019 and beyond.
All rights to this software are reserved.
It is prohibited to redistribute or reproduce of any part or all of the software contents in any form other than the following:
1. You may print or download to a local hard disk extracts for your personal and non-commercial use only.
2. You may copy the content to individual third parties for their personal use, but only if you acknowledge
the author David Bird as the source of the material.
3. You may not, except with my express written permission, distribute or commercially exploit the content.
4. You may not transmit it or store it in any other website or other form of electronic retrieval system for commercial purposes.
5. You MUST include all of this copyright and permission notice ('as annotated') and this shall be included in all copies
or substantial portions of the software and where the software use is visible to an end-user.
THE SOFTWARE IS PROVIDED "AS IS" FOR PRIVATE USE ONLY, IT IS NOT FOR COMMERCIAL USE IN WHOLE OR PART OR CONCEPT.
FOR PERSONAL USE IT IS SUPPLIED WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHOR OR COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
*/
// String readBattery()
// {
// uint8_t percentage = 100;
// float voltage = analogRead(GPIO_NUM_0) / 4096.0 * 7.23; // LOLIN D32 (no voltage divider need already fitted to board.or NODEMCU ESP32 with 100K+100K voltage divider
// //float voltage = analogRead(39) / 4096.0 * 7.23; // NODEMCU ESP32 with 100K+100K voltage divider added
// //float voltage = analogRead(A0) / 4096.0 * 4.24; // Wemos / Lolin D1 Mini 100K series resistor added
// //float voltage = analogRead(A0) / 4096.0 * 5.00; // Ardunio UNO, no voltage divider required
// Serial.println("Voltage = " + String(voltage));
// percentage = 2808.3808 * pow(voltage, 4) - 43560.9157 * pow(voltage, 3) + 252848.5888 * pow(voltage, 2) - 650767.4615 * voltage + 626532.5703;
// if (voltage > 4.19) percentage = 100;
// else if (voltage <= 3.50) percentage = 0;
// return String(percentage)+"%";
// }
float getBatteryVoltage(uint8_t voltagePin)
{
float voltage = analogRead(voltagePin) / 4096.0 * 6.0; /// 6.0 is just a guess to get a estimate reading of the battery level
return voltage;
}
uint8_t getBatteryPercentage(float voltage)
{
uint8_t percentage = 100;
percentage = 2808.3808 * pow(voltage, 4) - 43560.9157 * pow(voltage, 3) + 252848.5888 * pow(voltage, 2) - 650767.4615 * voltage + 626532.5703;
if (voltage > 4.19)
{
percentage = 100;
}
else if (voltage <= 3.50)
{
percentage = 0;
};
return percentage;
}
And there it was: A working mail notifier!
I only needed to do some soldering to get all the wires connected, and a resistor to pull the voltage to low when the reed sensor was opened. I soldered a battery from an old phone to the included JST connection lead, and charged it up using a USB-C cable.
I also needed an enclosure for this little project, so I decided to use a small lunch box of about 11 x 8 x 5 cm. To hold the battery and dev board in place, I used some hook-and-loop fasteners. In the lunch box I drilled a hole to add a barrel connector to be able to plug and unplug the reed sensor when needed.
I sticked the reed sensor to the lid of my mailbox.
I did a couple of test runs, and it works pretty well! The WiFi signal strength is about 34% with the mailbox closed, so not too bad.
For the full repository of this project, go to my GitHub page.
Comments