LocalTRV
Local Bluetooth control for Viessmann ViCare radiator valves through a ubisys R0. LocalTRV is a Home Assistant integration that needs a ubisys R0 Series 2; the Series 1 has no Bluetooth and cannot be used.
Not affiliated with or endorsed by Viessmann or ubisys. ViCare is a trademark of Viessmann. Use at your own risk; no warranty.
How it works
The ubisys R0 Series 2 is a Zigbee router that also speaks Zigbee Direct: a way to reach a Zigbee network over Bluetooth. Home Assistant connects to the R0 over Bluetooth and talks to your ViCare valves through it. Your ViCare gateway, app and schedules keep working as before.
What you need
| Component | Required | Tested with |
|---|---|---|
| Radiator valves | Viessmann ViCare ZK03840 | 8 valves, firmware 01.18.0008 02.06, model id 7637434 |
| Zigbee router | ubisys Router R0 Series 2 (Series 1 has no Bluetooth), on the same Zigbee network as the valves | ubisys R0 Series 2 |
| ViCare gateway | the gateway that runs your ViCare Zigbee network | Vitoconnect OPTO2 |
| Home Assistant | 2026.10.0 or newer | 2026.10.1 on Home Assistant OS |
| Bluetooth | a local adapter or an ESPHome Bluetooth proxy in range of the R0 | ESP32-C3 ESPHome proxy |
Bluetooth details:
-
An ESPHome proxy must allow active connections:
bluetooth_proxy: active: true
-
The integration keeps one connection to the R0 open all the time. It needs one free connection slot on your adapter or proxy.
-
The adapter must handle large Bluetooth packets (MTU). The tested ESP32-C3 proxy negotiated 247, which is plenty. If yours is too small, Home Assistant shows a repair issue (Troubleshooting).
-
The R0 accepts only one Bluetooth client at a time.
You also need the network key of your ViCare Zigbee network. See the next section.
The network key
The integration needs the 128-bit network key of your ViCare Zigbee network (32 hexadecimal characters). Without it the R0 does not let anyone in. There is no way around this.
- This project does not retrieve the key for you.
- Only obtain the key for equipment you own or are authorized to administer.
- The third-party ViLocal guide describes one method, in the section "Find the Channel of your ViCare Network and sniff for your Network Key". It needs a Zigbee sniffer and (re)pairing a ViCare device; a spare or new device works too. Follow that guide's own instructions and warnings.
- Home Assistant stores the key in the config entry and uses it on every (re)connect. Home Assistant backups therefore contain it. Protect them.
- Never paste the key in issues, forum posts or logs. The integration never logs it.
Installation
HACS (recommended)
Or by hand:
- In HACS, open the menu (three dots, top right) and choose Custom repositories.
- Add
https://github.com/jurgenj136/localtrvwith type Integration. - Open LocalTRV.
- Download it and restart Home Assistant.
Manual
- Download a release.
- Copy
custom_components/localtrv/into thecustom_components/folder of your Home Assistant config. - Restart Home Assistant.
On first start, Home Assistant downloads the matching localtrv Python package from PyPI.
After that, the integration needs no internet.
Setup
- Make sure nothing else is connected to the R0 over Bluetooth: another adapter or proxy, another Home Assistant instance, or a phone app that talks to the R0.
- Add the R0:
- Discovered: go to Settings > Devices & services. Under Discovered you see ubisys R0 found. Click Add and confirm.
- Not discovered: click Add integration, search for LocalTRV, and choose your R0 in Select the ubisys R0.
- In Zigbee network key, enter the key (32 hexadecimal characters).
- Leave Test the connection on and submit.
Test the connection connects to the R0 and checks the key, then disconnects. It takes up to 60 seconds and writes nothing to the valves. It does not join the network or look for valves; that happens after you save. Turn it off to save without checking, for example when the R0 is briefly busy.
After saving, the integration joins the Zigbee network through the R0 and starts looking for valves. Each valve appears as its own device once it answers. Valves sleep most of the time, so this can take a few minutes.
New key. If the R0 rejects the stored key, Home Assistant asks you to Re-enter the network key. Enter the current key. It is tested before it is saved. If the test times out, the key is saved anyway; other connection errors keep the form open until you fix them and try again.
What you get
Each valve is a device "ViCare valve …" (Viessmann ZK03840), linked to the R0 device.
| Entity | Type | Notes |
|---|---|---|
| (valve name) | Thermostat | Heat mode only. Current and target temperature, 0.5 °C steps, limits from the valve (8-30 °C if the valve does not report them). Shows heating while the valve asks for heat, idle when it does not, unknown while the heating demand is unknown |
| Heating demand | Sensor | 0-100 %, how far the valve wants to open |
| Battery | Sensor | %. Diagnostic |
| Link quality | Sensor | Zigbee link quality to the valve's parent. Diagnostic, disabled by default |
| Signal strength | Sensor | dBm. Diagnostic, disabled by default |
| Window open (experimental) | Binary sensor | Disabled by default. The meaning of the raw value is still a guess |
The R0 device has:
| Entity | Type | Notes |
|---|---|---|
| Connection | Binary sensor | On while the R0 connection is ready. Diagnostic |
| Connection state | Sensor | Idle, Connecting, Joining, Ready, Waiting to retry, Authentication failed or Stopped. Diagnostic |
Setting a temperature
- The thermostat shows your new target right away, while the valve is still asleep.
- Before writing, the integration checks that the device at the valve's network address really is that valve. Only then does it send the new target.
- The call finishes when the valve acknowledges the write: usually a few seconds. On a weak link a dropped and resent request can stretch that to about 20 seconds; after 60 seconds the call gives up.
- If that fails, the target goes back to the old value and you get one of two errors:
- "… The valve could not be reached to confirm its address, so nothing was sent. Try again."
- "… The valve did not confirm the change." The valve may still have applied it; the thermostat shows the right value after the next poll.
- After the acknowledgement, the integration reads the target back in the background to confirm it. The call does not wait for that.
- A change from Home Assistant acts like turning the valve by hand: the ViCare app shows the room as "manually set, for 2 hours".
- Your ViCare gateway and schedule push their own target again later: after that period, or at the next schedule change. The last change wins. Only the ViCare app (or waiting) ends the manual override; Home Assistant cannot cancel it.
The thermostat also has the attributes setpoint_pending, setpoint_acknowledged and
setpoint_confirmed to follow a change.
How updates work
- The integration asks every valve for its state every 300 seconds by default. Valves do not send updates over this connection on their own, so polling is how the data arrives.
- Valves sleep and answer slowly: typically 2-4 seconds, sometimes 10.
- A valve that misses 3 polls in a row becomes unavailable (15 minutes at the default). It comes back by itself when it answers again, and the log says so.
- If all valves stay silent, the integration rejoins the network by itself. If the Bluetooth connection drops, it reconnects with growing pauses (up to about 6 minutes).
- Known valves are remembered across restarts. They show up right away but stay unavailable until they answer. The valve list is refreshed every 6 hours, so new valves appear on their own.
- Every poll wakes the valve's radio. Keep the default interval unless you need faster updates.
Options
Settings > Devices & services > LocalTRV > Configure. Saving reloads the integration.
| Option | Range | Default | Notes |
|---|---|---|---|
| Poll interval (seconds) | 60-3600 | 300 | Valves run on batteries; do not poll faster than you need |
| Route reply mode (under Advanced) | Live, Specification, Off | Live | Leave on Live unless asked to change it |
Known limitations
- Heat mode only. No control over schedules, holiday mode or turning a valve off.
- Window open is experimental and disabled by default.
- The R0 accepts one Bluetooth client. While this integration runs, nothing else can connect to it.
- Polling only. Changes made in ViCare or on the valve show up at the next poll.
FAQ
Does the ViCare app keep working? Yes. Your valves stay on the ViCare network and the app works as before. The app goes through the Viessmann cloud, not Bluetooth, so it does not compete for the R0. You do not need to close it.
Why did my target temperature change back? Your ViCare schedule or gateway set it again. A change from Home Assistant is a manual override that ViCare ends after a while or at the next schedule change. If the valve never confirmed the change, the integration also puts the old value back.
Can I use it without the network key? No. The R0 requires it. See The network key.
Does it work with other Zigbee routers? It needs a router with Zigbee Direct over Bluetooth: the ubisys R0 Series 2. The R0 Series 1 has no Bluetooth, and routers without Zigbee Direct cannot work.
Does it need the internet? Only to install or update it (HACS and the Python package). After that everything stays local.
Troubleshooting
| You see | Likely cause | Fix |
|---|---|---|
| "No ubisys R0 found over Bluetooth." | The R0 is out of range, or no adapter or proxy can connect to it | Move the adapter or proxy closer. Check bluetooth_proxy: active: true |
| "This device is not a ubisys R0 with Zigbee Direct." | A different Bluetooth device | Pick the R0 |
| "Could not connect to the R0. …" | Another client holds the R0, it is out of range, or the proxy has no free slot | Disconnect the other client, move closer, free a proxy slot, try again |
| "The R0 rejected the network key." | Wrong key | Check the key. Only the ViCare network key works |
| "The key must be exactly 32 hexadecimal characters." | Typo, spaces inside the key, or a different format | Enter 32 characters 0-9 and a-f |
| Setup keeps retrying with "The ubisys R0 is not reachable over Bluetooth." | After a reload the R0 needs about 15 seconds before it accepts a connection again | Wait; Home Assistant retries by itself |
| Repair issue "Bluetooth MTU too small" | Your adapter or proxy sends too small packets. Some replies may be lost | Use another adapter or an ESPHome proxy |
| Repair issue "Bluetooth long writes unsupported" | Your adapter or proxy cannot send large frames | Use another adapter or an ESPHome proxy |
| Connection state "Authentication failed" | The R0 rejected the stored key | Enter the key again when Home Assistant asks you to Re-enter the network key |
| A valve is unavailable | Weak Zigbee link, or the valve's parent did not get the request to it in time | Wait; it usually comes back within minutes. Check Battery and Link quality |
| Setting a temperature is slow or fails | The valve is asleep or has a weak link | Wait up to a minute. Try again later |
Debug logging
Add this to configuration.yaml and restart:
logger:
logs:
localtrv: debug
Or use Enable debug logging on the integration page. The line connected, mtu=… shows the
negotiated MTU. Debug logs never contain the network key, but they can contain Bluetooth and
Zigbee network addresses.
Diagnostics
Settings > Devices & services > LocalTRV, menu, Download diagnostics.
- Never included: the network key or anything derived from it, the R0's Bluetooth address.
- Pseudonymised: device IDs (EUI-64) and the extended network ID.
- Included: options, connection counters, the MTU, network metadata (PAN ID, channel, short addresses), each valve's model, firmware, limits and state (temperatures, target, battery, heating demand), and a short, redacted log of recent frames.
Reporting a bug
Open an issue and attach:
- Home Assistant and integration versions
- your Bluetooth adapter or proxy, and the
mtu=log line - a debug log covering the problem
- the diagnostics file
Read your log before you post it. Never include the network key.
Removing the integration
- Settings > Devices & services > LocalTRV, menu, Delete.
- To remove the files too, uninstall it in HACS (or delete
custom_components/localtrv/) and restart.
Deleting stops the Bluetooth connection, so the R0 is free again. It also deletes the stored valve list and any repair issues of this integration. Older Home Assistant backups still contain the network key.
Contributing
See CONTRIBUTING.md.
Security
Please report vulnerabilities privately. See SECURITY.md.
Python library
The integration is built on the Python package
localtrv. It handles the Bluetooth
connection, the Zigbee Direct session and the valve mapping, and includes a small CLI
(localtrv scan, localtrv status). The API is not stable yet. See
CONTRIBUTING.md for the
code layout.
Protocol documentation
How the R0, the Zigbee Direct session and the valves work: docs/protocol.md.
License
MIT. See LICENSE.
Metadata
Release files for localtrv 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| localtrv-0.2.0.tar.gz | 736.2 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| localtrv-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 983.6 kB
Release files / localtrv-0.2.0.tar.gz
| Download URL | localtrv-0.2.0.tar.gz |
|---|---|
| Size | 736.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
7f4b8af2e79384a9057940b9cc0667eec7b6e9bc2fb5f65ea37f208f25767c4c
|
|
BLAKE2b-256 checksum How to use checksums |
4c3c1b86947d20655a3a80ef4d93e9da6cb01c914a44a135d8791df1b63e1613
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 17, 2026.
Transparency logRelease files / localtrv-0.2.0-py3-none-any.whl
| Download URL | localtrv-0.2.0-py3-none-any.whl |
|---|---|
| Size | 247.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
41de0eaad727a693a6987f70e6471f62f9a4026699a0d6cf7f7122d662d69e74
|
|
BLAKE2b-256 checksum How to use checksums |
a88d762653a4bc46df445ebdf56ad9d35e52cec2daa28c3215ddd3b617c2c5a8
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 17, 2026.
Transparency log