Custom firmware for the eQ-3 CC-RT-BLE - BTHome broadcasts, ~50% more battery

The eQ-3 CC-RT-BLE works with Home Assistant, but it requires an active connection and doesn’t even expose the measured room temperature.

I rewrote its firmware so it broadcasts its state instead. Home Assistant picks it up as BTHome - no pairing, no connection, no custom integration. An

I made a webapp to install it and manage all settings. Nothing to download, no account, no server:

:play_button: GitHub - dbuezas/eq3-custom-fw · GitHub

Chrome or Edge, on Android/Windows/macOS/Linux. (No Web Bluetooth in Safari or Firefox, so iPhones can’t install it - they can still use the thermostat normally afterwards.)

It is reversible. To any eQ-3 version, 1.05 through 1.48, is in the same page.


For Home Assistant

  • It broadcasts room temperature, target, battery voltage, valve % and the window/lock/boost flags - picked up natively as BTHome, with no connection held open.
  • Values arrive instantly.
  • Broadcasts can be encrypted with a key you choose.
  • Button presses and wheel turns arrive as events when child lock is on. A locked thermostat becomes a remote control for anything in your house.
  • Tell it a window is open from a door sensor, instead of leaving it to guess from a temperature drop.
  • Write text to its display from HA - longer than the screen, and it scrolls.
  • Set brightness, boost duration, boost valve opening and the Bluetooth options.
  • Give each thermostat its own name, so two are tellable apart.

Battery

  • Roughly 50% longer. After a battery change the stock radio settled into a high-power idle and stayed there for the life of those batteries. Measured on the bench at 3.3 V: sleeping current 114 µA → 36 µA, ten-minute average 204 µA → 137 µA.
  • A thermostat waiting for clock setup after boot now sleeps while it waits instead busy waiting at high current drain.
  • Leaving a menu open no longer costs ten times the current either.

On the thermostat itself

  • Every menu item scrolls its full name instead of a three-letter code. AEr was open-window detection. dSt was daylight saving. No more needing the manual to configure things.
  • Choose what each button does - short and long press, per button. Including “send this press to Home Assistant”.
  • Choose what the idle screen shows: target, measured temperature, clock, valve %, or a valve bar - one, or rotating.
  • Boost duration and valve opening are settable, and the countdown is mm:ss so you can read it.
  • Open-window detection is finally adjustable - auto-detect on/off, the temperature, the hold time.
  • The weekly valve stroke is explained and adjustable. Every Saturday at noon the valve is driven full travel so limescale can’t seize it - and it answers nothing for that minute. Now: weekly, 4-weekly, 8-weekly or off, and it can skip on a flat battery.
  • Adjustable contrast.
  • Adjustable advertisment Rate. Faster => connects faster but drains more battery, Slower => the opposite. (±30% battery life)

Security

  • Pairing is off until you switch it on - no PIN dance to talk to your own thermostat.
  • Set a fixed PIN if you want one, and it survives a battery change (it didn’t before).
  • The PIN also covers firmware updates. A lock with an “install anything” door next to it isn’t a lock.
  • A second, pair-free encrypted channel for a client that holds the key. (I’ll make a HA custom integration that supports that later)
  • unPAIr ALL to throw off every phone that ever paired.

Also in the page

Beyond installing: set the schedule, change every setting, and a live mirror of the thermostat’s screen with working buttons - the real segments, updated as they change.

Warning

Unofficial firmware. Not from eQ-3, not supported by them, may void your warranty. Your risk. But it is reversible.

Flasher code, protocol docs and the full changelog: GitHub - dbuezas/eq3-custom-fw · GitHub

Firmware not open source yet unless someone guarantees I won’t get in trouble with eq3 :slight_smile:

Thank you very much!!!

I flashed 3 TRVs, the last one needed trying 3 times. It started moving the valve after first part reached 100%

I have done the pairing thing several times, now i got it deactivated.

After i found i had to remove encrytion, i now see bthome values.

l wonder how i can set values rather than read it. Shortly before your post HA had a crash and is not yet running again so i cant try.

Yes the first loading bar is the stm8 that controls the valve and lcd, the second one is the Bluetooth chip. So after the stm8 is flashed it restarts and starts retracting the valve plunger, that is normal.

