Save power by (reliably) switching the ESP WiFi on and off

-Update Oct 2017-  Doug Elliot very kindly submitted a comment (see below) to let me know that the redoubtable Erik Bakke has done a much better job at both troubleshooting and writing up this issue in a series of articles on his blog.  Rather than wasting your time with my half-hearted fumbling around, I’ll point you directly to the first article in Erik’s series.  Stay with it and read the whole bunch right through to the end; he keeps on coming up with new tweaks as he goes along.  There are a couple of extra articles (one on eliminating network scan at start-up and another on solar power for the ESP8266) which are part of the series, so don’t miss them, either.


I have an ongoing project to add an ESP8266 to a solar trickle charger for a lead-acid battery.  Really, the only set-in-concrete requirement is that the controller must switch off the feed from the solar panel before the battery voltage gets to dangerous (“gassing”) levels, but it would be nice to have remote data logging of voltages (battery and solar) and temperature, as well as some control over the load (a small impeller pump in an external sump well, used for orchard irrigation during the summer and autumn).

Because the target battery is a standard (wet plate) car battery, I have quite a bit of capacity to play with, but even so, my back-of-the-envelope calculations show that the ESP8266 is quite capable of completely discharging the battery in under a week (assuming no sunshine and an ESP8266 module with WiFi enabled and transmitting on a more or less continuous basis), so I’d been looking for a reliable way to save power, but still have the ESP8266 do real time monitoring of the battery and solar voltages and interrupt the charge process when the battery was already fully charged (I should probably note that we get a lot of sunshine here during the summer and autumn months and our usage of the pump is only very intermittent, so despite having a mere 300mA output from the solar panel, it is possible to overcharge the battery over time).

I didn’t want to use deep-sleep mode (although it’s the least power hungry), because I want the ESP to be the actual charge controller and it can’t do that if it’s off in la-la land for the best part of its day.  I still want to use the WiFi capability (otherwise there’d be little point in using the ESP over a PIC), but I don’t need to have 24/7 connectivity.  Turning the WiFi on and off has always been a bit of a hit and miss business for me, though (and with quite a few other people too, judging by the number of hits a web search on the subject generates).  So I was pleasantly surprised to find some answers in a GitHub “issue” raised against the ESP8266/Arduino project.  The thread contained some excellent answers and testing experiences from some of the well known members of the ESP8266 community (thanks guys!), so I felt fairly sure I was onto a winner, but there were one or two twists along the way.  So here is a brief rundown on how I’m doing it with those kinks ironed out and here is a program which will demonstrate the practicality and reliability of using the “Issue 644” method.

First, let me assure the nervous reader that, despite what follows, we are not putting the ESP to sleep at all, we do not need to use an interrupt or a reset to restart the WiFi and we also do not need to make any hardware changes to the ESP module for this method to work.

The crux of this method is to use WiFi.forceSleepBegin() to put the ESP into “modem sleep” mode.  It’s important to realize that this does not put the core into any form of sleep; it simply turns the modem (radio) off.  There are some other incantations in there to ensure that WiFi is shut down in  an orderly fashion:-


