This repository contains code for ESP32 V2 Adafruit Feather to measure temperature and send it to MQTT broker via WiFi. The Feather is located in a fuse box, running from a 5V power. It reads pulses from a wire connected to a power meter.
This is somewhat similar to the shield project. Likewise, I used this project as a way to learn more about electronic circuitry, CircuitPython, MQTT, microcontrollers, etc. and I mostly enjoyed the process of building it.
Here is a bill of materials:
Purpose | Name |
---|---|
microcontroller | ESP32 Feather V2 with w.FL antenna connector |
antenna | RP-SMA: 2 dBi |
pigtail | RP-SMA to w.FL / MHF3 / IPEX3 Adapter |
temperature/humidity sensor | Adafruit AHT20 - Temperature & Humidity Sensor Breakout Board - STEMMA QT / Qwiic |
power meter | SDM 72D 0,25-100A MID |
5V power supply | Meanwell HDR-15-5 |
resistor | 1k Ohm |
wires | 22 AWG, black/red/blue |
Wago connectors | 3 x 221-413 |
enclosure | 3D Printed Case for Adafruit Feather - solid lid, bottom with wings |
mounts | nylon screw and stand-off set |
Most of the stuff comes from Adafruit.
In the midst of the energy crisis, I wondered what the power consumption of our household looks like in detail, esp. with running a small home lab.
I came across a web that completed just that using Arduino. Being a fan of Adafruit and CircuitPython, I wanted to replicate this with these.
Testing with ESP32-S2 that has built-in antenna placed inside the fuse box, revealed once again that there is some problem in the stack as some of the MQTT messages sent from the Feather to the MQTT broker are lost.
From the last Adafruit order, I had the antenna pigtail, and I found a USB WiFi adapter that came with small 2 dBi antenna. Since the WiFi adapter cost like 5 EUR, I used the antenna and trashed the USB dongle (of course, I recycled it properly with other electronic waste I had).
Initially, to test the code that was still heavily based on the shield code, I connected temperature/humidity sensor to have some data sent out.
Then I figured why not to leave the sensor in place. There will not be many variations inside the fuse box, however it might be interesting to see the values in the long term.
To place the microcontroller inside the fuse box securely, I needed a box. Since the local manufacturer of 3D printers has a map with places where it is possible to have the models printed, it was quite easy. I found a school in walkable distance that has a workshop with more than 10 printers, called the teacher responsible for the workshop, and I had the box ready in 2 days for cheap.
To fasten the microcontroller inside the case, I used the nylon screw and stand-off set. I am glad that I ordered the screw set earlier without having a firm plan what to use it for.
The nice thing about the case is that the Neopixel blinking (in blue, for half a second), which happens whenever a message is published to MQTT broker, is visible through the lid of the enclosure (also blue color) as it is a bit translucent.
The color coding I choose is based on the STEMMA QT spec -
red for V+ power, black for ground. The pulse wire connected to GPIO pin 37 (board.D37
in the code) is blue (matching the color
of the box for a bonus !). There are several tricks on how to determine and maintain the mapping between pin on the board and the CircuitPython board
attribute. One is to look into CircuitPython source code, another is to
save the dict(board)
to a .txt
file on the microcontroller (or elsewhere) for easy reference.
To get reliable pulse readings, a pullup resistor is needed. In order to connect all the wires and the resistor together I used the Wago connectors. I cut the leads to the resistor a bit so it almost completely hides inside the Wago connectors. The only soldering necessary was for the 3 wires attached to the Feather.
The only struggle I had was powering the Feather. It turned out that both +V and -V outputs from the power supply have to be used. There is very nice tear down video of the DR-15-5 on https://www.youtube.com/watch?v=2S5OFr9VSnY , which actually made me think about how to connect it properly. The +V is connected to the USB input/output pin and -V is connected to GND pin. Thus, the -V is connected to the Wago connector together with the GND from Feather and the '-' impluse contact from the power meter.
The +V connected to the USB pin has some caveats - care needs to be taken not to connect USB cable while the +V is connected.
This is a crappy diagram of how the wires are connected:
And here is the final result:
The power meter and the power supply were installed by an electrician, however then I needed to add a wire to -V on the power supply and one needs to be really careful while the fuse box is under current. Dangling wire with exposed contact is a no-no - just add a empty Wago connector to prevent it from touching the phase wires. Or shut the whole fuse box altogether and grab a head lamp.
The code is simple - in endless cycle it collects and sends the pulse count (and sensor data) to MQTT broker. Since it has to count the pulses, it would be undesirable to enter any kind of hardware sleep that would prevent that. It is also not needed, given the microcontroller is connected to 5V power supply inside the fuse box.
As for the pulse counting, it is made easy thanks to CircuitPython wrapping interrupts in the countio module, which I believe happens on ESP32 V2.
Firstly, the microcontroller needs to be converted to run CircuitPython. To do that, for ESP32 V2, initially I chose the command line esptool
on a Linux computer (since macOS appeared to have flaky serial connection for some reason).
Specifically, this command was used:
esptool.py --port /dev/ttyACM0 write_flash -z 0x0 \
~/Downloads/adafruit-circuitpython-adafruit_feather_esp32_v2-en_US-x.y.z.bin
These days it can be also quite conveniently done using WebFlasher with the .bin
file provided on https://circuitpython.org/board/adafruit_feather_esp32_v2/
The web workflow for this case is actually a boon, since going into the fusebox cabinet in order to change the code would be undesirable and potentially dangerous.
Having static IP address for the microcontroller is handy.
Once CicuitPython is installed, perform the initial set up by creating the settings.toml
file in the root directory (using screen
when the board is connected via USB data cable):
f = open('settings.toml', 'w')
f.write('CIRCUITPY_WIFI_SSID = "wifissid"\n')
f.write('CIRCUITPY_WIFI_PASSWORD = "wifipassword"\n')
f.write('CIRCUITPY_WEB_API_PASSWORD = "XXX"\n')
f.close()
and restart the microcontroller.
Then the following can be used:
- copy
*.py
files to the root directory using web workflow, assumes system withcurl
installed:for f in *.py; do curl -v -u :XXX -T $f -L --location-trusted http://172.40.0.9/fs/$f; done
- create
secrets.py
in the root directory (using the same technique as in the previous step) - install necessary libraries from Adafruit CircuitPython bundle to the
lib
directory:circup --host 172.40.0.9 --password XXX install -r requirements.txt
The number of pulses is stored in a counter in Prometheus.
The contents of /etc/prometheus/mqtt-exporter.yaml
should look like this:
mqtt:
# The MQTT broker to connect to
server: tcp://localhost:1883
# The Topic path to subscribe to. Be aware that you have to specify the wildcard.
topic_path: devices/#
# Optional: Regular expression to extract the device ID from the topic path. The default regular expression, assumes
# that the last "element" of the topic_path is the device id.
# The regular expression must contain a named capture group with the name deviceid
# For example the expression for tasamota based sensors is "tele/(?P<deviceid>.*)/.*"
device_id_regex: "(.*/)?(?P<deviceid>.*)"
# The MQTT QoS level
qos: 0
cache:
# Timeout. Each received metric will be presented for this time if no update is send via MQTT.
# Set the timeout to -1 to disable the deletion of metrics from the cache. The exporter presents the ingest timestamp
# to prometheus.
timeout: 15m
# This is a list of valid metrics. Only metrics listed here will be exported
metrics:
-
# The name of the metric in prometheus
prom_name: temperature
# The name of the metric in a MQTT JSON message
mqtt_name: temperature
# The prometheus help text for this metric
help: temperature reading
# The prometheus type for this metric. Valid values are: "gauge" and "counter"
type: gauge
-
# The name of the metric in prometheus
prom_name: humidity
# The name of the metric in a MQTT JSON message
mqtt_name: humidity
# The prometheus help text for this metric
help: humidity reading
# The prometheus type for this metric. Valid values are: "gauge" and "counter"
type: gauge
# A map of string to string for constant labels. This labels will be attached to every prometheus metric
#const_labels:
# sensor_type: dht22
-
# The name of the metric in prometheus
prom_name: pulses
# The name of the metric in a MQTT JSON message
mqtt_name: pulses
help: Pulse count
# The prometheus type for this metric. Valid values are: "gauge" and "counter"
type: counter
The MQTT exporter by itself sets the sensor
tag to the last component of the topic,
which in this case is the device name. To get also the location (2nd component of the topic),
re-labeling in Prometheus itself is used.
Under the scrape_configs
section in /etc/prometheus/prometheus.yml
there should be:
- job_name: mqtt
# If prometheus-mqtt-exporter is installed, grab metrics from external sensors.
static_configs:
- targets: ['localhost:9641']
# The MQTT based sensor publish the data only now and then.
scrape_interval: 1m
# Add the location as a tag.
metric_relabel_configs:
- source_labels: [topic]
target_label: location
regex: 'devices/([[:alnum:]]*)/[[:alnum:]]*'
action: replace
replacement: "$1"
The dashboard is rather simple:
It captures only the first day of measurement, so the per month values are not real yet.
The dashboard can be downloaded from Electricity.json.
For getting the data displayed in Grafana the way I wanted, the Explore tab is immensely useful. In my case, the power consumption graph displayed only wide bars and I found out that the scaping interval has to be made smaller in order to get enough data points, which in turn makes finer grained resolution and thus narrower columns.
There needs to be a secrets.py
file that contains Wi-Fi credentials and information about the MQTT broker.
It can look like this:
# This file is where you keep secret settings, passwords, and tokens!
# If you put them in the code you risk committing that info or sharing it
secrets = {
"ssid": "foo",
"password": "bar",
"broker": "172.40.0.3",
"broker_port": 1883,
"mqtt_topic": "devices/fusebox/esp32",
"sleep_duration": 30,
"log_level": "INFO",
"log_topic": "logs/fusebox/esp32",
}
To transfer the file to the microcontroller, the same method as in the Install section should be used.
Adafruit has largely such a good documentation that the links are worth putting here for quick reference:
- CircuitPython countio
- ATH20 guide
After completing the project, I found there are many sophisticated setups out there that allow electricity consumption monitoring for individual rooms, basically per breaker. These are usually done by having multiple power meters and/or dedicated gadgets that have multiple sensors placed on individual phase wires in the fuse box.
Rether than going this way, further cluttering the fuse box, I might use electricity consumption readings from various smart plugs around the house that are already connected to the IoT network.