ESPHome external component for the Powerpal BLE energy monitor. Reads power, energy and battery directly over BLE: no phone app, no Powerpal cloud.
Refactored from
WeekendWarrior1/esphome@powerpal_ble,
which was a fork of ESPHome itself and stopped compiling after
ESPHome 2025.6.x. This repo packages the same protocol logic as a
modern self-contained external_components folder, so it builds
against current ESPHome (2026.x+) and tracks upstream's BLE APIs
without forking.
Differences from the upstream fork:
- Modern external_components layout: no ESPHome fork required.
- ESP-IDF framework (the original Arduino-framework path is the source of the upstream's bit-rot).
- Cloud-upload path removed. This is intended as a local-only component, there's no need to share your energy use data with others.
- Defensive fallback: the pairing-code write fires from
ESP_GATTC_SEARCH_CMPL_EVTas well asESP_GAP_BLE_AUTH_CMPL_EVT, with a single-write gate. The Powerpal does not require BLE-level bonding, andAUTH_CMPLmay not fire at all under current Bluedroid / IDF v5; relying on it alone causes the connection to stall.
Any ESP32. Tested on M5Stack AtomS3 Lite (ESP32-S3FN8). Place within BLE range of the Powerpal: a few metres through one or two interior walls is fine.
esp32:
board: esp32-s3-devkitc-1
variant: esp32s3
framework:
type: esp-idf
esp32_ble_tracker:
ble_client:
- mac_address: XX:XX:XX:XX:XX:XX # see "Finding the MAC" below
id: powerpal
time:
- platform: homeassistant
id: homeassistant_time
external_components:
- source: github://pento/powerpal-esphome@main
components: [powerpal_ble]
sensor:
- platform: powerpal_ble
ble_client_id: powerpal
pairing_code: 123456 # from the Powerpal app / info card
notification_interval: 1 # minutes between batches (1–60)
pulses_per_kwh: 1000 # confirm against Powerpal app setup
time_id: homeassistant_time
power:
name: "Power"
daily_energy:
name: "Daily Energy"
energy:
name: "Total Energy"
battery_level:
name: "Battery"When a power sensor is configured, it publishes live updates by default:
the component subscribes to the device's pulse characteristic and drives
power from the live inter-pulse interval — updates every ~1.5–3.5 s, no
smoothing needed (the device pre-averages). Set live_power: false to instead
publish an average over the previous notification_interval minutes from the
batched measurement stream, once per interval. energy and daily_energy are
unaffected either way.
sensor:
- platform: powerpal_ble
# ...
live_power: false # opt out; publish the batched per-interval averageThe live subscription keeps a BLE notification stream open continuously, which
is more radio activity than the once-per-minute batched poll. Expect some
additional Powerpal battery drain, in the order of less than 0.5% per week.
Since live updates are on by default whenever a power sensor is present,
set live_power: false if you'd rather minimise radio activity. (With no
power sensor configured, leaving live_power unset is a no-op — and
live_power: true is rejected, since there's nothing to publish to.)
Individual values have some natural variance even with the device's
smoothing — layer ESPHome filters on the power sensor if you want more:
power:
name: "Power"
filters:
- median:
window_size: 5
send_every: 1At low loads (roughly under 30 W on a 1000 pulses/kWh meter, or under 10 W on a 3200 pulses/kWh meter), the live value is whatever the most recent pulse implied — it will look "stale" until the next pulse arrives. This is a property of pulse meters, not the protocol.
Use scripts/import_history.py to seed
Home Assistant's long-term statistics from Powerpal's cloud API. It
pulls every hourly reading the cloud has for your device and writes
the corresponding rows via recorder/import_statistics.
The MAC sometimes isn't printed on the Powerpal hardware. If this is the case, there are two ways to find it:
- Android: nRF Connect → scan → look for
powerpal NNNNNNNN. MAC is shown directly under the name (Android exposes real BLE MACs; iOS/macOS replace them with per-app UUIDs that don't work here). - The ESP32 itself: flash this component with a placeholder MAC.
On boot,
esp32_ble_tracker:logs every advertisement it sees, including the Powerpal's MAC. Update the YAML and re-flash.
The Powerpal supports a single BLE pairing at a time. Unpair it from your phone first (Bluetooth settings → forget device). Note the 6-digit pairing code somewhere persistent before doing so, since losing it means a factory reset.
After the ESP32 boots, look for the [powerpal_ble] log lines:
Writing pairing code to Powerpal →
ESP_GATTC_WRITE_CHAR_EVT (Write confirmed) → service reads and
notification subscriptions. First measurement arrives within
notification_interval minutes.
Optional log_api_key: true (default false) on the powerpal_ble
sensor platform. When set, the component reads the Powerpal's
cloud-API key from BLE characteristic 59DA0009-... once after
pairing succeeds and logs it at INFO level:
[I][powerpal_ble:NNN]: Powerpal API key (for cloud-API access): XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
Useful if you want to query Powerpal's readings.powerpal.net API
(e.g. to backfill historical data into Home Assistant) and your app
version doesn't expose "Generate an API Key" under Guidance. Toggle
on, OTA, capture from logs, toggle back off, OTA again. The key
identifies your account to Powerpal's servers and is otherwise the
same value on every read.
Config validation is covered by unit tests. Install the test dependencies
(see requirements-test.txt) into a virtualenv and
run pytest from the repo root:
python3 -m venv .venv && source .venv/bin/activate
pip install -r requirements-test.txt
pytestMIT.