This is part of my home automation setup. For details, see my blog.
- MySoilSensorESP32 -- a multi-channel soil moisture sensor with long battery life
- Objective
- Design decisions
- Building blocks
- Hardware
- Batteries
- Firmware
- MQTT messages
- Over-the-air update
- Debugging support
- Power consumption and battery life
- Lessons learned
In summer, I have a lot of potted plants out on the balcony, and I need to know when to water them. Indoors, most of my home automation gadgets are based on MySensors, but out on the balcony the signal is too weak, so I needed something WiFi-based, but without the high battery consumption typical of WiFi solutions.
There will be rain out on the balcony, so the modules need to be waterproof-ish. Real waterproof sealed cases are expensive, so I opted for a cheap plastic case with a Ziploc bag over it, with the opening facing down ... good enough for the intended use case.
To exchange information between the soil sensors and the home automation system (soil moisture and performance characteristics from the device, commands to change operating parameters or start over-the-air updates to the device), I could think of the following technologies:
- MQTT. Pro: I alreeady have an MQTT broker running, and this is independent of a specific home automation system. Con: Communication is a bit unpredictable, as I found out
- HTTP requests to a REST API, e.g. for OpenHAB. Pro: More deterministic, you know exactly when a message has been accepted. Con: Specific for one home automation system
I went for (1).
The final design uses an ESP32-WROOM module.
I also tried a ESP32-C3 "Super Mini" module, the source code supports this. This is a single core RISC-V based processor, with the following pros and cons, compared to the ESP32:
- Pro: lower current draw when WiFi is on, 100 mA vs 130 mA
- Pro: easy to use the little development module in a production system, because it has no USB-serial chip that would consume power all the time, the ESP32-C3 has builtin USB capability
- Con: higher current draw during sleep, I measured about 85µA
- Con: unreliable WiFi connection.
The unreliable WiFi connection with the ESP32-C3 probably has nothing to do with the processor per se, but with the board layout: the module I bought from Aliexpress looks exactly like the “bad module” shown in this post.
- a bare ESP32-WROOM module is better than a development module -- probably due to the voltage regulators and USB interfaces on those modules. Power consumption during deep sleep, with a bare ESP32-WROOM, powered from 2x AAA battery, is under 10µA.
- to avoid any power loss through a voltage regulator, I am running the ESP32 directly from a pair of AAA batteries. Newer processors will work down to 2.3V, I just had to make sure the soil moisture sensor also works below 3V (see below)
- the soil sensors are powered by GPIO pins, which are turned off during sleep
- to minimize the time the processor is awake, and in particular the time that WiFi is active, I try to reconnect to WiFi using previously established SSID, IP address and channel number, which are cached in RTC memory
- an ESP32 module with custom Arduino-based software
- two AAA batteries, which should last for more than a year
- up to 4 cheap capacitive soil sensors, of the kind you find on Aliexpress for less than a Euro
- my home network, with a DHCP server assigning pseudo-static IP addresses to everybody, based on MAC address
- my home automation system, with an MQTT broker and OpenHAB for visualization and control
The hardware is very simple, see the schematic and the pictures above.
For development and debugging, I added pin connectors to connect to FTDI-232 style USB-to-serial interfaces, one for the primary UART (for firmware upload) and one for UART#2, where the software outputs logging messages. For a production system, this is not necessary and can be left out, of course.
One option is to power the ESP32 directly from two AA or AAA batteries. The battery life calculations reported below apply to this configuration.
As an alternative, I have built a few units with a rechargable battery and a solar panel for charging, attached to the outside of the case. This works as well, if the case can be positioned such taht the solar panel faces the sun. Here is teh bill of materials for this variant:
- a "5V 75mA 80x45mm" solar panel, from Aliexpress, ca. €1
- a TP4056 battery management module, from Aliexpress, ca. €0.20
- a small Li-ion battery, 3.7V at 1000 mA, from Reichelt (p/n AKKU 30389), €1.80 ... this is a noname replacement battery for some GPS receiver. I have also used a replacement battery for a 1st gen Apple iPod nano.
- a low-drop, low quescent current voltage regulator for 3.3V, HT7833
The software was developed using Platformio. Just download the repository contents into an empty folder, and open that folder as a Platformio project.
In folder src/
, rename file myauth_sample.h
to myauth.h
and enter your
Wifi SSID and password.
Edit file platformio.ini
and enter the COM port and IP address for your
environment. I built multiple units and wanted to update each of them over-the-air,
so I needed a separate entry for each in platformio.ini
, specifying each
IP address as assigned by my DHCP server.
I used a 7x9 cm prototype board for ESP32 and ESP8266 boards, cut to fit inside a 100x60x25 mm plastic case. The soil sensors are connected via cables with an RJ12 6P4C connecter on the module end, for easy connection "in the field".
The capacitive soil sensors I got from Aliexpress had an NE555 chip that only works down to 3.0V, whereas the ESP32 itself will still work when the battery voltage is as low as 2.5V, so I replaced the NMOS NE555 with the CMOS version ILC555D, which work down to 2.0V. I also bypassed the voltage regulator on the sensor board.
The device publishes to MQTT topics that include its own hostname:
- to
soil/hostname/debug
it publishes a JSON string with various internal parameters, such as battery voltage, uptime etc., once every 12 hours - to
soil/hostname/state
it publishes a JSON string with measurement results, once every hour - to
soil/hostname/wifi
it publishes a JSON string with some performance metrics, such as Wifi quality, the time needed to connect to the Wifi access point, etc., once every hour - it subscribes to
soil/hostname/ota
to enable over-the-air updates (see below) - it subscribes to
soil/hostname/config
to receive a JSON-format string enable to change some operating parameters (see source code for details)
For example, my device has the hostname esp32-D80270
, so it will publish to
soil/esp32-D80270/state
etc.
The device supports OTA firmware updates, despite the fact that it is in deep
sleep most of the time. How? When it wakes up, it subscribes to MQTT topic
soil/hostname/ota
(see above), and if it finds a retained message
with the payload of 1
or ON
, it enables ArduinoOTA and waits for an upload
of new firmware, instead of going to sleep. It also deletes the topic to
acknowledge that it has received the command.
To perform an OTA firmware update,
- compile your code for the target that has "-ota" in its name (look at
platformio.ini
and adjust the IP address or device hostname to match your conguration) - publish a retained message for topic
soil/hostname/ota
with the payload of 'ON' For my deviceesp32-D80270
, I run the following shell comand on the home automation server:
mosquitto_pub -r -t "soil/esp32-D80270/ota" -m "ON"
- wait for the item to disappear. I watch it with a tool like
MQTT Explorer, or run this shell command
repeatedly
mosquitto_sub -t "soil/esp32-D80270/ota"
- upload your firmware via Platformio
The software prints a lot of logging messages to UART#2 (GPIO pins 16 and 17), to which I connect an optically isolated FTDI-style USB to serial module. This was really helpful for understanding what goes on, and where dekays occur.
As explained above, the time the module stays awake each measurement cycle is important for overall power consumption, so I investigated the wake time in some detail.
I built several of these units, some connected directly to the Wifi access point (a AVM Fritzbox 7530), and some via a Wifi repeater (a Fritz Repeater 1200ax), over a distance of 3-5m, through a massive outer brick wall (or maybe through a window).
On average, the wake time is about 400 ms for all units. Now and then, maybe once or twice a day, the wake time is much larger, above 1000 ms. This appears to happen mostly when the Wifi access point has switched to a a different Wifi channel. When I configured my access point to use a fixed Wifi channel, these long wake time events could no longer be observed.
These factors contribute to power consumption or battery drain, in decreasing order of importance:
- the self-discharge of the AAA batteries, according to [1], about 35 µA or 0.84 mAh per day
- deep sleep, at ~8 µA or 0.2 mAh per day
- wake time with WiFi ON, 130 mA x ~600ms per wakeup or 0.52 mAh per day
- wake time, with WiFi OFF, 30 mA x ~300ms per wakeup or 0.06 mAh per day
This adds up to a charge of 87mC per wake period, which closely matches an actual measurement with a Nordic Power Profiler.
We have brought the design to the point where overall power consumption is dominated by the self-dischange of the Alkaline batteries, so there is no point in attempting to further optimize the power consumption of the processor module.
The ADC on the ESP32 module has a bad reputation, there are many websites and forum posts that document its bad linearity and high noise level. For this application, the inferior performance is acceptable, because we are not attempting to make high precision measurements, just give an indication of "soil is ok" vs "soil is too dry".
The performance of the device, in terms of power consumption or battery life, depends of course on the design of the device itself and the design decisions I made (processor, program flow, etc.), but also on external factors that I cannot control. For example, power consumption depends on wake time, which is the aggregate of
- the time between turning on sensor power, and measuring the sensor voltage -- I chose 100ms, which appears to be enough for the sensor voltage to settle
- the time to re-connect to the Wifi access point, using stored information about WiFi channel, SSID etc -- this takes about 160ms most of the time, but sometimes can take as much as 2-3s
- the time to look up the IP address for the MQTT broker, usually 20-40ms
- the time to connect to the MQTT broker, mostly 30ms, but sometimes up to 500ms, which I can't explain
- the time to send MQTT messages -- I can influence this, by deciding how much information to send
- the time to disconnect from the Wifi access point -- mostly about 20ms, but sometimes as high as 130ms, which I can't explain
I found it fairly easy to create a first version of the code that sort of worked most of the time. Then I began to notice the rough edges, and it took a lot of time to get those fixed:
- easy: making measurements and reporting them via MQTT. Harder: get the measurements to be less jittery, and less affected by varying current consumption due to WiFi activity
- allow configuration changes via MQTT, and acknowledge the command by deleting the topic. Easy: get it to work most of the time, with occasional loss of a message. Harder: make it work every time, by inserting delays here and there, without a massive increase in the overall wake time
When the WiFi router is set to channel 12 or 13, connection time is much higher (>2000ms) than on WiFi channel 1-11 (200-300ms) ... didn't expect that, should have read this before!