I was very excited to get our new heating system which uses the OpenTherm protocol to communicate between the boiler and the thermostat. I found the electronic schematics, including PCB design and PIC firmware to make an OpenTherm gateway. This would allow me to plot the different temperatures and modes and learn from the statistics to fine-tune the heating! Only to find out that my, newer, version of the boiler doesn’t use OpenTherm anymore, but uses another type of bus… Back to square one. Here is my journey to reverse-engineer the protocol.

The facts

I have an Elco Thision S Compact 25 M75H. The room sensor is a QAA75. The boiler has a built-in display which looks very similar to the QAA75’s screen, with the same button-labels (but different physical layout). Boiler and sensor are connected by 2 wires, with polarity-indications.

Looking around

First step is always to look around on the internet. After some searching, I found out that the QAA75-sensor is actually made by Siemens. This broadened my search a bit. Turns out that Siemens refers to the communication as a “Boiler System Bus (BSB)”.

This is the first interesting piece: a bus. In contrast with the OpenTherm protocol, which is point-to-point, this name seems to indicate it’s a bus (which is point-to-multipoint). There are a lot of implications:

  • data is most probably transmitted in the voltage, not in the current, and certainly not both
  • it should be possible to passively listen in on the conversations
  • it should be possible to act as an additional device on the bus and transmit messages, without changing the bus wiring

Unfortunately, that was all the info I could get from Google. Siemens also seems to offer a product called OZW672, which claims to translate the bus into a web-interface. But it’s priced a bit above my budget (and I prefer the fun of reverse engineering).

Layer 1 – Physical

I’m assuming the data is in the voltage, so I attached an oscilloscope to the two wires connecting boiler and room sensor. What I saw was not a big surprise: mostly 12Vdc to power the room sensor with approximately every 10 seconds a short burst of 0Vdc signals. I needed to capture these bursts in order to investigate them in detail, so I wired up my poor-mans logic-analyzer.

The result is a “sound”-file that can be viewed in e.g. Audacity:


The above screenshots show the burst every 10 seconds, at 3 zoom levels. You can see that these are actually two bursts, what seems to be a question, followed by an answer. You can also see the DC-decoupling getting slightly charged at the end of each burst and slowly discharging. The idle period is actually 12Vdc, filtered out by the DC-decoupling; the pulses go down to 0Vdc (shown as approximately negative full-scale). The time dimension has varying lengths of pulses; the shortest ones are ~210µs in length (~4800 bps).

So we have the amplitude conquered, next up in the time dimension: what form of line code is used? Figuring this out just boils down to trying to apply the different schemes and seeing if they add up. This particular case looks like very regular unipolar non-return-to-zero. At this point, it’s not clear whether the 12V corresponds to a binary 1 or 0.

One can also see (after staring at the bit sequence for long enough) that bit 0 mod 11 is low; and bit 10 mod 11 is high. This looks very familiar to the bitstream produced by UARTs: a start bit, 8 data bits, a parity bit (odd even in this case) and a stop bit.

Open questions:

  • bit order: in what order are the bits of each byte transmitted? Least significant bit first (like UARTs do)? Or Most significant bit first? I’m currently assuming LSb first.
  • bit polarity: Does 12V correspond to a logical 1? Or does the physical low corresponds to a 1? I’m currently assuming 12V=1, 0V=0. 12V=0, 0V=1

Converting to RS232

Since the protocol resembles RS-232 a bit, I figured it should be easy to convert from/to RS-232 and do the rest of the processing in software on bits instead of on voltages.

  • The bus uses +12V idle; RS-232 expects -3~15V idle
  • The bus starts with a startbit of 0V; RS-232 expects a start-bit of +3~15V (logical 0)
  • The bus sends data 12V=1, 0V=0 12V=0, 0V=1; RS-232 expects data -3~15V=1, +3~15V=0
  • The bus sends even parity; RS-232 can be configured for even, odd or no parity
  • The bus sends a stopbit of 12V; RS-232 expects a stop-bit of -3~15V (logical 1)

So in order to have the bytes correctly recognized, we need to map +12V -> -3~15V and 0V -> +3~15V. This causes the idle, start, data and stop-bits to match, but inverts the data-bits and hence the parity.


