A native Home Assistant integration for the Fellow Stagg EKG+ electric kettle. It talks to the kettle directly over Bluetooth LE using Home Assistant's built-in Bluetooth stack, so no separate bridge, server, or Homebridge instance is required.
This is a from-scratch reimplementation of the protocol used by the homebridge-stagg-ekg-plus project, available in HACS.
- Features
- Supported devices
- Requirements
- Installation
- Setup
- Connection mode
- Entities
- How data is updated
- Known limitations
- Troubleshooting
- Example automation
- Blueprints
- Removing the integration
- Protocol notes
- Development
- AI disclosure
- Credits
- License
- Climate, switch, sensors, and binary sensors for the kettle - set the target temperature, turn it on/off, and read current/target temperature, the auto-off (hold) timer, holding status, and base presence. See Entities.
- Follows the kettle's Fahrenheit/Celsius setting automatically (104-212 F / 40-100 C).
- Local push: state streams live over Bluetooth notifications, with automatic reconnect.
- Selectable connection mode (on demand or persistent), with an optional background poll to catch a physical power-on. See Connection mode.
- Works with a local Bluetooth adapter or an ESPHome Bluetooth proxy.
- Automatic Bluetooth discovery.
- Fellow Stagg EKG+ (the Bluetooth model). It advertises as
FELLOWfollowed by the last bytes of its address (for exampleFELLOW46B9). - Not supported: the Fellow Stagg EKG Pro (a different, WiFi-based kettle with its own protocol) and the original non-smart Stagg EKG.
- Home Assistant 2026.3 or newer.
- A Bluetooth adapter on the Home Assistant host, or an ESPHome Bluetooth proxy within range of the kettle.
Important: Bluetooth LE allows only one active connection to the kettle at a time.
- Click the button above, or search for Fellow Stagg EKG+ in HACS.
- Install Fellow Stagg EKG+.
- Restart Home Assistant.
Copy custom_components/stagg_ekg_plus into your Home Assistant config/custom_components/ directory and restart.
The kettle is discovered automatically once it is powered and in range. You will get a notification to set it up, or add it manually:
- Go to Settings -> Devices & Services -> Add Integration.
- Search for Fellow Stagg EKG+ and select the discovered kettle.
Go to Settings -> Devices & Services -> Fellow Stagg EKG+ -> Configure to choose how Home Assistant talks to the kettle:
- On demand (default): connects when you send a command and stays connected while the kettle is powered on, so temperature streams live while it heats and holds. It disconnects a few seconds after the kettle turns off to free the Bluetooth adapter; entities then show the last known state.
- Persistent: keeps one Bluetooth connection open at all times for always-live state and instant commands. A keep-alive watchdog reconnects automatically if the link goes quiet.
On demand shares the adapter more politely and is the default; choose persistent for always-live state or the fastest control while the kettle is off.
On demand cannot tell when the kettle is switched on physically (using the dial on the kettle), because the kettle does not broadcast its state - while disconnected, Home Assistant has no way to know. The background poll closes that gap: at the interval you choose (Off by default, or every 1 / 2 / 5 minutes) Home Assistant briefly reconnects while the kettle is off, checks its state, and disconnects again unless it finds the kettle on - in which case it keeps the connection and streams live.
A shorter interval notices a physical power-on sooner but uses the adapter more often; Off keeps the adapter free but means physical power-ons are not reflected until you next control the kettle from Home Assistant. The setting has no effect in persistent mode (already always connected).
| Entity | Platform | Notes |
|---|---|---|
| Kettle (target temp + heat/off) | climate |
Follows the kettle's F/C unit; range 104-212 F / 40-100 C |
| Power | switch |
On/off |
| Current temperature | sensor |
Unavailable when off or lifted off the base |
| Target temperature | sensor |
|
| Hold timer | sensor |
Auto-off countdown (60 min with hold, 5 min post-boil) |
| Holding temp | binary_sensor |
On once at target and maintaining |
| On base | binary_sensor |
On when seated on the base |
| Hold enabled | binary_sensor |
Physical hold-slider position (disabled by default) |
| Signal strength | sensor |
Bluetooth RSSI, diagnostic (disabled by default) |
This is a local push integration. While connected, the kettle streams its state over Bluetooth notifications (roughly once a second), and Home Assistant updates the entities as those arrive - there is no polling of values. The optional background poll (on-demand mode) does not poll values; it only briefly reconnects to learn whether the kettle has been switched on.
- Hold (keep-warm) and the temperature unit (F/C) are physical-only. The kettle exposes no Bluetooth command for them, so they are read-only here.
- One Bluetooth connection at a time. If another app, bridge, or an old Homebridge/Pi server is connected to the kettle, Home Assistant cannot connect until it is released.
- Signal strength is the last advertised value, not a live measurement (the kettle stops advertising while connected).
- Current temperature is unavailable when the kettle is off or lifted off its base - the kettle only reports a real reading while actively measuring.
- On-demand mode without the background poll cannot detect a physical power-on (see Connection mode above).
- Entities show unavailable / cannot connect: make sure nothing else is connected to the kettle, and that it is within range of a Bluetooth adapter or ESPHome proxy. Bluetooth allows only one connection at a time.
- Slow connects or
[Errno 12] Out of memoryin the log: these come from the host's Bluetooth adapter being under load, not the integration. A longer background-poll interval or moving the kettle onto an ESPHome Bluetooth proxy reduces the load. - Filing a bug: open the device page (Settings -> Devices & Services -> Fellow Stagg EKG+ -> the device) and use Download diagnostics; attach that to the issue. Enabling debug logging (see below) and including the log helps too.
Notify when the water has reached the set temperature:
automation:
- alias: Kettle ready
triggers:
- trigger: state
entity_id: binary_sensor.fellow_stagg_ekg_plus_holding_temp
to: "on"
actions:
- action: notify.notify
data:
message: "The kettle has reached temperature."An automation blueprint that shows an iOS Live Activity for the kettle: the current temperature while it heats, a "Ready" alert when it reaches the target, and it clears when the kettle turns off.
Requirements: Home Assistant 2026.3 or newer (same as the integration), the Home Assistant Companion app on iOS (Live Activities need iOS 16.1 or newer), and this integration set up.
Click the button above to import it, then create an automation from the blueprint and fill in:
- Power Switch - the kettle's
switchentity - Holding Temp Sensor - the
binary_sensor.*_holding_tempentity - Climate Entity - the kettle's
climateentity - Notify Service - your phone's direct notify service
(e.g.
notify.mobile_app_yourphone), not a group (group services do not carry the Live Activity token) - Notification Tag and Device Name - optional; sensible defaults are provided
Go to Settings -> Devices & Services -> Fellow Stagg EKG+, open the three-dot menu on the entry, and choose Delete. If you installed it through HACS and want it gone entirely, remove it from HACS afterward and restart Home Assistant.
Two details of the kettle's Bluetooth protocol that are easy to get wrong:
This frame backs the Hold timer sensor: the countdown, in seconds, until the
kettle powers itself off. The value is a 16-bit little-endian number - it's
split across two bytes with the small part sent first - and the pair is sent
twice for redundancy (e.g. 10 0e 10 0e). Reassemble it as low + (high * 256),
so 10 0e -> 0x0e10 = 3600 seconds = 60 minutes.
- With the hold (keep-warm) slider on, it starts at 3600 (60 min).
- With hold off (the short post-boil warm), it starts at 300 (5 min).
- It counts down to
0, then the kettle shuts off; it reads0when not in a hold window.
Reading only the first byte makes it look like a meaningless counter that rolls
255 -> 0 every 256 seconds; reading both bytes reveals the real timer.
This is a clean on/off flag that is true only when the kettle has reached the
target and is actively maintaining temperature (keep-warm), and false while it
is still heating up or off. It is more reliable than 0x01 (the physical
hold-slider position), which flickers as the heating element cycles on and off
at setpoint. 0x06 stays steady, so it is the dependable "is the kettle keeping
the water warm right now" signal, and it is what drives the Holding temp binary
sensor.
A standalone Bluetooth probe is included for protocol testing without Home Assistant:
python3 -m venv .venv
.venv/bin/pip install bleak
.venv/bin/python tools/scan.py # find the kettle
.venv/bin/python tools/probe.py # connect and print decoded state
.venv/bin/python tools/probe.py --power on --temp 200The protocol codec lives in custom_components/stagg_ekg_plus/api.py and has no
Home Assistant dependencies, so it can be exercised directly.
To inspect the raw Bluetooth protocol from inside Home Assistant, enable debug
logging on the integration (Settings -> Devices & Services -> Fellow Stagg EKG+
-> three-dot menu -> Enable debug logging). The log then contains the raw frames
(rx ...), each decoded frame (frame 0xNN ...), and the decoded state on every
change (state ...).
This integration was developed with the help of AI coding tools. I am not a programmer by trade. Every change is reviewed by a human (me) before it is committed, and the integration has been tested against a real Fellow Stagg EKG+ kettle. Issues and pull requests are welcome if you spot something that can be improved.
Protocol reverse engineering by
philscott-dev,
tlyakhov, and
levi. tlyakhov first mapped out the
state frame types. This integration decoded the 0x04 auto-off timer as a 16-bit
little-endian value (his code read only its low byte and guessed it was a lift
countdown) and worked out the 0x06 "actively holding" signal (which his code
left undecoded).
GPL-3.0. See LICENSE.