ESP32 - alert on power outage

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. :slight_smile:

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

4 Likes

FYI Did some playing with the code to get a more accurate read on the battery as well as remove some compilation warnings. Have now updated the code above.

Very elegant solution. I’ve got ESPHome-based Bluetooth Proxy devices near BLE temperature sensors that are inside freezers & fridges. (I love the Switchbot Indoor/Outdoor Hygrometers for that.) This could indeed track the temps during power outages.

And even if I don’t need an ESP/battery setup in one place where HA can detect power failure of other (mains-powered) devices, and BLE reporting can continue thru a POE-powered BT Proxy, you’ve certainly given me ideas for additional monitoring automation.

1 Like

Interesting. Glad it’s working for you! I initially tried some battery powered sensors, but had issues with signal range and battery life. This was years ago and not sure the Switchbot stuff was available then. Anyhow I eventually decided to use ds18b20 sensors that come in stainless steel probes and ran the wires direct to an esp stuck to the outside of the fridge/freezer. :slight_smile:

Oh, many years before I started using esphome, I setup physical switches connected to the door(s) so I can raise an alert if one is left open for too long.


Have two fridges - one plays the Imperial March from Star Wars, the other plays Ah-Ha “Take on me”. I made those using arduino pro minis with passive buzzers to play midi files - very nasty noise, surprisingly loud, and works really well to provide incentive for even the laziest teenager to get up and close the door. Thought about replacing them with ESPs, but could not replicate the sound. :rofl:

1 Like

Just curious- What did you use with ESPs to try replicating sounds? I’ve been meaning to try an ESPHome with passive buzzer, using RTTL. Is that likely to be usable sound?

Yeah, I can’t say enough good about the Switchbot BLE “indoor/Outdoor Thermo-Hygrometers”. They are so solid in reaching my BT Proxy BT devices. I thought one had a signal issue, and moved a BT Proxy nearby. But turned out that was the fault of the BT Proxy that was already supposed to be covering that end of the house. I don’t know how the Switchbot units get thru the metal cabinets, but they do (maybe thru door seals?). Device specs say BT range is 394 feet.

I’ve got another one outdoors, on the outside of a metal building, and that one works fine too. They are amazing. And cheap. And IP65 waterproof.

Good luck with that. I tried and failed to work out how to do it properly with an esp device. Best option at the time seemed to be to use an actual speaker, but it really didn’t nail that horrible greeting card noise I was aiming for. That’s why mine are still using arduino pros as seen here. With RTTL support now it definitely seems possible so I might revisit.