This circuit is probably not RS-232 compliant, but it works. It draws some power from the DTR & TxD lines to pull the RxD line into the desired state. Elco-bus and RS-232 side are insulated from each other with an 4N25 optocoupler. I added the 1MΩ base-emitter resistor to get the speed up to the needed level.

Putting all these components together on a self-made print looks like this:

print-front print-back

Please note that this circuit converts a half-duplex bus into a full-duplex RS-232 port. The RS-232 device should be aware that the bus is half-duplex, and should try to avoid collisions! This is particularly hard with a general purpose computer with a general purpose operating system. My Atom-based motherboard with a linux 3.2.0 kernel only notifies me after receiving 8 bytes, which makes it almost impossible to avoid collisions. For now, I’m just ignoring this issue…

Layer 2 – Message format

With the above information, here is a sample of what I saw on the line:

4.437: 23 75 ff f4 f9 c2 f2 fa e6 b0 73             dc 8a 00 0b 06 3d 0d 05 19 4f 8c
4.526: 23 7f f5 f1 f8 f2 c2 fa e6 ff f1 1f 9e 55    dc 80 0a 0e 07 0d 3d 05 19 00 0e e0 61 aa
6.638: 23 79 ff f4 f9 c2 f2 fa e6 a1 c4             dc 86 00 0b 06 3d 0d 05 19 5e 3b
6.727: 23 7f f9 f1 f8 f2 c2 fa e6 ff f1 1f 79 2b    dc 80 06 0e 07 0d 3d 05 19 00 0e e0 86 d4

Every almost every message seems to start with 0x23 0xdc. Note line 4.437 and 6.638: they only differ in the second byte, and the last two bytes. Which pops up the idea of a CRC-16 of some sort.

Hoping that Elco didn’t reinvent the wheel, I’ve tried some of the standard 16-bit CRCs. When applying the CCITT-one (poly 0x1021, init 0xffff, final xor 0x0000, no reflections) I saw something remarkable:

4.437: 23 75 ff f4 f9 c2 f2 fa e6 b0 73   2adc
4.526: 23 7f f5 f1 f8 f2 c2 fa e6 ff f1 1f 9e 55   c321
6.638: 23 79 ff f4 f9 c2 f2 fa e6 a1 c4   2adc
6.727: 23 7f f9 f1 f8 f2 c2 fa e6 ff f1 1f 79 2b   c321
8.016: 23 79 ff f4 f9 c2 d2 fa 6c 17 c0   2adc
8.105: 23 7f f9 f1 f8 d2 c2 fa 6c ff fa 9f ef 3c   c321
8.209: 23 79 ff f4 f9 c2 d2 fa e1 57 e5   2adc
8.297: 23 7f f9 f1 f8 d2 c2 fa e1 ff fa 82 c8 1e   c321
9.017: 23 79 ff f4 f9 c2 f6 cf cb 72 ab   2adc
9.103: 23 7f f9 f2 f8 f6 c2 cf cb ff f5 94 58   fed3
9.936: 23 79 ff f4 f9 c2 fa fa de bf 3e   2adc

Although the CRCs doesn’t match (they should return all 0), they do return the same value for messages of the same length! One possibility of this happening is that the initial register value is different from 0xffff. This change will ripple through and result in a length-dependent value.

So I have had the computer run through all possible init-values, looking for the value that results in a matching CRC. Unfortunately, the init-value was length-dependent as well. So we’re not on the right track.

Second try: maybe I had one of my previous assumptions wrong: what if I invert all bits: 12V becomes 0, 0V becomes 1. Flipping the bits resulted in a (different) length-dependend CRC. But re-running the init-value search turned up much more promising: initializing the register to 0x0000 resulted in a CRC match for all messages that I captured so far! This confirms that the bit-polarity is reverse of what I first assumed.

The message format

I try to interpret the meaning of each byte (or bit), and replace the hex-dump by the meaning. The following log-dumps will have more and more hex replaced by the meaning.

After some more staring at these hex-dumps, I started looking for a length-field. Byte 4 seems to match that description, indicating the total length of the message (including CRC).

The first byte seems to be 0xdc in almost all messages, although there are some 0xde’s.

The lower 7 bits of bytes 2 and 3 look like addresses. A pair of messages has these two fields reversed, adding to the belief that it’s indeed a question followed by an answer. This assumption is further confirmed by some messages that look like broadcasts, with multiple responses:

