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

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() {

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)) {
#ifdef DEBUG
if (WiFi.status() != WL_CONNECTED)
return (true);
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;


while ((WiFi.status() != WL_CONNECTED)
&& (conn_tries++ < WIFI_RETRIES)) {
#ifdef DEBUG
if (WiFi.status() == WL_CONNECTED)
return (true);
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?

ESP8266 Overclocking & Speedometer

A few days ago I posted the code for a(n almost unusable) version of Zork (or actually, the Zmachine) for the ESP8266.  It was (and is) really, really slow.  The main problem appears to be that it constantly re-writes the Zmachine stack back to SPIFFS which, in addition to causing the running-through-chest-deep-molasses effect, will also wear-out your flash in double-quick time.  The fix for this (obviously) is to shoehorn the whole thing into main memory (which is a work-in-progress), but while trying to squeeze enough performance out of the ESP8266 to make this early cut at least somewhat playable, I thought I’d take the easy option first and run the ESP at 160MHz instead of the default 80MHz.  I should warn you right now that as far as making Zork playable went, it was a total failure, but I was pleasantly surprised at just how easy the speed selection is when using PlatformIO.  I didn’t even have to sacrifice a chicken.

Here’s the simple incantation, which you just need to add to the bottom of your platformio.ini file in the project directory (the comment line is optional):-

; Set the ESP8266 clock frequency to 160MHz
board_f_cpu = 160000000L

…and then just recompile (and, because you’ve just made a change to the platformio.ini file, PlatformIO itself is smart enough to know that it needs to do a complete rebuild, not a partial).

That’s it …you’re done!


NOTE – You can also use the system call “system_update_cpu_freq()” to dynamically update the ESP8266 clock frequency from within the program itself, instead of using the platformio.ini “board_f_cpu” setting.  Using the system call will override the compile-time setting.

CODE – Ray Burnette’s adaptation of the Dhrystone test program is available from my repository in both static and dynamic versions.  The static version simply loops endlessly, reporting the performance of the current module (so effectively the compile-time clock setting from the platformio.ini file).  The dynamic version toggles the clock speed using the system_update_cpu_freq() system call on each iteration and displays the performance for whichever the current setting is.



Getting started with PlatformIO and the ESP32

Here’s the shortest “Getting Started” you’ve ever seen (disclaimer†  …I’m making the huge assumption that you already use PlatformIO as your development environment for your ESP8266 projects.  If you don’t, you should!).

Add support for the ESP32 with:-

platformio platform install espressif32

Create your new development (project) directory and, in that new directory, initialize the environment for the type of board you have‡  with:-

platformio init --board=esp32dev

Start writing code, as normal, in the newly created src directory and then compile with:-

platformio run

At this point, PlatformIO will go off and automatically download the framework support for your environment (this first time, only) and then compile your code.

You just can’t get any easier than that!

† I don’t actually have an ESP32 board yet.

‡ List the available target boards with “platformio boards

Ψ If you’re tired of typing “platformio” in full each time, you can shorten it to “pio” (“platformio” is used for clarity here).

ω For more information on getting started with PlatformIO, see the full documentation at:-

433MHz/IR MQTT Gateway Project

It’s nice to see a project that develops over time and Florian has a good example on his blog.  He started off in the middle of last year by putting together a low-cost, arduino-based sensor for his garden and things seem to have just grown from there :-).  You can follow along on his blog from the initial version of his bidirectional 433MHz, ESP8266-based, MQTT gateway, progressing to the IR enabled version by the end of the year and his OpenMQTTGateway project this year.  It’s a good read and a great little project to follow.



How to do a Sonoff memory upgrade

Jonathan Oxer, over at SuperHouseTV, has been running a series of video episodes on the Sonoff product line recently and I really recommend his latest.  The theme of the video is Sonoff-specific hints and hint #1 is how to upgrade the memory chip in a Sonoff.  Jonathan does a great job of demonstrating how to remove the chip with nothing more than “a big, old, clunky soldering iron” and a screwdriver.  A picture is worth a thousand words, so you need to multiply that by whatever the frame rate of a YouTube video is.  Watch it!  It’s worth it.

Simple demo for the “Yellow” board

As promised in the previous post, we’re going to explore a simple application for the Yellow Development Board this time round.  The one thing that this board has plenty of is LEDs.Yellow Serial Development Board  There are half-a-dozen red LEDs and one blue across the bottom of the board, as well as a 5mm RGB LED.  So, not surprisingly, we’re going for the “Blinkenlights” option as the first demo.  You won’t need an MQTT server or even a working access-point to have this demo work, but the display does get a bit boring quite quickly.

If you have modified your board with the addition of the latching power switch described in the hardware article, this demo will turn itself off after three minutes of run time.  If you haven’t added the switch, you’ll have to remove power by pulling one of the batteries out, otherwise it will just keep on running until the batteries are completely depleted.

The code for this first demo is available (with the other demos) in the GitHub repository.  The filename is Initial_Power+LEDs.ino.  My preferred environment for compiling ESP8266 programs nowadays is PlatformIO (if you haven’t tried it yet, you should give it a go); it makes compiling programs for multiple different architectures a breeze and you don’t have to worry about shoehorning things in, it just does the right thing.  Anyway, the demo programs are basically Arduino-IDE format for the ESP8266, so whether your preferred  environment is PlatformIO or Arduino-IDE, they should just work for you (if you’re still in the dark ages, using Espressif’s SDK, you’re on your own!).

In addition to the base demo itself, Over-The-Air update functionality is also built in to each program, so assuming that you have a working network and access point, you should only need to upload to your ESP8266 by serial the very first time, after that you can use the normal Arduino or PlatformIO OTA commands to upload a new demo file.

Note that all of the demos are set up to use a static IP address, netmask, gateway and DNS server.  This is to ensure that the ESP starts up as quickly as possible in a real “button” application.  The WiFi channel number is also specified, again as part of a fast, robust start-up procedure.

Before compiling any of the demos, you need to go through the user_config.h file and change all of the default settings in there to match your local network.  The specific settings you should be interested in are:-

  • LOCATION   [optional]  Setting this to your general region may help you to find your own results when using a public MQTT server for the later demos.
  • STA_SSID  [required]  Set this to the SSID (name) of your Access Point.
  • STA_PASS  [required]  Set this to the (WiFi network) password requested by your Access Point when connecting.
  • WIFI_IPADDR  [required]  Set this to be the IP address which you want this specific ESP module to use (it is assumed that you know your own network …DO NOT simply choose a random IP).
  • WIFI_NETMASK  [required]  This should be set to use your network specific addressing scheme.  Generally, if your IP addresses begin with “192.” you should be okay to leave this as “”, but if you don’t know, please ask someone who does.
  • WIFI_GATEWY  [required]  This should be set to the IP address of your gateway router.
  • WIFI_DNSSRV  [required]  This should be set to the IP address of your local DNS server.
  • WIFI_CHANNEL [required]  This is the number of the WiFi channel which your access point is using (and can be between 1 and 14, depending upon where in the world you are).
  • MQTT_HOST  [required for later demos]  See later explanation for this option.

If you don’t know the answer to any of the “WIFI_*” settings, you need to find someone with a better knowledge of your local network and ask them;  DO NOT just guess.  You could effectively cripple the whole of your local network with a wrong answer to some of these questions.

If you have a good, robust working DHCP server and you don’t care about start-up speed, you might want to convert these programs to use DHCP instead.  As this is by far the most common option in ESP8266 programs generally available on the ‘net, I will leave this conversion as an exercise for the reader (it’s quite a bit shorter and simpler than the static address variation used here).

Although MQTT isn’t required for this first demo, I’m going to mention it here anyway, just so you know what some of the options in the user_config.h file are.  The default setting for MQTT_HOST in that file is “”, which is a publicly accessible server in Germany which HiveMQ very kindly make available for testing.  HiveMQ also provide a whole bunch of MQTT related resources and services, so please do send any business you can in their direction.  Please DO NOT bug them with support requests for these demos; those requests should come to me.  Please also note that the HiveMQ server gets -very- busy (it’s not unusual to see in excess of 1,000 concurrently connected clients), so it can be quite difficult to see any output from your specific device on their web dashboard.  Please resist the temptation to increase your publishing rate; the server is already busy enough.

If, on the other hand, you have a local MQTT broker (server‡), then you should change the MQTT_HOST option in user_config.h to point to it specifically from the very start (if you don’t yet have an MQTT server set up, I would recommend the Mosquitto package; it comes with the broker and with two command-line utilities, mosquitto_pub and mosquitto_sub, which allow you to easily publish or subscribe to topics on either your local broker, or any other publicly accessible broker service on the ‘net.  Mosquitto is available as an optional package for most Linux variants through the software manager and there’s also a binary package available for Windows).

The MQTT_CLIENT_ID is a symbolic ID used by your ESP8266 when talking to the server.  It enables the MQTT broker to differentiate between the dozens of ESP8266 modules which you have connected to your network.  The string “ESP8266_%06X” is automatically replaced at run time with a six-digit number created from the unique ESP8266 chip-ID, to provide something like “ESP8266_09C721”.  This prevents potential clashes where two (or more) ESPs sharing the same ID would cause each other to be disconnected from the MQTT broker when a new request comes from a different address.  This means that if 500 of my closest friends all decide to build the MQTT demo and use the default, publicly addressable HiveMQ server, they should all work perfectly, without trampling over one another.

Finally, note that our ESP client can handle three different topics (only two are defined by default).  TOPIC1 is defined as “timestamp” and we become a subscriber to that topic (the HiveMQ broker currently publishes a timestamp every few seconds).  TOPIC2 is defined as “Yellow/LDR” and our MQTT demo program will publish regular updates to it.  The data published to that topic is the ADC value of the ESP8266 pin with the LDR attached (so the value depends upon the ambient light level).

All of the demo programs, including the non-MQTT version, will print out info to the serial port, including regular LDR value updates.

The mood-light display mode on the Yellow is based on code by “Mike Mc” (Mike McRoberts, author of “Beginning Arduino”).  The code uses a 256 entry look-up table to compensate for our weird vision and produce what looks like a linear fade to our eyes.  Kudos to Mike for writing such a durable routine, which works very well on the ESP, despite its proclivity for running off and doing WiFi-ish stuff at unpredictable intervals.

Speaking of the need to service WiFi requests and other housekeeping, the reader will notice that there are calls to yield() sprinkled liberally throughout the code and that there is also an added function, Ydelay(), which is simply a call to the normal delay function with an additional call to yield() thrown in.  It seems like you can’t call yield() often enough in any ESP code that could get itself caught in a tight loop.


‡ I use the terms “broker” and “server” interchangeably, but strictly speaking, the “broker” is the MQTT process running on what we loosely refer to as a (hardware) server.  Your client devices all connect to and exchange messages with this server.  You’re not confused, are you?!?!

Yellow doorbell/dash project

In the previous post, I outlined how the “Yellow Development Board” from AI-Thinker had become my new, go-to ESP8266 board for projects, partly because the headers make it easy for interconnects and partly because it’s got lots of LEDs already on board.  Not to mention that it’s reasonably priced and comes equipped with a battery box and a low quiescent current voltage regulator (important when you’re running from batteries).

Even more recently, I wanted to experiment with a doorbell-cum-dash style button, based on one of the dollar-store LED push-button lights which seem to be available everywhere nowadays. The idea is that pushing the button will initiate a connection to the MQTT server Three-LED lightfrom the ESP8266 embedded inside the button and then, based upon which ESP just called, the MQTT server will initiate a process. That process could be anything from ordering another bottle of Rivella from Amazon, to switching on the stair lights, or announcing that there’s someone at the back door (or just about anything else you can think of where a momentary push button is used). The dollar-store light, as it comes, incorporates three, white LEDs and a latching switch, with a battery box for three, AA batteries built into the bottom. The construction of these lights is extremely flimsy (I just cracked the battery cover on the one pictured above while positioning it for the photo), but they are still amazing value for what they are. Taking the thing apart reveals that the LED “reflector” is nothing more than a flimsy sheet of white card, but that doesn’t matter, as we won’t be using it anyway. In fact, we can remove the LED PCB completely and just hold onto the white LEDs for some other project (our Yellow board already has more than enough LEDs) just keeping the base for our project. The original switch is a latching type, which isn’t what we want, either. On Prototype internalsthe first LED light that I bought, I tried to dismantle the original switch, with the idea that I’d convert it into a momentary type by throwing away the latching mechanism. However, the mechanism is so tiny that my motor skills weren’t up to the job (and that tiny little spring will no doubt be found by the vacuum cleaner, eventually). In the end, I simply removed the plunger from the original switch and glued a momentary “tact” switch on top of it (which put the new switch at exactly the right height to work with the push mechanism). The Yellow board (this time minus its own battery box) fitted perfectly on top of the built-in battery box (with that bit of flimsy white card insulating it from the exposed battery connectors), while some extra circuity fitted on a strip of prototyping board at the end of the battery box opposite to the switch. Despite the sticky-tape and hot glue, this prototype worked very well.

The circuitry on the prototyping board deserves a quick explanation here. The whole purpose of this project was to have an ESP8266 which would sit doing nothing at all for very long periods. Basically, that’s something that the ESP isn’t very good at. Even if it’s sitting there in deep sleep, it can still deplete three AA batteries in about three months, which is not very useful for a doorbell/dash type application, so the intention here was to have the ESP turned off completely, until it needed to wake up. The button implies physical interaction, so we know we can get the unit to switch on for the length of time that the button is held closed, at least. For a doorbell type application, that’s likely to be some significant part of a second, at a minimum, but still probably not long enough for the ESP to go from a cold start, through negotiating a connection with an access point to sending an MQTT message.  What  we needed was a method for the ESP to ensure, once it had been initially powered, that it could latch the power on until its work was complete and then (importantly) turn it back off again.

The answer is provided by the circuit below.  The ESP will start up very quickly indeed, especially if you don’t muck about trying to initialize serial ports, or wireless, or sending debug messages.  So what we do in our application is, as the very first thing, use the pinMode() command to initialize a designated pin as an output (which we’ll call “POWER_SW”, for power-switch) and then call digitalWrite() to set POWER_SW to “HIGH”, then we go back to our normal setup() routines.  This happens in a few milliseconds and we use the output of the GPIO pin designated as “POWER_SW” to drive the PWR_LATCH input signal of our latch circuitry, below.†

MOSFET power-latch schematic

Okay, so how does all of this work?  Well first of all, power is coming in from the battery box on the L/H side of the diagram on the pads marked as BAT+ and BAT-.  Our Yellow board is supplied from the R/H side pads, marked PWR_OUT and GND.  In between, right at the top, there are two more pads marked as SW_BATT and SW_VREG.  These are the two wires to our actual momentary contact “tact” switch, which we just pressed (a few milliseconds ago) to provide the initial power connection between the battery box and the ESP.  What we have now is a situation where the switch has been released, the battery is disconnected from the ESP8266 again and the charge on the smoothing capacitor next to the voltage regulator on the Yellow board is rapidly starting to deplete.

The ESP though, has already started up from cold, initialized the POWER_SW GPIO and set it to high.  As we noted, that GPIO is actually driving the PWR_LATCH line (bottom, R/H side of the diagram above).  This is connected to the gate of Q2, an N-channel MOSFET and will turn that device on, pulling the voltage at the junction of R1 and R3 low in turn.  This turns Q1 (a P-channel MOSFET) on, providing an alternative path for the battery power to reach the ESP8266.  As long as the “POWER_SW” signal remains high, the power will remain connected to the ESP8266.

As you’ve probably already guessed, once the ESP has finished running its designated tasks, it simply flips the POWER_SW signal from HIGH to LOW and, before the ESP8266 has time to print out “Goodbye cruel world!” to the console, the power is gone.

Why don’t we just invert the logic and have the ESP8266 drive Q1 directly, instead of adding the extra components around Q2?  Well the main reason is that the battery pack on the LED light has three 1.5 volt batteries in it.  When they’re brand new, the total voltage across all three cells can measure as high as 4.8 volts.  The ESP, as we all know (despite rumours to the contrary) is a 3v3 device and the voltage at the end of the resistor chain of R1 and R3 would float up to full battery voltage when the ESP was switched off and not pulling that line low, so Q2 is mainly there to protect the ESP from the full battery voltage (the N-channel used for Q2 needs to be chosen to withstand full battery voltage with some additional headroom, which isn’t asking much of a stand-alone MOSFET).  The chain of R1 and R3 is also there to protect the gate of Q1, the p-channel device, from being driven beyond its gate/source voltage limit (which is unlikely in this particular circuit, anyway).

Finally, the question is, which GPIO should we use as the POWER_SW drive?  Well, GPIO-0, GPIO-2 and GPIO-15 are bad choices, because they are all read at power-up time to determine which mode the ESP will boot into.  GPIO-16 is a good candidate though, as we definitely will not be connecting it to the reset pin for deep-sleep wake-up in this project.  If you want to change this on your particular board, feel free.  Just change the definition in the user_config.h file (and ensure that your chosen GPIO is not used for any of the other functions).

Update – The prototype auto-latching switch board worked very well, so I’ve knocked together a PCB using mostly through-hole components (that’s what I have in my parts drawers and besides, my hands are too shaky for SMD stuff nowadays …I can just about manage the AO3415, but only because I can’t find it in TO-92).  If anyone wants to roll their own, the board files (Eagle format) are available on GitHub.  My next version will probably provide an alternative SMD footprint for the 2N7000 (using the AO3414, which is what I had originally planned), but feel free to go 100% SMD if you prefer.

Okay, that’s our hardware.  The software is coming along next in two or three separate pieces.

† – It’s worth noting that if the GPIO on the ESP8266 isn’t initialized to “digital” mode, the MOSFET switch will be held “on” and you’ll be unable to switch it off (which is somewhat counter intuitive).