I have an ongoing project to improve the performance and automation of the heating system in our house, given the ever-rising cost of energy. As part of this project I am planning to zone the central heating system to allow the temperature of each room to be controlled independently, and to do this I need a way of remotely controlling the radiators. Because of the level of integration I am looking for I’ve been unable to find an off-the-shelf solution that meets my needs at a price that is acceptable. Time to start hacking!
The Conrad FHT8V wireless thermostatic radiator valves (TRVs) are available for around £30, which is the sort of price I’m prepared to pay to get this project up and running. I would ideally like to talk directly to these valves from my own controller, so I bought one of the £60 starter kits which includes the remote thermostat and took a look at the on-air protocol using Gnuradio.
To receive the signal a cheap RTL SDR dongle was used. This has the Elonics E4000 tuner, which has almost continuous coverage from about 65 MHz to nearly 2 GHz. The TRVs operate in an ISM band on 868.35 MHz. Helpfully, the protocol has already been reverse engineered, making building a receiver a simple task. The modulation is on-off keying with the symbols encoded as mark and space times of two different lengths, so a custom block was needed in order to decode and frame the messages. This was written in C++ and integrated with the rest of the receiver in Python.
The code can be found here, and should serve as a simple introduction into writing custom Gnuradio blocks.
The flow graph is coded in Python without using Gnuradio Companion because I felt it was easier to get the separate decoding thread working in this way, and is simply:
Source -> 50 kHz channel filter -> Demod (Magnitude squared) -> Decimate -> Custom Decoder
Modtool was then used to generate the boilerplate code for the custom block:
$ gr_modtool.py create fs20 $ cd gr-fs20 $ gr_modtool.py add framer_sink -t sink Operating in directory . GNU Radio module name identified: fs20 Code is of type: sink Block/code identifier: framer_sink Full block/code identifier is: fs20_framer_sink Enter valid argument list, including default arguments: Add Python QA code? [Y/n] n Add C++ QA code? [Y/n] n
The decoder block performs symbol decoding, rejecting pulses of unacceptable length, passing the resulting binary values to a state machine for packet synchronisation and parity checking. Once enough bits have been received the packet is pushed onto a queue for asynchronous processing from Python.
To build, ensuring Gnuradio is in your path and that you have CMake:
$ cd gr-fs20 $ mkdir build && cd build $ cmake .. $ make # make install
The decoder can then be run in-situ with:
The asynchronous part of the decoder runs in a separate thread within the Python script. Packets are popped off the message queue as they arrive and are checksummed using the FHT algorithm, which is subtly different to the FS20 one. If the packet is valid then the length of the message is inspected to determine whether it came from the thermostat or a window sensor. Messages are then pretty-printed on stdout as they are received (which is about every 2 minutes).