A HACS-installable Home Assistant custom integration that controls a Terma MOA WiFi towel-radiator/heater locally over Bluetooth Low Energy (BLE), using the same BluFi protocol the manufacturer's app uses. It exposes one climate entity per heater: target temperature, on/off, and mode presets (Manual / Schedule / Dryer / Antifreeze).
- Fully local, no cloud. This integration talks directly to the heater over BLE. It never contacts Terma's servers, and it doesn't need (or use) the Terma mobile app, an account, or internet access to function day-to-day.
- It re-implements the heater's BluFi/protobuf command protocol (temperature, power, mode) and telemetry parsing based on real captured traffic. There's no vendor SDK involved.
- It does not modify or replace the heater's own control panel or app — you can keep using those. HA and the app talk to the same physical device.
- Home Assistant with Bluetooth support (either a local Bluetooth adapter reachable from the HA host, or an ESPHome Bluetooth proxy, or both).
- The heater's 6-digit pairing PIN (see below).
- HACS (recommended) or manual installation into
config/custom_components/.
BLE has limited range, and — unlike Bluetooth proxying for e.g. sensors — controlling this heater requires an actual bonded (paired) connection, because the heater requires passkey authentication.
- You don't need an ESP32 if the Home Assistant host itself has a Bluetooth adapter that is in range of the heater. In that case the integration can attempt to bond directly against the heater using your PIN (Path A, see below).
- You do need an ESP32 if the HA host is out of Bluetooth range of the heater (e.g. HA runs on a server/NAS in a different room, or as a VM/container without direct BLE access). In that case, flash a cheap ESP32 board with the bundled
esphome/terma-proxy.yamlconfig, place it near the heater, and let it act as both a Bluetooth proxy and the bonding client (Path B, see below). This is the recommended, most reliable setup.
The Terma MOA WiFi heater has a 6-digit pairing PIN, printed on/near the device (a label on the unit) or shown in the Terma mobile app under the device's Bluetooth/pairing settings. This is the same PIN the official app asks for the first time you pair with the heater. You'll enter this PIN into the HA config flow (and, for Path B, into the ESPHome YAML) — it's not a value you invent yourself.
- Make sure the HA host's Bluetooth adapter can see the heater (it should show up via discovery).
- In Home Assistant, go to Settings → Devices & Services → Add Integration and search for "Terma MOA WiFi" (or wait for automatic discovery — the integration advertises a Bluetooth matcher, so a newly-in-range heater should prompt you to set it up).
- Enter the device PIN when asked.
- The integration will attempt to pair/bond with the heater directly over the local adapter using the PIN, then verify the connection. If this succeeds, the
climateentity appears.
Note: local-adapter bonding (Path A) depends on your Bluetooth stack (BlueZ on Linux) and is best-effort — some hosts/adapters/OS combinations don't expose the D-Bus pairing agent interface this relies on. If bonding via Path A doesn't work for your setup, use Path B (ESP32) instead, which is the more reliable, tested route.
- Copy
esphome/terma-proxy.yamlinto your ESPHome configs. - Edit the substitutions at the top:
terma_pin: your heater's 6-digit PIN.terma_mac: the heater's current BLE MAC address (find it via Bluetooth discovery/scanning, e.g. in HA's Bluetooth integration or a BLE scanner app).
- Make sure your
secrets.yamlhaswifi_ssid/wifi_passworddefined (or inline them, not recommended). - Flash the config to an ESP32 board and place it within BLE range of the heater.
- Put the heater into pairing mode: hold both physical buttons on the heater for about 3 seconds until the LED starts pulsing blue. This opens a pairing window.
- While the heater is in pairing mode, the ESP32 (configured with
io_capability: keyboard_onlyand anon_passkey_requesthandler that replies withterma_pin) will bond with it automatically. Watch the ESPHome logs to confirm the bond succeeded. - Once bonded, the ESP32 acts as a Bluetooth proxy for Home Assistant. In HA, go to Settings → Devices & Services → Add Integration, search for "Terma MOA WiFi" (or accept the automatic discovery prompt), and enter the same PIN. The integration will verify the connection through the proxy — no direct pairing is required from HA's side, since the ESP already bonded.
You only need to redo the pairing-mode step (step 5) if the heater is factory-reset, unpaired, or you're pairing a replacement ESP32.
Once set up, the heater appears as a single climate entity:
- Target temperature: adjustable between the heater's supported range, in 0.5 °C steps.
- HVAC mode: Off / Heat.
- Presets:
manual,schedule,dryer,antifreeze— mirrors the heater's own operating modes. Selecting "dryer" runs the built-in towel-dry timer.
Use the standard HA climate card/dashboard tile, automations, or scripts against this entity like any other climate device.
not_bondederror in the config flow: the integration could connect to the heater's BLE advertisement but the authenticated connection failed. This almost always means the heater isn't bonded yet with whatever adapter/proxy HA is using. For Path A, retry — local BlueZ bonding can be flaky; if it keeps failing, switch to Path B. For Path B, re-check that the ESP32 actually completed a bond (see its logs) and thatterma_pin/terma_macare correct.- Device found but connection times out / entity is "unavailable": check range — BLE has a much shorter effective range than WiFi. Move the ESP32 proxy closer, or add a second proxy.
- ESP32 proxy loses the connection intermittently: make sure
power_save_mode: noneis set underwifi:in the ESPHome config. WiFi power-saving on the ESP32 interferes with the timing BLE connections need and is a common cause of dropped or flaky BLE links. This is already set in the bundledterma-proxy.yaml— don't remove it. - Heater address changed / integration can't find the device anymore: BLE MAC addresses can change after a factory reset or firmware update. Re-scan and update
terma_macin the ESPHome config (Path B) or re-add the integration (Path A). - PIN doesn't work: double check you're using the pairing PIN from the device/app, not a Wi-Fi password or account password — this integration doesn't use the heater's cloud/WiFi credentials at all, only the local BLE pairing PIN.
The BLE command protocol (BluFi framing + a minimal hand-rolled protobuf encoder/decoder) was reverse-engineered from real captured traffic between the Terma app and a Terma MOA WiFi heater. There's no dependency on any vendor library or protobuf schema file — field numbers and framing were derived empirically and are golden-tested against real captured frames in tests/.
This is an unofficial, community-developed integration and is not affiliated with, endorsed by, or supported by Terma or any of its subsidiaries. It was built by reverse-engineering observed Bluetooth traffic and may stop working if the manufacturer changes the device firmware or protocol. Use at your own risk — in particular, be careful with temperature/mode automations on a device that heats towels/rails, and always verify behavior manually before relying on unattended automations. No warranty of any kind is provided; see LICENSE (MIT).