Home Automation: Integrating Salus RT500 RF in OpenHAB using a Jeelink

We recently had a new boiler fitted with the Salus RT500RF controller. I’ve been playing around with a lot of home automation recently involving LightwaveRF, RFXCom unit and OpenHAB. Having temperature monitoring in my lounge and bedroom I really wanted the option to be able to control the boiler allowing the OpenHAB control system to decide when to enable the boiler based on factors such as room temperature, time of day and outside temperature.

Opening the RT500 RF transmitter is pretty easy (just a couple of screws inside) and I found a nicely labelled RFM02B module inside.

Initial attempts

I then used a JeeLink USB stick and nRFmon to find out what frequency we were operating on. I was fortunate that the transmission was long enough to be picked up on the frequency scan. It seems that nRFMon is pretty slow over a wide band. I initially thought it looked like OOK. I had hoped that I would be able to use the RFM12B in the JeeLink for reverse engineering the protocol, but this isn’t straight forward. So I got out my Open Logic Bench and decided to attack from the other side and get the baseband signal going into the RFM02.

Using the RFM02 datasheet I probed the FSK pin and discovered it’s running at approximately 2.4kHz. Knowing this I could set the logic analyser to nice long capture window by dropping the sampling rate. Using a sample rate of 100kHz I got the following trace:

Boiler On Waveform

This then allowed me to enter the bit timings into a spreadsheet so I could compare different messages.

At this stage I still thought I has OOK on my hands and proceeded to start trying to send the boiler control messages using the OOK method from the Jeelink. This was far from successful.

Changing Vector

I decided I needed a bit more information about the RF protocol and decided to invest in a USB SDR (software defined radio) for about £13 from eBay. This would allow me to see the RF signal properly, decode it and then compare to what I was outputting on the Jeelink so I could debug it.

This was much more successful as I was able to receive the signal and record the RF signals as an IQ wave file for post-processing using SDR#.

SDR Sharp Boiler Receive

Using gnu-radio and the information from a blog post about using gnuradio to decode rfm12 signals I was able to create a decoder for the boiler transmitter. The baseband signal I received and decoded from the transmitter matched nicely with what I had probed from using my logic analyser.

GnuRadio decode setup

GnuRadio boiler On decode

I was also able to ascertain that the deviation from the carrier frequency was ~+130kHz and that the carrier frequency was ~868.2 Mhz.

Decoding messages

Now I had the raw line coded message I was needed to work out the format. Reading the RFM module datasheet I eventually realised that the message was using a standard format of a pre-amble followed by the sync word, the data and then what appears to be a stop byte. With some more data points (recording the signal sent with different address jumper settings) it should become apparent what the format of the data bits is.

Salus RT500RF On/Off Bitstream Table

However, I put that to one side at this stage as I had what I needed to turn the boiler on and off – which is the primary aim!

Jeelib library

I then created my own version of the jeelib library to allow the control of the salus RT500RF boiler controller via the Jeelink USB stick. The library and example code can be found on github as part of my JeelinkHa (Jeelink Home Automation) project.

The main modifications are the stripping out of the RX stuff and CRC so that I can send my own custom messages. I have also created a nice wrapper for the sending of boiler commands which makes the arduino sketch somewhat simpler.

The plan is to re-implement the RX side of things so that I can receive none RFM packets (such as from the eq-3 MAX! range of radiator valves) and to also extend it so I can transmit other 868Mhz messages (such as to the eq-3 MAX! radiator valves).

OpenHAB integration

OpenHAB is brilliant – it’s amazingly flexible and very powerful for home automation! To integrate the JeelinkHA control into OpenHAB I created a custom binding based on the RFXCom binding that was already present. This JeelinkHA binding can be found on github.

This entry was posted in Home Automation and tagged , , , , , , , , , , , , . Bookmark the permalink.