bool WiFi_Off() {
WiFi.disconnect();
WiFi.mode(WIFI_OFF);
WiFi.forceSleepBegin();

One of the issues at this point though, is that the WiFi can take a finite, but variable time to shut down after the final forceSleepBegin() call, so at this point I’ve just added a timeout loop, checking the status of the connection until it goes down (returning without an error) or times out (returning with an error):-


while ((WiFi.status() == WL_CONNECTED)
&& (conn_tries++ < WIFI_RETRIES)) {
delay(100);
#ifdef DEBUG
Serial.print(".");
#endif
}
if (WiFi.status() != WL_CONNECTED)
return (true);
else
return (false);

The default timeout of 3 seconds is more than enough for the WiFi_Off() function.

The WiFi_On() function is pretty much a reversal of the “off” code:-


bool WiFi_On() {
conn_tries = 0;

WiFi.forceSleepWake();
WiFi.mode(WIFI_STA);
wifi_station_connect();
WiFi.begin(STA_SSID, STA_PASS, WIFI_CHANNEL, NULL);

while ((WiFi.status() != WL_CONNECTED)
&& (conn_tries++ < WIFI_RETRIES)) {
delay(100);
#ifdef DEBUG
Serial.print(“.”);
#endif
}
if (WiFi.status() == WL_CONNECTED)
return (true);
else
return (false);
}

The call to “wifi_station_connect()” seems to be the equivalent of a sacrificial chicken for most people, though. Without it, the restart of the WiFi doesn’t always work; with it, everything is hunky-dory (whatever that means).

Unfortunately, actually getting the call to wifi_station_connect() to compile turned out to be a bit problematic for me (from within the PlatformIO build environment). Just where the heck is it? Well, some searching found a define for it in the user_interface.h file in Espressif’s SDK. Adding a simple:-

#include "user_interface.h"

…in the header of my ESP_Power_Save.ino file got me past the initial “not found” messages, but then, at the last stage, the linker failed with “undefined reference” errors. Grrrr! The magic sauce for that though, is simply to let the C++ compiler know that it’s looking for a plain C function (buried deep in the nether regions of the Espressif SDK, in this case) by modifying that include to be:-

extern "C" {
#include "user_interface.h"
}

Finally, it compiled …and worked, too.

Before you flash ESP_Power_Save.ino to your ESP8266, you’ll need to configure the IP addresses, access-point SSID and password in the user_config.h file.

After flashing, connect to the ESP8266 console as normal and you’ll find yourself in a mini test environment where you can use a few simple, single character commands:-

  • “w” — to toggle the state of the WiFi between on and off.
  • “s” — to display the current state of the WiFi connection.
  • “c” — to display a simple count on the console, just to prove that the ESP core is still operational.

I’d recommend opening a second window and using it to ping the ESP’s IP address, to verify whether it is up or not as you toggle the WiFi.

Finally, a word of caution …If you try to connect to a non-existent access-point, or one which is out of range, or you use an incorrect password, then the WiFi will not turn off. That seems pretty counter-intuitive to me and I haven’t yet worked out why it happens. At any rate, it’s obviously worth doing some testing (I use a hand-held ESP8266 rig running on three AA batteries) to make sure that you have connectivity before deploying this power-save code to your remote units.


Update — The latest version of the code now includes a cut-down version of the “FSWebServer” example (from the ESP8266WebServer library) which will server up a 750kB JPEG file from SPIFFS on the ESP.  It takes a non-trivial amount of time for the little ESP to retrieve such a substantial file from SPIFFS, chunk it up and push it out across the network, which hopefully will give you adequate time to measure the current consumption of your module while busy transmitting, for comparison with when the WiFi is toggled to the “off” state.


Update #2 –  Real world testing has brought some interesting “features” to light.  Most problematical is that the WiFi doesn’t seem to always switch off completely, which is peculiar.

First, you need to know that the ESP8266 returns its current status when interrogated with the WiFi.printDiag() request.  When running normally, the results look something like this:-


Mode: STA
PHY mode: N
Channel: 11
AP id: 0
Status: 5
Auto connect: 1
SSID (9): Mainwaring
Passphrase (10): D0ntPan1c!
BSSID set: 0

The two most important indicators are the Mode and Status lines (although the SSID and Passphrase entries will also change when the WiFi is disabled).

The current draw on  my test module is generally in the area of 75ma when WiFi is enabled, with very brief peaks when actually transmitting (so brief that they’re impossible to measure with an ordinary digital multimeter).  That value drops off to 20ma when the WiFi is disabled and at the same time, WiFi.printDiag() changes to show “Mode: NULL” and “Status: 255” (and the module stops servicing web requests or even  a simple “ping”).

Frequently, toggling the WiFi (with the “w” command) will give the same result in terms of connectivity (ie:- the unit stops responding to web requests or pings) and the result of a WiFi.printDiag() is still “Mode: NULL” and “Status: 255”, but the current draw remains in the 75ma region.  I suspected that the auto-connect or auto-reconnect settings may have been responsible, but after further testing that doesn’t seem to be the case.

So, after using the word “reliably” in the title of this post (which, admittedly, referred specifically to being able to turn WiFi back on after a modem sleep), I now find that the current reduction isn’t reliable at all.  Does anyone have any ideas?

Advertisements