To set values you need to connect to the thermostat, and if your home assistant has the eq3 configured it will fight with the webapp for the connection. If you want to use the app you need to disable the thermostat in home assistant.

This will work better when I make the custom integration (which will stop connecting all the time just to fetch data)

I dont permanently need the webapp, i want to use HA. The old integration still works?

Yes, it is compatible with the old integration. The app is mostly to configure things (fixed pin, pair or no pair, bthome encryption, descaling frequency, …), and to flash it.

By the way, make sure to disable the thermostat from HA before trying the app, otherwise they fight eachother

I had previously tried flashing with the old method which repeatedly failed. So when I put the batteries back in it showed “Upd”.

I was able to connect with your new app and it installed the first part of the firmware correctly. But the second part never flashed. The app just exited the update screen.

Maybe I am blind, but how can I complete the flash process now?

Log:

18:39:10
this browser handed the page no saved thermostat — nothing to watch
18:39:10
this browser handed the page no saved thermostat — nothing to watch
18:37:00
this browser handed the page no saved thermostat — nothing to watch
18:36:59
this browser handed the page no saved thermostat — nothing to watch
18:36:27
firmware: the thermostat came back as nothing rather than 200, so the radio has NOT been touched. It is safe here — try again.
18:36:16
this browser handed the page no saved thermostat — nothing to watch
18:36:16
User cancelled the requestDevice() chooser.
18:36:16
this browser handed the page no saved thermostat — nothing to watch
18:36:16
this browser handed the page no saved thermostat — nothing to watch
18:35:59
this browser handed the page no saved thermostat — nothing to watch
18:35:59
User cancelled the requestDevice() chooser.
18:35:59
this browser handed the page no saved thermostat — nothing to watch
18:35:01
thermostat firmware v2.00
18:34:52
firmware: thermostat 34816 B in 55 s (637 B/s)
18:33:53
radio firmware v4.4
18:33:42
asked this thermostat its name 5 times (cmd 0x5B) and no answer arrived
18:32:43
the thermostat did not answer when asked which firmware it runs
18:32:43
It connected, but never said which thermostat it is, so there is nothing to save. That is what a thermostat does when its firmware install was interrupted — it can still be reinstalled, and it is also what ours does when its replies are not getting through.
18:32:38
radio firmware v4.4
18:32:38
this browser handed the page no saved thermostat — nothing to watch
18:32:38
connected to CC-RT-BLE
18:32:34
this browser handed the page no saved thermostat — nothing to watch
18:32:34
connecting…
18:32:15
this browser handed the page no saved thermostat — nothing to watch
18:32:14
this browser handed the page no saved thermostat — nothing to watch
18:32:12
this browser handed the page no saved thermostat — nothing to watch

Thank you very much for this firmware, its a blast!
Especially the BTHome advertisements.

Some notes for others converting their devices:

  • The pairing pin is shown when pressing the middle button for 5 secs. (I searched for this way to long)
  • Pairing was only possible for me whenever the pairing pin was NOT shown on the display, so note it separately and pair after.

Just things I noticed:

  1. the battery voltage is rounded a lot per Default in HA. Is this something that can be solved in BThome? I guess not, but you can change it in HA easily.
  2. Some namings are strange because they are named by the BThome fields that are reused for other things. But this can also be easily fixed in HA.

That shouldn’t have happened. I suggest you enable the suggested flags in chrome for best function of the app (the yellow boxes at the top). And then restart the browser app.

But just flashing should work, I’d suggest you just retry.

Yes, for some reason HA shows 1 digit by default, nothing i can do against it. But as you said, easy to change. We have millivolt resolution on the battery!

Regarding the namings, count is mode (auto/manual/vacation) and generic is boost. There’s no standard codes for these. When i make the custom integration these will be expressed correctly and bthome data will be merged with that of the climate entity.

By the way, try child locking the buttons (press left and right buttons together) and then in lock mode do press buttons and scroll the wheel. You will see those as events! Dimming lamps with the wheel works so well!

Maybe the app could also skip the parts of the firmware that are installed already? That way the annoyinf re-initialization of the TRV is skipped.
Ada always takes forever and randomly fails with F3. Really annoying to have to rerun that :see_no_evil_monkey:

EDIT:
I reflashed and this time both were flashed.
However, the new radio seems to be less stable or maybe is using a different broadcast frequency or something?

I suddenly keep getting