6.440: dc [1 src=0x06] [0 dst=0x7f] [len] 01 05 05 00 64 CRC OK
6.545: dc [1 src=0x00] [0 dst=0x06] [len] 02 05 05 00 64 00 61 00 88 00 04 48 05 0b 37 CRC OK
6.712: dc [1 src=0x0a] [0 dst=0x06] [len] 02 05 05 00 64 00 76 00 88 03 fc 00 09 de d7 CRC OK

Since address 0x06 is only visible if the room sensor is connected, it’s easy to derive its address; address 0x00 seem to be the boiler, since both 0x0a and 0x06 are talking to it; address 0x0a is probably the boiler-display.

The messages

From this point on, things get complicated. So I decide to only figure out the messages that I actually need, and simply ignore the rest. I’d like to capture:

  • the current boiler temperature
  • the desired boiler temperature
  • the return temperature
  • the outside temperature
  • the temperature of the hot tap water
  • the status of the system (whether it’s heating the tap water, or the radiators, or just “off”), preferably with indication of modulation percentage

Finding and decoding these messages wasn’t very difficult. I just requested that data on the room sensor’s screen, and noted what messages were exchanged to get that info. It doesn’t take too long until you figure out what message contains what data. Watching the hexdumps for a little longer also give an indication which bytes change when the value changes. Here’s what I found out so far:

  • Bytes 8-9 0x05 0x19 indicate current boiler temperature, bytes 11-12 contain the temperature in 1/64th ºC
  • 0x05 0x23 indicate the desired boiler temperature, but is otherwise identical to the current boiler temperature
  • 0x05 0x1a: return temperature
  • 0x05 0x21: outside temperature
  • 0x05 0x2f: hot tap water temperature
  • 0x30 0x34: status in byte 11
   1.381: dc [1 src=0x06] [0 dst=0x00] [len] 06 3d 11 05 1a CRC OK
   1.523: dc [1 src=0x00] [0 dst=0x06] [len] 07 11 3d 05 1a 00 08 a1 CRC OK   Return=34.515625
   2.317: dc [1 src=0x06] [0 dst=0x00] [len] 06 3d 0d 05 19 CRC OK
   2.405: dc [1 src=0x00] [0 dst=0x06] [len] 07 0d 3d 05 19 00 0c 84 CRC OK   Boilertemp=50.0625
   2.509: dc [1 src=0x06] [0 dst=0x00] [len] 06 3d 0d 09 23 CRC OK
   2.598: dc [1 src=0x00] [0 dst=0x06] [len] 07 0d 3d 09 23 00 0c 80 CRC OK   Boilertemp_target=50 
   5.433: dc [1 src=0x06] [0 dst=0x00] [len] 06 3d 09 30 34 CRC OK
   5.539: dc [1 src=0x00] [0 dst=0x06] [len] 07 09 3d 30 34 00 0a CRC OK   Status=10
 169.784: dc [1 src=0x06] [0 dst=0x00] [len] 06 3d 05 05 21 CRC OK
 169.874: dc [1 src=0x00] [0 dst=0x06] [len] 07 05 3d 05 21 00 01 a9 CRC OK   Outdoor=6.640625
 170.735: dc [1 src=0x06] [0 dst=0x00] [len] 06 3d 31 05 2f CRC OK
 170.823: dc [1 src=0x00] [0 dst=0x06] [len] 07 31 3d 05 2f 00 0b fb CRC OK   Tap=47.921875

The result

I adapted my code from Velbus to do the same for Elco-messages: bridge messages between the serial port and TCP sockets. The code is published at GitHub. The resulting graphs look like this:

