I was very intrigued when the very inexpensive ESP8266 module hit the market. All of a sudden you could add full WiFi functionality to basically any project at a very low price.
I was even more intrigued when I read this article about how you can set the module to a very low power state in which it could lay dormant for more than an hour. This opened an interesting opportunity of having battery-powered IoT applications that connect directly to your WiFi router.
I had to see if it could be done!
The Bare NecessitiesOk, so the first thing you'll need is a ESP8266 board. There are dozens on the market, I chose the Sparkfun ESP8266 Thing Dev because it already had a handy USB port for programming and debugging, but it's really not critical you choose this particular board for the project.
If you are not familiar with the board, it would be a good idea to check Sparkfun's hookup and programming guide if you are trying to build this project. It explains how to power the board and also how to set up and program it through Arduino.
The board requires some tiny modifications to make it fully low-power, which I will describe below.
First, on the back of the board you'll find a solder jumper marked PWR_LED. This connects by default the blue power LED. As the board will be battery-powered and the LED draws quite a lot of power, we'll need to disconnect it. Cut the thin track between the pads with a cutter knife.
The ESP8266 chip can be driven by software into a very low-power state called Deep Sleep. However, in order to wake up from it, it needs to auto-reset itself. To do that, its XPD pin needs to be connected to the RST (RESET) pin. Sparkfun has conveniently thought of that and designed its boards with a way to join the two pins. We'll need to solder a jumper header to the SLEEP_EN connector marked on the back.
There is only one modification needed to prepare the board for our project, and it was not a straight-forward one. I was running the code examples from Sparkfun's tutorial and measuring current consumption, but it was nowhere near the 77µA they were reporting in deep sleep!
After some debugging, I found the culprit. Both the Thing and the Thing Dev board which I used for this project have a AP2112 low-dropout voltage regulator. It's used to regulate the voltage that powers the board to a steady 3.3V. However, it also draws power in doing so: the datasheet states 55µA but it's actually higher.
I was using two AA batteries to power the board and the ESP8266 chip can run with as low as 1.7V, so there's virtually no need for the LDO regulator in this case. I connected the battery pack directly to the 3.3V pin of the board, but the current draw was still high! It seems that the AP2112 also has a nasty reverse current draw if it's not powered. That chip had to go!
This last step brought the current consumption to around 20µA in deep sleep mode, which was really, really low!
There's a drawback: you won't be able to power the board through USB anymore if you want to program it without the battery pack, but... 20µA!
Knock Some Sense Into It!I wanted to add some sensing capabilities to my project, so I chose two relatively inexpensive breakout boards: Adafruit's BME680 and the CJMCU MAX44009. If the codes don't say much to you, BME680 is an integrated temperature / humidity / pressure / air quality sensor from Bosch and the MAX44009 is a very sensitive luxmeter. They both use the I2C protocol to communicate and can operate with as low as 1.7V. Oh, and they're both very low power as well.
So, I wired the whole thing up as shown in the picture below, and the whole hardware setup was done!
The code for the project was written in a single Arduino sketch. It uses Adafruit's library for the BME680 sensor. I could not find a suitable library for the MAX44009 chip, so I wrote one.
The ESP8266 would read sensor data, connect to the local WiFi network, send the measured data and then periodically go to sleep. As the chip performs a reset every time, all code is written in the setup() part of the sketch.
To save and display data, I've used the AllThingsTalk platform. You'll need to create an account and set up a new device to start sending data.
//#define JSON
#define CBOR
//#define DEBUG
#define SEALEVELPRESSURE_HPA (1013.25)
#define WIFI_SSID <YOUR SSID>
#define WIFI_PWD <YOUR PASSWORD>
#define DEVICE_ID <YOUR DEVICE ID>
#define DEVICE_TOKEN <YOUR DEVICE TOKEN>
// Define http and mqtt endpoints
#define HTTP_URL "api.allthingstalk.io" // API endpoint
#define MQTT_URL "api.allthingstalk.io" // MQTT broker
#include <ESP8266WiFi.h>
#include <PubSubClient.h>
#include <ArduinoJson.h>
#include <ATT_IOT.h>
#include <Wire.h>
#include <Adafruit_Sensor.h>
#include "Adafruit_BME680.h"
#include "MAX44009.h"
// I2C sensors
Adafruit_BME680 bme;
MAX44009 light;
void callback(char* topic, byte* payload, unsigned int length);
WiFiClient espClient;
PubSubClient pubSub(MQTT_URL, 1883, callback, espClient);
ATTDevice device(DEVICE_ID, DEVICE_TOKEN);
#ifdef CBOR
#include <CborBuilder.h>
CborBuilder payload(device);
#endif
float lux = 0.0;
float temperature, pressure, humidity, voc;
// timer variable
unsigned long elapsed_msec;
int BME680init()
{
if (!bme.begin())
return 0;
// Set up oversampling and filter initialization
bme.setTemperatureOversampling(BME680_OS_8X);
bme.setHumidityOversampling(BME680_OS_2X);
bme.setPressureOversampling(BME680_OS_4X);
bme.setIIRFilterSize(BME680_FILTER_SIZE_3);
bme.setGasHeater(320, 150); // 320*C for 150 ms
return 1;
}
int setupWiFi(const char* ssid, const char* password)
{
unsigned char tries = 0;
delay(10);
Serial.print("Connecting to ");
Serial.println(ssid);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED)
{
tries++;
delay(50);
Serial.print(".");
if(tries == 50) return 0;
}
return 1;
}
void setup()
{
#ifdef DEBUG
elapsed_msec = millis();
#endif
pinMode(LED_BUILTIN, OUTPUT);
digitalWrite(LED_BUILTIN, HIGH); // Turn the onboard LED off
Serial.begin(9600); // Init serial link for debugging
Wire.begin();
if(light.begin())
{
Serial.println("Could not find a valid MAX44009 sensor, check wiring!");
ESP.deepSleep(10000000, WAKE_RF_DEFAULT); // go to sleep for 10s to force a reset
}
if(!BME680init())
{
Serial.println("Could not find a valid BME680 sensor, check wiring!");
ESP.deepSleep(10000000, WAKE_RF_DEFAULT); // go to sleep for 10s to force a reset
}
// Enter your WiFi credentials here!
if(!setupWiFi(WIFI_SSID, WIFI_PWD))
{
Serial.println("Could not connect to wifi.");
ESP.deepSleep(10000000, WAKE_RF_DEFAULT); // go to sleep for 10s to force a reset
}
else
Serial.println("connected!");
/* //Call this only once, or manually create all Assets in the AllThingsTalk web interface
while(!device.connect(&espClient, HTTP_URL)) // Connect to AllThingsTalk
Serial.println("retrying");
// Create device assets
device.addAsset("light", "light", "light intensity", "sensor", "{\"type\": \"number\"}");
device.addAsset("temperature", "temperature", "ambient temperature", "sensor", "{\"type\": \"number\"}");
device.addAsset("humidity", "humidity", "ambient humidity", "sensor", "{\"type\": \"number\"}");
device.addAsset("pressure", "pressure", "ambient pressure", "sensor", "{\"type\": \"number\"}");
device.addAsset("VOC", "VOC", "ambient VOC", "sensor", "{\"type\": \"number\"}");
*/
//device.addAsset("toggle", "toggle", "toggle", "actuator", "{\"type\": \"boolean\"}");
while(!device.subscribe(pubSub)) // Subscribe to mqtt
Serial.println("retrying");
lux = light.get_lux();
#ifdef DEBUG
Serial.print(lux);
Serial.println(" lux");
#endif
if (!bme.performReading())
{
Serial.println("Failed to perform reading :(");
return;
}
if (!bme.performReading())
{
Serial.println("Failed to perform reading :(");
return;
}
temperature = bme.temperature;
pressure = bme.pressure / 100.0;
humidity = bme.humidity;
voc = bme.gas_resistance / 1000.0;
#ifdef DEBUG
Serial.print("Temperature = ");
Serial.print(temperature);
Serial.println(" *C");
Serial.print("Pressure = ");
Serial.print(pressure);
Serial.println(" hPa");
Serial.print("Humidity = ");
Serial.print(humidity);
Serial.println(" %");
Serial.print("Gas = ");
Serial.print(voc);
Serial.println(" KOhms");
#endif
#ifdef JSON
device.send(String(lux), "light");
#endif
#ifdef CBOR // Send data using Cbor
payload.reset();
payload.map(5); //send a total of 5 values for the declared assets
payload.addNumber(lux, "light");
payload.addNumber(temperature, "temperature");
payload.addNumber(humidity, "humidity");
payload.addNumber(pressure, "pressure");
payload.addNumber(voc, "VOC");
payload.send();
#endif
//device.process(); // Check for incoming messages
#ifdef DEBUG
Serial.print("Millis elapsed: ");
Serial.println(millis() - elapsed_msec);
#endif
ESP.deepSleep(900e6, WAKE_RF_DEFAULT); //sleep for 900 seconds, aka 15 minutes
}
void loop()
{
}
It takes about 11 seconds for the node to measure the sensors and send data to the AllThingsTalk cloud, during which current consumption averages around 70mA. However, when the node is in deep sleep, current consumption is around 20µA.
Here's the kicker: if the sleep period is set to one hour, then average current consumption will be at around 250µA, which means that a pair of alkaline AA batteries will be able to power up the whole setup for more than a year!
This is what success looks like:
Comments