That thermostat was not added
the thermostat accepted the connection and then dropped it before its services could be read. That is the link failing rather than the thermostat refusing: move closer, and try again. (A thermostat that wants its PIN gets further than this and then answers nothing, which the Status tab offers a way through.) (GATT Server is disconnected. Cannot retrieve services. (Re)connect first with `device.gatt.connect`.)

Try again
Dismiss

Before the flashing of the radio 5.0 (was 4.4) it always connected straight away. I also enabled the experimental feature. But more than 5 attempts and not one successful connect.

21:51:50
the thermostat accepted the connection and then dropped it before its services could be read. That is the link failing rather than the thermostat refusing: move closer, and try again. (A thermostat that wants its PIN gets further than this and then answers nothing, which the Status tab offers a way through.) (GATT Server is disconnected. Cannot retrieve services. (Re)connect first with `device.gatt.connect`.)
21:51:50
disconnected
21:51:50
disconnected
21:51:50
connecting…
21:51:02
the thermostat accepted the connection and then dropped it before its services could be read. That is the link failing rather than the thermostat refusing: move closer, and try again. (A thermostat that wants its PIN gets further than this and then answers nothing, which the Status tab offers a way through.) (GATT Server is disconnected. Cannot retrieve services. (Re)connect first with `device.gatt.connect`.)
21:51:02
disconnected
21:51:02
disconnected
21:51:02
connecting…
21:48:38
the thermostat accepted the connection and then dropped it before its services could be read. That is the link failing rather than the thermostat refusing: move closer, and try again. (A thermostat that wants its PIN gets further than this and then answers nothing, which the Status tab offers a way through.) (GATT Server is disconnected. Cannot retrieve services. (Re)connect first with `device.gatt.connect`.)
21:48:38
disconnected
21:48:38
disconnected
21:48:38
connecting…
21:48:28
the thermostat accepted the connection and then dropped it before its services could be read. That is the link failing rather than the thermostat refusing: move closer, and try again. (A thermostat that wants its PIN gets further than this and then answers nothing, which the Status tab offers a way through.) (GATT Server is disconnected. Cannot retrieve services. (Re)connect first with `device.gatt.connect`.)

From the LCD, go to the menu, bluetooth, enable pin. When this or the advertised name is changed, some Bluetooth adapters get confused and you need to forget the Bluetooth device. From bluetoothctl i think it is just “forget [mac address]”

By the way, anyone noticed the display tab of the app yet?

Also, Bluetooth in general is not the most stable thing ever, so sometimes it beeds turning it off and on and closing chrome fully and restarting it. Particularly after changing Bluetooth settings or installing this firmware, which moves a lot of things around. But once setup it is solid

Disabled PIN via trv menu, which was a bit tricky because I tested on a TRV with flacky wheel. Difficult to operate manually.

Any chance PIN could be disabled by default? That way connection will always work and PIN can be set via app.

Also, would it be possible to change the advertised BLE name? Just use the name set in the app for the advertisment also instead of CC-RT-BLE?

edit:
Or maybe ask for a new PIN on flashing and then write it to the device right away?

question:
encryption key is for BTHome reading only, right? Or can someone control the device via BTHome also? I don’t care about someone seeing my values. I do care about someone changing my trv.

Both can be done from the webapp (change pin to a fixed one or disable it, and changing the advertised name)

And encryption is for BTHome and for a new pair-less active connection channel. The app can use it but home assistant not yet. With it you can leave the pairing pin enabled and still connect without pairing if you know the encryption key.

The only thing the new encrypted channel can do is ota upgrades

And btw feel free to star the repo :slight_smile:

I think the problem was that there was a key that my comouter knew from a previous connection but after flashing the new firmware, the computer did not put 2 and 2 together and required me to repair with the PIN again. But it did not query me for the PIN.

I solved it using my phone but my thought was:
If during flashing the PIN were disabled then the reconnetion would always work. Once the flashing is other and the device reconnects, the user could then be actively queried to set a pin via app.

That way connections would continue to work and safety would only be compromised for the period of flashing.

P.S.: added the repo to my favs right away, but that is of course not the same as starring it.

Oh, I see. Yes forgetting bluetooth devices is the first thing to try when something doesn’t work. Bluetooth is very finicky.

One of my TRVs in BTHome shows only 6 entities rather then 10, no temperatures, no valve, no voltage.