The short version:
I have setup an esp32 that can raise an alert should the mains power go out. Ideally requires an esp that has both a usb port and a battery socket plus a pin that has voltage to it only when power is being provided via usb - although likely all of that could be done by some deft soldering on different esp boards that don’t have all of that.
The longer version:
I have a number of esp based devices in my house, some of which I deem fairly critical. For example, ones that measure the temperature of the freezer. The issue is that if the fridge/freezer loses power generally that also means that the esp device is no longer monitoring the freezer.
I’d like to know how warm the freezer got to before the power came back (so I know if the food has to be thrown out). I’d also like to get alerted should someone accidentally turn the power off to the fridge/freezer & esp device (which has happened) - so I can do something about it.
This is just one use case scenario - I am sure that there are many more out there. For example, I have various esp devices scattered about the house used for ble proximity tracking that could also provide alerts should one specific power circuit lose power due to a safety fuse trip. Note - keep in mind that all my network kit is supported by UPS so if the power goes out, the network stays up for a while so alerts will keep firing.
I had already given some esp devices effectively mini UPS’s - basically plug the esp into a usb battery pack that is in turn connected to a wall charger. Worked fine, and a good way of using any old 18650 cells laying around, but hard to monitor if there is an issue.
How to do it. I had a hunt through the forums and couldn’t see any examples of people doing it. Found a number of people asking about something like this, with various suggestions using external AC relays, and a few other options like using Ring extenders, but it all seemed a bit complex and expensive. Quite likely I just missed an example of someone doing just this, but I seemed to be on my own. Fine. My theory - setup an esp that is powered via usb but that also has a battery. When the power goes out, the device runs off the battery, and detects that there is no power in the 5v rail so sends an alert. When power is restored, it can send an alert about that.
I decided to try a Lolin D32 - it has been around for ages, is cheap, and has both USB and battery sockets. As a bonus, I had a spare to play with. It also has a pin marked “USB” that, when the esp32 is getting power from the USB port power is sent to that pin. I figured that if I connected the USB pin to one of the pins that would be able to toggle it high or low as power turned on/off via the usb port.
So, time to code. Turned out to not be too hard. Pretty quickly I was able to test the thing, and it worked first time. I use Bermuda for proximity tracking so my code is basically for one of those - obviously you could use the basics for any device you have out there - just maybe changing the GPIOs to match depending on what you are using.
# Compiled and tested on esphome 2025.2.2 and HA 2025.3.0
# Notes:
# * need to jumper between USB pin and pin 16 - when USB power is connected, power goes to the USB pin and sets pin 16 high
# * Using a Lolin D32 as cheap and has built in battery socket
# * Note that need to check polarity on battery - in my case frequently they are reversed.
substitutions:
name: ble03
friendly_name: ble03
devicename: ble03
location: master
esphome:
name: ${name}
friendly_name: ${friendly_name}
min_version: 2024.6.0
name_add_mac_suffix: false
project:
name: ninkasi.ble
version: '1.1'
comment: BLE Sensor LOLIN D32 $location
platformio_options:
build_flags:
- "-D CONFIG_ADC_SUPPRESS_DEPRECATE_WARN=1" # Putting this in temporarily to remove warning “legacy adc calibration driver is deprecated" message during compilation - https://github.com/esphome/issues/issues/5153#issuecomment-1847547482
esp32:
board: esp32dev
framework:
type: esp-idf
version: recommended
# Custom sdkconfig options
sdkconfig_options:
COMPILER_OPTIMIZATION_SIZE: y
# Advanced tweaking options
advanced:
ignore_efuse_mac_crc: false
# Enable logging
# Change to avoid "Components should block for at most 20-30ms" warning messages in the log - an issue since 2023.7.0
# Not really a breaking change - it's an issue I suspect due to the device being slow and this error previously
# simply not being reported
logger:
baud_rate: 0 # disable serial uart logging to maybe save a little ram
logs:
component: ERROR
api:
encryption:
key: !secret esphome_encryption_key
ota:
password: !secret ota_password
platform: esphome
wifi:
networks:
- ssid: !secret wifIoT_ssid
password: !secret wifIoT_password
priority: 2
# Backup SSID just in case
- ssid: !secret wifi_ssid
password: !secret wifi_password
priority: 1
# Enable fallback hotspot (captive portal) in case wifi connection fails
ap:
ssid: "$devicename Fallback Hotspot"
password: !secret ota_password
esp32_ble_tracker:
scan_parameters:
# continuous: True
active: True
interval: 211ms # default 320ms
window: 120ms # default 30ms
bluetooth_proxy:
active: true
sensor:
- platform: wifi_signal
name: "WiFi Signal Sensor"
id: wifisignal
update_interval: 60s
unit_of_measurement: dBm
accuracy_decimals: 0
device_class: signal_strength
state_class: measurement
entity_category: diagnostic
- platform: copy # Reports the WiFi signal strength in %
source_id: wifisignal
id: wifipercent
name: "WiFi Signal Percent"
filters:
- lambda: return min(max(2 * (x + 100.0), 0.0), 100.0);
unit_of_measurement: "Signal %"
entity_category: "diagnostic"
- platform: uptime
id: uptime_s
name: "$devicename Uptime"
update_interval: 60s
- platform: template
name: $devicename free memory
lambda: return heap_caps_get_free_size(MALLOC_CAP_INTERNAL);
icon: "mdi:memory"
entity_category: diagnostic
state_class: measurement
unit_of_measurement: "b"
update_interval: 60s
# Define the battery pin (GPIO35) as an ADC sensor
- platform: adc
pin: GPIO35
name: "Battery Capacity"
id: battery_capacity
icon: mdi:battery-medium
unit_of_measurement: "%"
accuracy_decimals: 0
attenuation: 12db
update_interval: 60s # Update every 60 seconds (adjust as needed)
filters:
- multiply: 2.0
- median:
window_size: 7
send_every: 7
send_first_at: 7
- throttle: 15min
- calibrate_polynomial:
degree: 3
datapoints:
- 0.00 -> 0.0
- 3.30 -> 0.0
- 3.35 -> 5.0
- 3.39 -> 10.0
- 3.44 -> 15.0
- 3.48 -> 20.0
- 3.53 -> 25.0
- 3.57 -> 30.0
- 3.62 -> 35.0
- 3.66 -> 40.0
- 3.71 -> 45.0
- 3.75 -> 50.0
- 3.80 -> 55.0
- 3.84 -> 60.0
- 3.88 -> 65.0
- 3.92 -> 70.0
- 3.96 -> 75.0
- 4.00 -> 80.0
- 4.05 -> 85.0
- 4.09 -> 90.0
- 4.14 -> 95.0
- 4.20 -> 100.0
- lambda: |-
if (x <= 100) {
return x;
} else {
return 100;
}
on_value_range:
# Trigger an action if the battery voltage goes below a threshold (e.g., 3.3V)
- above: 30
then:
- logger.log: "Battery voltage is above threshold"
- below: 30
then:
- logger.log: "Battery power detected (below threshold)"
# Optional: Set a custom threshold to trigger actions, e.g., battery level below 3.3V
binary_sensor:
- platform: template
name: "Low Battery"
lambda: |-
if (id(battery_capacity).state < 30) {
return true;
} else {
return false;
}
on_press:
- logger.log: "Battery is low"
on_release:
- logger.log: "Battery is back to normal"
# Define the GPIO pin connected to USB power detection
- platform: gpio
pin: 16 # GPIO pin connected to USB power
name: "USB Power Status"
device_class: power
filters:
- delayed_on: 100ms
- delayed_off: 100ms
on_press:
# Action to take when USB power is disconnected (i.e., running on battery)
then:
- logger.log: "Running on battery power"
on_release:
then:
- logger.log: "Running on USB power"
# Optionally, trigger actions for when USB power is present
So if you want to give this a go, you just compile the code (changing to suit your device), send it to your esp32, connect the jumper wire, and hook up a battery & usb charger. Aside from devices that have no wiring, this has to be one of the simplest wiring diagrams ever. Run dupont jumper wire from USB pin to pin 16 (originally used 5 and was totally fine, but changed to 16 to avoid strapping pin warning messages). Enjoy.
Notes:
- Now I know it works, I’m probably going to convert a number of my sensors over to this. I don’t need to do all of them, but some key ones definitely.
- I am sure there are many different esp devices that could be used for this. Feel free to try something else.
- This is still early days - I have not run this for an extended period so cannot confirm as to how reliable it is, but so far it’s been good
- Eventually the battery will die I suspect, given that it is on charge constantly although the Lolin does appear to turn off charging for a while - monitoring it to see what level the battery needs to drop to before it starts charging again, but even so it’s not great.
- Although small, if the battery does fail there is a risk - if also small - that it will do so in a spectacular fashion involving flames so best not to hide this sort of thing in a roof cavity.
- I’ve used a 1200 mAh battery because that’s what I had - this easily supports the esp32 running as a ble tracker for over 8 hours. Unless it’s being used to monitor a fridge/freezer, could probably use a much smaller battery.
- You could have the esp raise alerts directly, maybe flash lights etc, but I tend to prefer putting all that into automations so I can manage them separately. That’s just me - again, feel free to do it differently. An example automation would be (obviously changing to match your details):
alias: Alert - ble03 has detected a power outage!
description: ""
triggers:
- trigger: state
entity_id:
- binary_sensor.ble03_usb_power_status
to: "off"
for:
hours: 0
minutes: 0
seconds: 5
conditions: []
actions:
- data:
title: ble03 has detected a power outage!
message: ble03 has detected a power outage!
action: persistent_notification.create
- metadata: {}
data:
message: ble03 has detected a power outage!
title: ble03 has detected a power outage!
target: [email protected]
action: notify.example_com
- action: notify.mobile_app_pixel_9_pro
data:
message: ble03 has detected a power outage!
title: ble03 has detected a power outage!
mode: single