Boiler temperature Burner modulation Outside temperature Tap water temperature


  1. borsti87 says:

    Which transistors and diodes you have used in your circuit?

  2. Niobos says:

    Hi borsti87,

    I used transistors and diodes that I had lying around. The transistors are BC107’s and one BC177.
    The protective diode on the DTR line is an IN4001. The “signal”-diodes (Gnd and bus) are unmarked, but are “small signal diodes”.

    I don’t think any of these components have particularly strict specs. The bus-side diode protects against wrong polarization, so should just be capable of withstanding 13V reverse voltage. The maximum current is 25mA, so that’s not particularly high either. And we’re working at 4800bps, so even switching times of a microsecond is acceptable.


  3. DrTronic says:

    Hello there,

    Nice piece of masterwork I must admit. Being the owner of a similar Elco (Thision Duo S25) I’m very glad I could find your post.
    I browsed through your code and was wondering if you have write support? E.g. for setting the setpoint temp?
    Do you have experience with multi zone setup? e.g. 1 zone floorheating, 1 zone bathroom, 1 zone other rooms, …?


  4. Niobos says:

    Hi Joris/DrTronic,

    About the write support: That depends on what exactly you mean. The deamon has full write support. It simply passes messages between the bus and the TCP-socket, and doesn’t care if it’s a request (get current pressure) or a command (set point to X).
    However, I haven’t reverse-engineered the other commands than those in the elco-parse-log.pl. I started with the read-only commands, because they are slightly safer to experiment with. I don’t want to send the “self destruct” command by accident 😉

    But reverse-engineering the setpoint commands should not be to difficult. I usually put my laptop with the logging on screen next to the thermostat, and start doing stuff. I try to guess what log-line corresponds to what action I did on the thermostat. Next, I try to verify that guess by looking for the needed info in the packets: e.g. for setpoint, I expect to see the (new) temperature somewhere in the packet.
    Once I’m confident that my parser is accurate, I try to write the commands to the bus myself. This is the dangerous part, since I’m never 100% sure what the exact meaning is of what I’m sending.

    For the multi-zone setup. I actually have a multi-zone setup, but not the optimal setup. My Elco is set in pure weather-dependent mode, and I have a domotica-system that opens/closes the valves of the radiotors in the different rooms. It would be more optimal to have the domotica talk to the heating, so it could tell the pump to shut down if no valve is open.


  5. Pasi says:

    Nice job 🙂

    I have Siemens Albatros RVS61 controller and I’d like to get temperatures logged into mySQL database. Then using php I make reports out of it. Currently I have several 1-wire sensors in this logging system, but prefer using the data Albatros sees.

    about LPB:
    https://www.hqs.sbt.siemens.com/gip/general/dlc/data/assets/hq/Basic-system-data-local-process-Bus-LPB_20138_hq-en.pdf (local copy)
    https://www.hqs.sbt.siemens.com/gip/general/dlc/data/assets/hq/Local-Process-Bus-LPB_20140_hq-en.pdf (local copy)

    I noticed that the voltage is 15V. There was also a mention about collisions…

  6. Pasi says:

    I did some more research (googling) and found out the following:
    https://www.hqs.sbt.siemens.com/gip/general/dlc/data/assets/hq/Interface-module-I-O-OPEN-ALBATROS-PTM59-20V1_19957_hq-en.pdf (local copy)
    “The OCI69 interface is used to perform the conversion between the interface module
    RS232 and BatiBUS. The local process bus (LPB) is based on the BatiBUS protocol
    standard with company specific extensions.”

    https://prof.hti.bfh.ch/uploads/media/BatiBus_v1.4.pdf (local copy)

    I try to find one more document “LPB, System Engineering, Basic Documentation, CE1P2370E”. I already found a Polish version, but I prefer English 🙂

  7. Niobos says:

    thanks a lot Pasi,

    I’ve made local copies of the documents for archiving purposes, and added the link to these copies into your comments. I hope that’s OK.


  8. rayden says:

    hi, can You provide me the link to the polish version of the document that You’ve found.

  9. Glenn says:

    Hi guys

    Very interesting stuff and exactly what I was looking to be able to do with my new Elco Thision S boiler 🙂

    Only issue: the IT part I master, but I wouldn’t know how to start to build the print…

    Anyone interested in building such a print for me and selling it to me?



  10. rayden says:

    I have a problem with changing anything using this protocol.
    The interface works fine. I can read the frames correctly using the terminal.
    But when i send a identical frame to read something out or write something I’m getting no response… can anyone help me?

  11. Niobos says:

    Hi rayden,

    Are you sure that the data you sent is actually put onto the bus (e.g. with an oscilloscope)? Maybe there is something wrong with the PCB…

  12. rayden says:

    im sure I’ve checked the timings several times. I Use different transoptor SFH-600 though. Im getting the transmitted data back with the circuit by terminal… I’m getting tired of this… do You think that tve RS232 interface doesn’t have enough current (from RTS) to drive SFH-600 correctly?
    any help will be appreciated.

  13. ameliaamelia says:

    First of all thanks for the circuit.
    They were two years I was looking for something like this.

    My system consists of:
    b-Siemens RV61.843/160 + AVS37.294
    c-Siemens QAA75.611

    To operate the RS232 in my computer, I added a 33K resistor
    between the diode of DTR (DTR = 10.96V TxD idle -10.58V) and the collector of the first transistor, so the signal goes from -2v to +9.0 V.

    I have decoded all the telegrams of the QAA75.

    I know nothing about electronics, what should I do to use the circuit with a microprocessor (TTL level)?
    Thanks in advance.


  14. Niobos says:

    Hi amelia,

    Using a micro-controller is actually a better idea, since you can implement the CDMA/CD (it’s probably more arbitration, since there is no carrier to sense…).
    You would need to convert the output TTL’s to bus commands; A simple transistor should do (unless you want/need optical insulation). For the receiving end, a resistive divider should get the voltage down to TTL levels for you to sense.


  15. Merijn says:

    To Niobos: thank you very much for publishing this great post. I hope it will help me determine what makes my heating system use so much energy and what parameters I need to adjust to get better results.

    To Amelia: you say you decoded all telegrams. Any chance you’d share this info with us? I would realy appreciate it!

  16. Niobos says:

    Are there any telegrams in particular that you are looking for? I only reverse engineered the messages that I was interested in, and didn’t really pay attention to the others. But it shouldn’t be hard to reverse additional messages.

  17. Pasi says:

    I’m upgrading my home logging system (mostly 1-wire sensors) from Win to Arduino. The prototype I have been testing stores data into MySQL database. Next step would be to connect it to Siemens Albatros…

    I’m also very interested in getting the decoded telegrams of QAA75.

    Have anybody managed to connect Arduino to Batibus? The HW should be quite simple, but I have not found libraries that would “speak” Batibus.

  18. Merijn says:

    Well Niobos, I’m not sure what telegrams I can use so I think I’ll start with the one’s you’ve mentioned in your post and work my way trough the others while I’m monitoring them. Then, I’d like to connect the bus to an arduino/jeenode and let it control the room temperature. And the final step in this project is to let my domotics system control the microcontroller. While I’m at it, I’d like to save some m3’s of gas and finetune the paramaters of the Elco. I hope my heating systems will work long enough for me to finish this project! 🙂

    It looks like Pasi is working on a similar project, except for the Albatros. Maybe we should join forces?

  19. Niobos says:


    I had the same goal: integrate my existing domotica system (Velbus-based) with the heating to get a more efficient end result. But I’ve apparently lost my drive halfway through… Anyway, I would gladly join forces!

    I think the first step would indeed be to start monitoring your setup. In order to do that the correct way, I would suggest to go with the Arduino from the start, so you can have proper CSMA/CD.

    If you provide me with your GitHub username, I’ll add you to the repository.

  20. Merijn says:

    Hi Niobos

    I’ve ordered a couple of JeeNodes to monitor my gas utility meter and the Elco-bus.

    I assume you have my private email-adres trough this blog. I’m not sure it’s a good idea to post my github details here… In the meantime, I’ll start thinking about CSMA/CD and I’ll study the links in the replies above.


  21. dzairo says:

    Hi. I find problem .. in my Delphi Component I have set Discard null .. then if received $00$00 then delete this char .. and I received bad data ..

    now all working ..


  22. Merijn says:

    I see my last reply dates back almost 3 years. I finally found some time to pick up my project, this time using an ESP8266 with webinterface and MQTT support. Your blog has been of great use for this (again). I just wanted to ask: does your heater send those values continuously? I don’t get values for outdoor temperature etc. unless I explicitly ask for them at the room controller.

    Also, do you have any idea what the 3 bytes after the length mean? They must have something to do with the circuit number. I have 2 heating circuits and I see a difference in those bytes for the same requested values (same command bytes). I noticed that the second and third byte of those 3 get swapped between request and reply.

  23. Niobos says:

    Hi Merijn,

    coincidentally, I’m also picking this up again. I was going to log a lot more information, but I was unhappy with the duplex mismatch caused by my current design. So I’m re-implementing the communication protocol in the PRU of a BeagleBone Black. What are you planning to do?

    To answer your question: indeed, these messages are only sent on-demand. I have my computer send the question-messages every minute, and record the answer.

    I found an interesting forum (in German) that has reverse-engineered the protocol to a much greater extend than I did: https://www.mikrocontroller.net/topic/218643

  24. Merijn says:

    I had a new boiler installed last winter and it registered approximately 20000 burner starts in one year. I find this to be a lot but my installer said it’s normal. I’m not convinced so I want to bring this number down by tweaking the settings. But to see the results, I need to log as much as I can. For now, I’m using an Arduino Mega to read the bus and send the data to my laptop, but I have been playing around an ESP8266 to send messages to an MQTT server on a RPi. On the RPi, I will run a python-script to connect to that MQTT-server and store the packets in a database. Then, I want to create a web interface to visualize everything. And if my boiler is still in use after I’m finished with all this, I hope my installer – or anybody else with knowledge on the subject – is willing to give me some tips on how to optimize my system (but I fear my boiler is just to powerfull for my house).

    Thanks for the link. I think I read trough most of it back when I first started this project, but a lot of info was added since. Very interesting! It will be a good read this evening 🙂

  25. Niobos says:

    I have a similar situation, and came to a similar conclusion. The answer that I got from my installer is that heaters nowadays are scaled for making hot tap water, which requires a lot more power than “regular” heating. Therefor, they usually can’t modulate low enough for continuous heating.

    I’ve since moved from radiators to floor heating, and that gives better results. I’ve had days with only 1 burner start, and a continuous burn throughout the day. But with the current weather (9ºC, little wind) it pulses on/off again.

    If you can deliver a bidirectional channel between the bus and the RPi, you can simply use my scripts to generate the graphs I’ve shown in the blog post.

  26. Merijn says:

    Parallel universes do exist 🙂 Switched to floor heating too, but we’ve kept the radiator circuit for upstairs. It sounds like we even have the same installer :-)! Where are you from (if I may be so curious)? I think most installers go with the high power heaters just to be on the safe side. And I must admit, it works. Just not as efficient as could be, but that would probably require calculating heat losses for our building. That might be a challenge. I’m kind of hoping I can get that info from monitoring my system. Anyway; continuous burning is never achieved at our place. On a bad day, it starts every 5 minutes and runs for about 10 seconds. This has to change. For the moment I only have a unidirectional channel because I don’t want to break anything. I guess I’ll have to take it to the next level. And for the graphs, I’m thinking about using dygraphs (http://dygraphs.com/) for the fun of learning javascript and creating SPA’s.

  27. xAPPO says:

    Just found this .. very interesting.

    I have a Honeywell EC65 boiler and a Siemens QAA70 thermostat and I’m hoping this is pretty similar as I’d established the BatiBus basis. I’d been intending reverse engineering to try and integrate with my own home automation system but it’s a tedious task so it never made the top of the list. This makes it so much easier.. thankyou.

    At 65KW I too have an overpowered boiler as ‘recommended’ by my installer – so a lot of cycling every minute or so and even when heating water. It can’t modulate down low enough. I have plans to use a two coil tank to improve heat transfer into the water.

    I’ll let you know how I get on , I use Pi’s and have a BeagleBone Black so some commonality there. Any further progress you have made I would be most interested in hearing about.

    Also @Pasi – some of your English URL links are now broken – if you do have any updated ones that would be good or maybe I could contact you via email.

    Cheers Kevin

  28. xAPPO says:

    @Pasi Google has been my friend for those two English docs… now reading.. thanks

  29. Frederik says:

    Hi everyone,
    I just thought I would be giving you feedback here that there is now a fully-fledged solution available that runs on Arduino and covers the three different bus systems that build upon the Siemens: BSB, LPB and PPS. Or to put it easier: If you have a QAA75, QAA70, QAA55 or QAA50, you can read and/or control your heating system with this project, and through the help of many users we managed to figure out more than 1000 parameters.
    All is freely available on GitHub at:

    We also do collective orders in regular intervals for a ready-made PCB board that saves a lot of work. If you are interested you can contact me through the e-mail provided on the GitHub website.