This is a summary of the protocol used by the Conrad/ELV FHT8V thermostatic radiator valves. Some of it is based on my own reverse engineering, and some from a translation of this German page.
These valves are typically sold as part of a kit containing the valve itself (FHT8V-3), a window sensor (FHT80TF-2) and a thermostat/control unit (FHT80B-3).
The protocol uses proprietary on-off keying at 868.35 MHz and is somewhat similar to the FS20 home automation protocol. Bits are represented as:
- 0 – 400 us of carrier, 400 us of silence
- 1 – 600 us of carrier, 600 us of silence
Every packet is introduced with a preamble of 12 ‘0’ bits followed by a ‘1’. Data bytes follow on immediately, sent most significant bit first, and followed by a 9th even parity bit. The transmission ends with a single additional ‘0’ bit.
The packet format is:
|0||HC1||House Code 1, range 0 to 99 decimal to match C1 value on valve|
|1||HC2||House Code 2, range 0 to 99 decimal to match C2 value on valve|
|2||ADDRESS||Sub-address from 1 to 8, or 0 to command all valves with the specified house code|
|3||COMMAND||Command byte, see below|
|(4)||EXTENSION||OPTIONAL and command specific|
|n||CHECKSUM||A simple sum, modulo 256, of all preceding bytes plus 0x0c|
The upper nibble of the command byte contains flags defined as follows:
|7||Indicates a repeat of the previous transmission (the valves expect to see a transmission in every 2 minute frame or they assume loss of signal)|
|6||Something to do with the optional base station – not sure|
|5||Extension byte present – always set for valves even if the command doesn’t use it|
|4||If set, sound a low battery warning if batteries are depleted|
The lower nibble of the command byte selects the operation. Some operations take a parameter which is passed in the extension.
|0||End of sync sequence||Valve position 0 (closed) to 255 (open)|
|1||Open valve fully|
|2||Close valve fully|
|6||Valve opened to specified amount||Valve position 0 (closed) to 255 (open)|
|8||Offset adjust||Bit 7 sign, 6:0 absolute value from 0 to 50 (NOT 2’s complement AFAIK)|
|10||Start de-scaling cycle then return valve to specified position||Valve position 0 (closed) to 255 (open)|
|12||Sync countdown, sent once per second during sync||Down-counter (see below)|
|14||Causes the valve to respond with a beep|
To reduce power consumption the valves only turn on the receiver for about a second every (approximately) two minutes. A synchronisation process must be completed between the controller and the valve so that the controller can transmit at the appropriate time. It is believed that any given valve will turn off its receiver as soon as it receives a valid command with matching house code, so only one command can be sent per-frame.
To begin synchronisation the controller sends packets at one second intervals with command 12 (sync) and a down-counter in the extension byte. The counter starts at 241 and decreases by 2 each time. The last value to be transmitted is always 3. The actual transmit timeslot occurs a few seconds later, and, along with the period between transmissions, varies according to the least significant three bits of HC2. The first command sent after the sync must be a command 0 packet, which completes the sync process.
The time between the last command 12 and the command 0 can be found by:
t = 4 + 0.5 * (HC2 & 7) seconds
The time between subsequent transmissions can be found by:
t = 115 + 0.5 * (HC2 & 7) seconds
The process can be summarised as follows:
SYNC(241), PAUSE 1 second, SYNC(239), PAUSE 1 second, SYNC(237),... , SYNC(3) PAUSE 4 seconds, PAUSE 0.5 * (HC2 & 7) seconds SYNC_END(0) PAUSE 115 seconds, PAUSE 0.5 * (HC2 & 7) seconds Send command PAUSE 115 seconds, PAUSE 0.5 * (HC2 & 7) seconds ...
A command must be sent in every slot even if there is no change. In this case the last command is repeated but with bit 7 of the command byte set.
To synchronise with a valve having house code 10 04 then set to half way:
0A 04 00 2C F1 37 [SYNC(241)] - pause 1 second 0A 04 00 2C EF 35 [SYNC(239)] - pause 1 second 0A 04 00 2C ED 33 [SYNC(237)] - pause 1 second 0A 04 00 2C EB 31 [SYNC(235)] - pause 1 second ... 0A 04 00 2C 05 4B [SYNC(5)] - pause 1 second 0A 04 00 2C 03 49 [SYNC(3)] - pause 6 seconds 0A 04 00 20 00 3A [SYNC_END(0)] - pause 117 seconds 0A 04 00 26 80 C0 [SET(128)] - pause 117 seconds 0A 04 00 A6 80 40 [REPEAT SET(128)] - pause 117 seconds ...