18 Responses to Home Automation: Integrating Salus RT500 RF in OpenHAB using a Jeelink

  1. Hi Paul, you mention extending the code to control eq-3 MAX! TRV’s.  Have you got any further with this?


  2. Forgot to enable follow-up comments, doing that with this comment!

  3. Onosher says:

    Great work. Thanks for publishing this. I got this working on my Arduino with very little effort. I had to use the RFID address that you use (11100). Did you find the 4 byte data codes for any other addresses? I would like to try to work out the protocol that Salus use. I suspect that the first byte is the Gateway_ID, followed by a command byte, single byte CRC and a terminator byte. However, I have not had any luck with this, so far. Not sure how the jumper settings equate to the Gateway_ID either.




  4. Mark says:

    Just had a Salus remote thermostat installed and keen to start playing with home automation. Some great starter points here

  5. Pingback: Upgrades... Salus RT500RF 868MHz wireless boiler control - QDH

  6. Dan says:


    I am new to arduino and am struggling to get your library to work, I get the following error.

    Arduino: 1.6.4 (Mac OS X), Board: “Arduino Uno”

    at processing.app.BaseNoGui.removeDescendants(BaseNoGui.java:944)
    at processing.app.BaseNoGui.removeDir(BaseNoGui.java:966)
    at processing.app.Base.removeDir(Base.java:2559)
    at processing.app.Sketch.saveAs(Sketch.java:706)
    at processing.app.Editor.handleSaveAs(Editor.java:2398)
    at processing.app.Editor.handleSave(Editor.java:2339)
    at processing.app.Editor.handleRun(Editor.java:1996)
    at processing.app.EditorToolbar.mousePressed(EditorToolbar.java:324)
    at java.awt.Component.processMouseEvent(Component.java:6522)
    at javax.swing.JComponent.processMouseEvent(JComponent.java:3324)
    at java.awt.Component.processEvent(Component.java:6290)
    at java.awt.Container.processEvent(Container.java:2234)
    at java.awt.Component.dispatchEventImpl(Component.java:4881)
    at java.awt.Container.dispatchEventImpl(Container.java:2292)
    at java.awt.Component.dispatchEvent(Component.java:4703)
    at java.awt.LightweightDispatcher.retargetMouseEvent(Container.java:4898)
    at java.awt.LightweightDispatcher.processMouseEvent(Container.java:4530)
    at java.awt.LightweightDispatcher.dispatchEvent(Container.java:4462)
    at java.awt.Container.dispatchEventImpl(Container.java:2278)
    at java.awt.Window.dispatchEventImpl(Window.java:2750)
    at java.awt.Component.dispatchEvent(Component.java:4703)
    at java.awt.EventQueue.dispatchEventImpl(EventQueue.java:751)
    at java.awt.EventQueue.access$500(EventQueue.java:97)
    at java.awt.EventQueue$3.run(EventQueue.java:702)
    at java.awt.EventQueue$3.run(EventQueue.java:696)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.security.ProtectionDomain$1.doIntersectionPrivilege(ProtectionDomain.java:75)
    at java.security.ProtectionDomain$1.doIntersectionPrivilege(ProtectionDomain.java:86)
    at java.awt.EventQueue$4.run(EventQueue.java:724)
    at java.awt.EventQueue$4.run(EventQueue.java:722)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.security.ProtectionDomain$1.doIntersectionPrivilege(ProtectionDomain.java:75)
    at java.awt.EventQueue.dispatchEvent(EventQueue.java:721)
    at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:201)
    at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:116)
    at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:105)
    at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:101)
    at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:93)
    at java.awt.EventDispatchThread.run(EventDispatchThread.java:82)

    /Users/Dan/Documents/Arduino/libraries/JeelinkHaLib/JeelinkHaRF12.cpp:319:19: fatal error: RF12.h: No such file or directory
    compilation terminated.
    Error compiling.

    Could you give me any pointers on what I might be missing.


  7. JohnO says:

    I just noticed that there is a One-Touch-Override (OTO) unit which can set back the temperature by a set number of degrees, when you go out for instance. The OTO unit communicates with the thermostat rather than the boiler controller which will be a challenge for the batteries. There must be a jumper that tells the thermostat to leave its radio in receive to see the OTO commands as and when they are sent.

    There appears to be a lot going on radio protocol wise and I hope someone might have got to the bottom of the OTO radio protocol.

  8. AlanS says:

    Total novice to this: Do we have any novice instruciotns to get this working.
    So far I’ve programmed the JeeLink and compiled the OpenHAB binding to 1.7.1

    I know I’m missing something as I can’t get around in my head how the two communicate the JeeLink and the Boiler.
    I get the following:
    10:25:00.284 [INFO ] [c.internal.ModelRepositoryImpl:80 ] – Loading model ‘default.sitemap’
    10:25:00.518 [INFO ] [c.internal.ModelRepositoryImpl:80 ] – Loading model ‘rrd4j.persist’
    10:25:00.549 [INFO ] [c.internal.ModelRepositoryImpl:80 ] – Loading model ‘exec.persist’
    10:25:00.559 [INFO ] [c.internal.ModelRepositoryImpl:80 ] – Loading model ‘db4o.persist’
    10:25:00.568 [INFO ] [c.internal.ModelRepositoryImpl:80 ] – Loading model ‘logging.persist’
    10:25:00.580 [INFO ] [c.internal.ModelRepositoryImpl:80 ] – Loading model ‘mysql.persist’
    10:25:00.620 [INFO ] [c.internal.ModelRepositoryImpl:80 ] – Loading model ‘default.items’
    10:25:00.670 [DEBUG] [i.internal.GenericItemProvider:154 ] – Processing binding configs for items from model ‘default.items’
    10:25:00.675 [DEBUG] [i.internal.GenericItemProvider:133 ] – Read items from model ‘default.items’
    10:25:01.153 [INFO ] [.o.u.w.i.servlet.WebAppServlet:79 ] – Started Classic UI at /openhab.app
    10:25:02.285 [DEBUG] [.r.internal.RuleModelActivator:42 ] – Registered ‘rules’ configuration parser
    10:25:02.302 [DEBUG] [m.r.internal.engine.RuleEngine:77 ] – Started rule engine
    10:25:04.627 [DEBUG] [.j.internal.JeelinkHaActivator:34 ] – JeelinkHa binding has been started.
    10:25:04.644 [DEBUG] [j.internal.JeelinkHaConnection:48 ] – Activate
    10:25:04.645 [DEBUG] [j.internal.JeelinkHaConnection:74 ] – Configuration updated, config true
    10:25:04.646 [INFO ] [j.internal.JeelinkHaConnection:107 ] – Connecting to JeelinkHa [serialPort=’/dev/ttyUSB0′ ].
    10:25:04.659 [DEBUG] [.b.j.internal.JeelinkHaBinding:58 ] – Activate
    10:25:04.751 [DEBUG] [j.i.c.JeelinkHaSerialConnector:152 ] – Data listener started

    and thats it.
    Has anyone documented a guide.
    Would appricate it.

  9. AlanS says:

    Do we have a basic guide for setting up the bindings?
    I have compiled to openhab 1.7.1 , when I run I get to Data Listener started. I feel I’m missing something as I don’t understand how the device would communicate with my Salus receiver on the boiler.

  10. Dan says:

    I’ve been playing with this too – but doing hardware mod on the thermostat programming unit to IOT enable it. I suspect this unit is a lot more basic than one might think.

    On the RFM board there are 4 pins – VCC, vOn, vOff, GND. Briefly connecting VCC to vOn or vOff transmits signal which turns the boiler on of off. (>=250ms connection is stable, 30ms doesn’t work; I guess this briefly powers up the board which transmits as soon as powered). It looks like the thermostat decides when the boiler should be on or off – the only link between this unit and the receiver is the transmission by the RT500RF of an on or off command as needed (The icon on the unit shows the boiler on even if the receiving unit is powered off – I doubt it would do this if there were authentication protocols etc.). The RT500RF doesn’t seem to receive, only transmit. These are the only 4 connections between the RFM02B board and the MCU.

    The jumper for the address on the unit connects to the RFM02B board, not the main board like the other jumpers. I’m guessing this jumper adjusts the radio signal somehow rather than the bytes which are transmitted. It seems the receiving unit has to be on the same radio settings to receive a signal. i.e. no authentication protocols etc. (This might be checked by comparing the signals when the jumpers are changed).

    As I understand it, the OTA unit (RT300) is paired with and sends to the receiver (boiler controller), not the programmable thermostat itself. The RT300 provides a simple override – i.e. sending an on or off command to the boiler control unit if the temperature is above or below what is set on the OTA unit. I guess this works the same as the temporary override function on the RT500RF thermostat unit. What is the reason for thinking the RT300 transmits to the RT500RF thermostat? (there are certainly no such jumpers inside the RT500RF and, according to Salus tech, no change of settings on existing setup is needed to add an RT300 unit. The only configuration is that jumpers on the new RT300 must be set the same way as on the existing setup).

    I’m guessing from price, size and functionality that the RT300 OTA unit uses the same RF board, but no MCU as in the RT500RF, just a basic thermostat and switches to set the override.

    Note that there are some more recent units which look like they have authentication and pairing (as one would expect). The compatibility between Salus units can be confusing (at least I’ve found that – as tech support explained, these newer units ending with ‘5’ such as RT305 are not compatible with the RT500RF, unlike the RT300. My old RT500RF is a lot more basic than the Salus ‘5’ series, but probably easier to mod.

    BTW, as POC, I’ve soldered 4 wires to these pins in the RT500RF and hooked up to a logic level shifter and an Arduino. The Arduino can then be used to turn the boiler on and off wirelessly. I plan to put in an Arduino Pro Mini (modded to be ULP) and NRF24L01+ inside the RT500RF – there is even room inside for these to have separate batteries. (I decided an ESP8266 is too power hungry). I may also try to control the unit’s Reset, Set, Select and Up and Down Switches with the Arduino (copper tape may be needed). Then I’d be able to reprogram the unit remotely.

  11. JohnO says:

    I have decoded most of the Salus protocol and as you say it is quite primitive. The RT500ROF is superior in matters other than the OTO controller. I actually upgraded from the RF to the ROF to get the additional features. The ROF does transmit to the boiler controller not the thermostat. The setback and thence boiler on/off is determined by the controller using the thermostat transmissions together with the OTO transmissions.

  12. Pingback: RFM12B Linux Kernel Module development - QDH

  13. Andrew Stancliffe says:

    Great work, I wonder if you could help me with something on the RX side? My goal is to operate a second relay with a Arduino and RFM02 to power a circulating fan when the heat is on.

  14. John O'Hare says:

    @Andrew Stancliffe

    Take a look over at these sites:

  15. Andrew says:

    Thanks for the reply.
    Is there anyone out there who can enlighten me with details of nodes preamble protocol etc. I am looking at cloning the RX side

Leave a Reply

Your email address will not be published. Required fields are marked *