Some hardware links on this page are affiliate links. As an Amazon Associate I earn from qualifying purchases. We may earn a commission if you buy through them, at no extra cost to you. See the affiliate disclosure.
A battery remote that stays on Wi-Fi is a battery remote that dies. This one never joins a network. A button wakes an ESP32-C3, the chip sends a single BTHome v2 advertisement, and it goes back to deep sleep. Home Assistant already understands that packet through the BTHome integration, as long as a Bluetooth proxy (or the host adapter) is in range.
The shipped YAML is four buttons plus a battery divider. ESP32-C3 can wake from GPIO0 through GPIO5 only, so six buttons is the ceiling, and the sixth button takes the pin the battery divider was using.
A reed that reports open and closed over Wi-Fi, with the same board and a slower sleep, is battery reed door sensor. Filtering noisy BLE adverts at the proxy is BLE proxy advert filter. Short and long press on a mains binary sensor, without a radio, is binary sensor short and long press.
What problem it solves
You want a few wall or nightstand buttons that Home Assistant can see, without a Zigbee hub and without charging the remote every week.
How it works
- Wake. Each button is a momentary switch to ground on a wake-capable GPIO with the internal pull-up on. Any one of them waking the chip is enough. A backup timer (pack default 12 hours) wakes it anyway so the battery percent still arrives.
- Gesture. The firmware classifies the press before it waits on the radio. Held for 700 ms is a long press. Released, then pressed again within 400 ms, is a double press. Otherwise it is a single press. Every configured button is written into the packet, with “none” on the ones you did not touch, so Home Assistant keeps stable
button_1…button_Nevent classes. - Advertise. One BTHome v2 service-data burst (about 1.5 seconds), then the radio is turned off. The packet carries a packet id, battery percent, and the button bytes. Encryption stays off until you replace the
OFFplaceholder with 32 hex characters. - Sleep. The firmware waits until the button is released (up to 20 seconds, so a held long-press does not immediately wake it again), arms every button pin for a low-level wake, arms the timer, and deep-sleeps.
- Home Assistant. The BTHome integration fires
bthome_ble_event. The blueprint listens for that device and runs the press, double, or long action you set for each button.
There is no API and no OTA. The first flash, and every later change, is USB.
Hardware notes
- Pins. The YAML uses GPIO0, GPIO1, GPIO3, and GPIO5 for buttons, and GPIO4 for the divider. GPIO2, GPIO8, and GPIO9 are strapping pins. Leave GPIO8 and GPIO9 for the board’s boot button. A fifth button can use GPIO2 if you set
ignore_strapping_warning: trueon that pin. A sixth button replaces the battery pin, and then the advertisement has no battery byte. - Divider. Two equal resistors from battery positive to GPIO4 to ground. The YAML multiplies the ADC by
divider_ratio(default 2.0) and maps 3.0 V to 0% and 4.2 V to 100%. - Current. Months on one cell is realistic only after the board is actually quiet. Many Super Mini boards keep a power LED and a regulator that still draws while the chip is asleep. Lift the LED, or power the chip from a charger board whose standby current you have measured. The firmware cannot fix a part that is still lit.
- Download mode. Use the board’s BOOT button (GPIO9 on the Super Mini) when USB flashing does not start. Do not hold a remote button that shares a strap pin while you plug the cable in.
Snippet
Names and pins are substitutions. The bindkey stays OFF until you choose to encrypt.
substitutions:
device_name: bthome-remote
friendly_name: "Button remote"
bthome_bindkey: "OFF"
battery_pin: GPIO4
divider_ratio: "2.0"
button_1: GPIO0
button_2: GPIO1
button_3: GPIO3
button_4: GPIO5
sleep_hours: "12"
esp32:
board: esp32-c3-devkitm-1
framework:
type: esp-idf
Setup outline
- Copy
esphome/bthome-button-remote.yamland theesphome/components/folder into your ESPHome config, keeping that relative path.secrets.example.yamlis in the pack because every ESPHome pack includes one. This device YAML does not read it. - Edit the substitutions if you are not building the four-button default. Install over USB.
- Put a Bluetooth proxy, or the Home Assistant host’s own adapter, where it can hear the remote. Press a button. The BTHome integration should add a device after that first burst.
- Copy
blueprints/automation/bthome_button_remote.yamlintoconfig/blueprints/automation/edotdev/and reload blueprints. - Create the automation, pick the BTHome device, and fill the press, double, and long actions you care about. Leave the rest empty.
- To encrypt, generate 32 hex characters, put them in
bthome_bindkey(keep the quotes), reflash over USB, and enter the same key in the BTHome device options. Do not publish the key.
Troubleshooting
- Home Assistant never sees it. Nothing is in range that forwards BLE advertisements, or the burst ended before the proxy’s scan window. Raise
advertise_ms. Confirm the proxy is up. A timer wake (the 12 hour one) sends the same packet even with no button. - It wakes and never sleeps. A button is still held, a pin is floating, or the LED and regulator are the thing you are measuring. The firmware waits up to 20 seconds for a release, then sleeps anyway. A pin that sits low will wake it again immediately.
- Double press arrives as two singles. The second press was later than
double_ms(400). The gesture is decided on the device, before the radio starts, so proxy delay is not the cause. - Button numbers shift. Every advertisement includes every configured button. If you change the pin list and reflash, Home Assistant may add or drop
button_Nclasses. Re-pick the actions. - Battery stays at 0. The divider is not on GPIO4, the ratio is wrong, or the ADC has not produced a sample yet. A fast press still waits up to a second for a reading, then sends 0 if there is none.
- Encryption does not parse. The bindkey must be exactly 32 hex characters, and the same value must be set on the BTHome device.
OFF, or a radio address that is still all zeros, sends the packet in the clear.
Credits
The sleepy BTHome button pattern is discussed in Ultralow power (i.e. battery-powered) BTHome buttons. The byte layout follows the public BTHome format. This pack’s broadcaster is original; see docs/THIRD-PARTY-NOTICES.md.