homebridge-xbloom
v1.1.3
Published
Brew your saved xBloom coffee recipes from HomeKit/Siri over Bluetooth LE.
Maintainers
Readme
homebridge-xbloom
Brew your saved xBloom coffee recipes from HomeKit / Siri, over Bluetooth LE.
Each recipe you define becomes a HomeKit switch, so you can say "Hey Siri, turn on Ethiopia" or trigger a brew from any HomeKit automation (alarm, sunrise, presence, …). The plugin connects to the machine only when needed and frees it again afterwards, so your phone's xBloom app keeps working normally.
Unofficial. Not affiliated with, endorsed by, or supported by xBloom. It talks to the machine locally over Bluetooth (no cloud) using a clean-room understanding of its BLE protocol. Use at your own risk.
Features
- 🎯 One switch per recipe — trigger by name with Siri, scenes, or automations.
- ☕ Recipes defined in the config UI — dose, grind, ratio, and full pour profile (water / temperature / flow / pattern / pause per pour). No app or capture required.
- 🔄 Connect-on-demand — no permanent Bluetooth connection; the machine stays free for your phone between brews (see Bluetooth & the single-device limit).
- 📊 Live status — an
xBloom Brewingsensor in Apple Home, plus fault reporting for no beans / tank empty / bad dose, and a log of grind → pour → complete with live ml. - 🛑 Optional Stop Brew switch and an optional manual Bluetooth hold/release switch.
Accessories it exposes
| Accessory | Type | Default | Purpose |
|---|---|---|---|
| (each recipe) | Switch | — | Turn on to brew that recipe (auto-resets) |
| xBloom Brewing | Contact Sensor | on | Open while grinding/brewing; fault on errors |
| Stop Brew | Switch | on | Aborts the current brew |
| xBloom Bluetooth | Switch | off | Manual hold/release of the BLE link (override) |
Requirements
- Homebridge v1.8+ or v2.x running on Node.js 22 or 24.
- A Linux host with BlueZ (
bluetoothd) and a working Bluetooth adapter (most Raspberry Pi / NUC / mini-PC setups). The plugin talks to BlueZ over D-Bus. - An xBloom machine (tested on the original/Studio-class,
XBLOOM …BLE name). - The machine must be physically loaded (pod or grounds, cup, water) for a brew — Bluetooth only triggers the program; it can't load coffee.
macOS/Windows Homebridge hosts are not supported for the BLE link (BlueZ is Linux-only).
Installation
Option A — Homebridge UI (recommended)
- In the Homebridge UI, go to Plugins, search
homebridge-xbloom, and install. - Open the plugin settings and add your machine + recipes (see Configuration).
- Make sure the host can reach Bluetooth (see Linux / BlueZ setup).
Option B — Command line
sudo npm install -g homebridge-xbloomLinux / BlueZ setup
The plugin uses node-ble, which speaks to the
system Bluetooth daemon over D-Bus (it coexists with bluetoothd — no adapter takeover).
- Install and enable BlueZ:
sudo apt install bluez sudo systemctl enable --now bluetooth bluetoothctl power on - Permissions. If Homebridge runs as root, BlueZ's default policy already allows
access. If it runs as a non-root user, grant that user access to
org.bluezby creating/etc/dbus-1/system.d/node-ble.conf:
Replace<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd"> <busconfig> <policy user="homebridge"> <allow send_destination="org.bluez"/> <allow send_interface="org.bluez.Adapter1"/> <allow send_interface="org.bluez.Device1"/> <allow send_interface="org.bluez.GattService1"/> <allow send_interface="org.bluez.GattCharacteristic1"/> <allow send_interface="org.bluez.GattDescriptor1"/> <allow send_interface="org.freedesktop.DBus.ObjectManager"/> <allow send_interface="org.freedesktop.DBus.Properties"/> </policy> </busconfig>homebridgewith the actual user, thensudo systemctl reload dbus.
Docker Homebridge
Only needed if your Homebridge runs in Docker. Native installs (Raspberry Pi, etc.) need nothing here — the plugin uses the system Bluetooth automatically.
A Docker container can't see the host's Bluetooth by default, and the official
homebridge/homebridge image (with ENABLE_AVAHI=1) runs its own internal D-Bus. Three
one-time things are required:
- Mount the host's D-Bus socket into the container.
- Unconfine AppArmor for the container (otherwise the host's dbus-daemon blocks it).
- Set
dbusAddressin the plugin config so it uses that host bus.
Steps 1 & 2 — edit your Homebridge docker-compose.yml:
services:
homebridge:
# …existing config…
volumes:
- ./volumes/homebridge:/homebridge
- /run/dbus/system_bus_socket:/run/host-dbus/system_bus_socket:ro # ← add this
security_opt:
- apparmor=unconfined # ← add this (AppArmor blocks container→host D-Bus otherwise)Then docker compose up -d to recreate the container.
Why
apparmor=unconfined? On hosts with AppArmor (e.g. Ubuntu), the default Docker profile + dbus-daemon mediation block a container from talking to the host's system D-Bus (you'd see "An AppArmor policy prevents this sender…"). Unconfining lets the container reach Bluetooth. The Homebridge container already runs with host networking; if you'd rather keep confinement, write a custom AppArmor profile that allowsdbus send.
Step 3 — set dbusAddress in the plugin config to that path:
unix:path=/run/host-dbus/system_bus_socketThat's it. The plugin points only its own Bluetooth connection at the host bus — your container's avahi / HomeKit advertising is left completely untouched (no container-wide environment variables). The image runs as root, so BlueZ's default permissions are enough.
Finding your device (address is optional)
You usually don't need the MAC address. Leave deviceAddress blank and the plugin
auto-discovers your machine by its advertised name (default prefix XBLOOM ). xBloom machines
advertise as XBLOOM <number>, so the default matches.
Set an explicit address only if you have more than one xBloom, or auto-discovery isn't finding it. To find it:
bluetoothctl --timeout 12 scan le | grep -i xbloom
# e.g. [NEW] Device AA:BB:CC:DD:EE:FF XBLOOM 123456Other ways to find the MAC: the xBloom app (machine/Bluetooth info), or your router's DHCP/device list (look for the XBLOOM hostname).
Either way, the machine only advertises while awake and not connected to another device (disconnect the phone app first).
Configuration
Use the Homebridge UI settings, or add a platform block to config.json:
{
"platforms": [
{
"platform": "XBloom",
"name": "xBloom",
"exposeBrewingSensor": true,
"exposeStopSwitch": true,
"exposeConnectionSwitch": false,
"holdTimeoutSec": 300,
"brewTimeoutSec": 300,
"dryRun": false,
"recipes": [
{
"name": "Morning Pour Over",
"cupType": "xdripper",
"doseGrams": 18,
"grinderSize": 36,
"rpm": 60,
"ratio": 17,
"pours": [
{ "volume": 55, "temperature": 93, "flowRate": 3.5, "pattern": 0, "pausing": 40 },
{ "volume": 95, "temperature": 93, "flowRate": 3.5, "pattern": 1, "pausing": 20 },
{ "volume": 85, "temperature": 92, "flowRate": 3.5, "pattern": 2, "pausing": 20 },
{ "volume": 71, "temperature": 92, "flowRate": 3.5, "pattern": 2, "pausing": 5 }
]
}
]
}
]
}Platform options
| Option | Default | Description |
|---|---|---|
| deviceAddress | — | Machine BLE address. Optional — leave blank to auto-find by name. Set only for multiple machines. |
| deviceName | XBLOOM | Name prefix used to auto-find the machine when no address is set. |
| recipes | [] | Your recipes (see below). Each becomes a switch. |
| dryRun | false | Log the frames that would be sent without touching Bluetooth. Great for first setup. |
| exposeBrewingSensor | true | Expose the xBloom Brewing status sensor. |
| exposeStopSwitch | true | Expose the Stop Brew switch. |
| exposeConnectionSwitch | false | Expose the manual xBloom Bluetooth hold/release switch. |
| holdTimeoutSec | 300 | When manually held on, auto-release after this many seconds (0 = until turned off). |
| brewTimeoutSec | 300 | How long to watch a brew for completion before releasing the link. |
Recipe options
| Field | Notes |
|---|---|
| name | Switch name / Siri phrase. |
| cupType | xdripper, xpod, or other (sets the cup weight bounds). |
| doseGrams | Beans to grind, 5–40 g. |
| grinderSize | Grind setting, 1–80. |
| rpm | Grinder speed: 60/70/80/90/100/110/120. |
| ratio | Brew ratio (1:N), 1:5–1:25 in 0.5 steps (e.g. 17 for 1:17). |
| pours[] | Ordered pour stages. Volumes should sum to doseGrams × ratio (see note below). |
| pours[].volume | Water for this pour, ml. |
| pours[].temperature | °C (40–100). |
| pours[].flowRate | e.g. 3.5. |
| pours[].pattern | 0 = center, 1 = circular, 2 = spiral. |
| pours[].pausing | Seconds to pause after this pour. |
| pours[].vibBefore / vibAfter | Optional grounds vibration. |
| cupMax / cupMin | Advanced: explicit cup weight bounds (g), overrides cupType. |
Dose & ratio are the source of truth. The total water =
doseGrams × ratio(e.g. 18 g × 17 = 306 ml), and your pour volumes should add up to that. If they don't, the plugin logs a warning at startup — the machine will still pour exactly the volumes you entered, but the actual ratio in the cup won't match what you set. Adjust the pour volumes to sum todose × ratio, or change the dose/ratio.
Tip: dial in your recipe in the official xBloom app first, then copy the numbers (dose, grind, ratio, and each pour's water/temp/flow/pause/pattern) into the config.
Usage
- Siri: "Hey Siri, turn on Morning Pour Over."
- Automations: trigger a brew from a HomeKit automation (alarm, time of day, a button…).
- The recipe switch turns itself off automatically when the brew finishes.
- The
xBloom Brewingsensor shows Open while grinding/brewing and reports a fault if the machine reports no beans, tank empty, or bad dose.
Remember the machine must be loaded (pod/grounds + cup + water) — Bluetooth only starts the program.
Bluetooth & the single-device limit
The xBloom connects to only one device at a time. If your phone's app is connected, the plugin can't connect, and vice-versa. This plugin is designed around that:
- Connect-on-demand (automatic). The plugin holds no permanent connection. It connects only when you start a brew, streams status until the brew completes, then disconnects — so the machine is free for your phone the rest of the time. You don't have to do anything.
- Manual override (optional). Enable
exposeConnectionSwitchto get anxBloom Bluetoothswitch:- On → the plugin connects and holds the link (e.g. to reserve it).
- Off → releases it immediately, handing the machine back to your phone (even mid-brew — the machine keeps brewing on its own once started).
- A safety
holdTimeoutSecauto-releases the link so a manual hold never occupies the machine indefinitely.
If you try to brew while your phone is connected, the brew will fail to connect — just close the app (or disconnect it) and try again.
Troubleshooting
"No device advertising a name starting with XBLOOM was found"
Auto-discovery couldn't see the machine. Make sure it's awake and not connected to your
phone (one device at a time). If you have multiple xBloom machines, set an explicit
deviceAddress (find it with bluetoothctl --timeout 12 scan le | grep -i xbloom).
Device not found / connect times out
- The machine only advertises while awake and not connected to your phone. Wake it and disconnect the app, then retry.
- Confirm the adapter is on:
bluetoothctl show→Powered: yes(bluetoothctl power on).
operation timed out or le-connection-abort-by-local
Transient BLE hiccups, common right after another connect/disconnect. The plugin retries; if it
persists, toggle the adapter (bluetoothctl power off && bluetoothctl power on).
Permission denied / org.bluez access
Homebridge is running as a non-root user without a D-Bus policy. Add the policy in
Linux / BlueZ setup and sudo systemctl reload dbus.
Docker: org.bluez was not provided by any .service files
The plugin reached the container's D-Bus, which has no Bluetooth. Mount the host D-Bus socket and
set the dbusAddress config option — see Docker Homebridge. (Two one-time
steps; no container-wide environment variables needed.)
Wrong Node version
Homebridge v2 requires Node 22 or 24. Check with node -v.
I just want to test without brewing
Set dryRun: true. The plugin logs the exact frames it would send and never touches Bluetooth.
How it works
xBloom exposes a serial-style BLE service (0000e0ff-…) with a write characteristic (FFE1)
and a notify characteristic (FFE2). A brew is the same four-frame sequence the official app
sends — set dose, set cup bounds, send the recipe, execute — and the machine streams status
codes back (grind/pour/complete, plus live weight and dispensed-ml telemetry). This plugin
re-implements that protocol natively in TypeScript and validates it against real captures.
Credit to the reverse-engineering work in PyBloom and xbloom-ai-brew.
Support
If this saved you a few taps before coffee, you can support development here:
Bug reports and PRs welcome on GitHub.
Disclaimer
This is an unofficial, community project. It is not affiliated with or endorsed by xBloom. Brewing involves heat, water, and moving parts — only trigger a brew when the machine is properly loaded and attended. The author is not responsible for any damage, mess, or under-caffeination resulting from use.
License
MIT © Abdulaziz Alharbi
