Home Automation: Heating With eBus and Bulex
Heating manufacturers might not be scrambling to join the IoT craze, so here's how to automate your own Vaillant heater using open source tooling.
Join the DZone community and get the full member experience.
Join For FreeOne of the targets of my house automation was to integrate heating. Having some familiarity with heating systems, it was already clear that interfering with the internal regulation would be a big no-no. Ideally, the heating system needs to offer some kind of open interface to accept sensor values normally sent by a thermostat, but now from the house automation components instead.
The requirements:
- Retrieve basic information from the system. Is it active? Are there any errors?
- Replace the wall thermostat with house automation components and software
- Control the temperature of every room separately (where there is need to at least)
So the journey started to boldly go where no one has gone before; asking the heating guy if he could deliver a module that offers some kind of "API" to do this. As it turned out, we fell out of warp pretty fast. I will really try to limit my rant here, but heating manufacturers live in another time-space continuum. Where most software companies need to reinvent themselves every 10 years or so, heating manufacturers can do what they want to it seems.
Instead of offering a somewhat flexible interface, most of them are starting to sell "Internet thermostats" to end users. Which is great if you want to retrofit, but not if you want to integrate it in a real home automation system. Of course, those modules can probably be 'hacked' in the sense that if a mobile app can operate it, it must have some kind of interface. However, most of these modules still depend on a fixed thermostat in the house for transmitting the actual room temperature. So it will only be a partial solution to the problem.
To be fair, there are vendors that offer complete integration solutions. Buderus, for example, offers a KNX module for this, but these are ranged for "professional customers" for integration with building control. They are expensive, require you to have an even more expensive control unit (only those units allow to connect the KNX module), and it appears that, in the case of Buderus, using the KNX module means that all regulation has to be done via the module, as it takes over the internal regulation.
The most realistic option seemed to be Viessmann, together with the Vitogate 200 KNX module. This module allows high-level operations, replacing a thermostat while leaving all other regulation up to the heating system. The problem, however, was the overall price tag. A Viessmann system would be three times more expensive than comparable systems.
So, in the end, I decided to go with Bulex. For the record, Bulex is a well-known brand in Belgium. In France, they are known as Saunier-duval and in Germany as AWB. These brands are the exact same and all belong to the Vaillant group and offer a good price/quality deal. They don't have all those nifty modules or possibilities you get with brands like Viessmann — hell, don't even expect a decent manual — but quality-wise they are more than OK.
Fortunately, after some investigation, it turned out that not offering any integration modules does not have to be a show stopper per se. Bulex, like other Vaillant products, use eBus as a communication medium between devices (Wikipedia). eBus is more or less open in the sense that information on how the different layers should work is publicly available: Physical and data-Link layer and application layer.
This information is enough to build software yourself that can send and receive commands. Besides the software, one also needs a module that can be physically connected to the eBus. As it turned out, there is already an existing solution for both. Back in business we are!
The eBus USB adapter (I got mine here: eservice-online) is something that can be custom built. However, at a price of 75euro, this was not worth it for me. Some warning statements: There is also an Ethernet version of the eBus adapter on eservice-online. As I started out with this adapter first, my experience is that this does not work as expected. The adapter seems to buffer values that it reads from the bus and sends them with a delay over the network. While this is not an immediate problem, it makes it difficult to discover which command does what, as there is no instant relation between doing an action and seeing the command in the software. While this "buffer" value can be adjusted, I was not able to get it to work properly.
Another problem is the configuration of the module (like IP address, ...). For that, it requires a piece of crappy software (that only runs on Windows) and is rather problematic to work with. My advice: Use the USB adapter. It costs 50% less and the device itself requires no configuration. In my case, plugging it in on a Raspberry PI 3 was all that needed to be done.
Next, besides having an adapter, one also needs software. Over at GitHub, there is a project called 'Ebusd', John30, the creator, is doing an awesome job building and maintaining this, which does everything you want with eBus (https://github.com/john30/ebusd/). Without his effort, my little project would probably never have existed. The software comes with MQTT support and a TCP server, so communicating with it is a breeze. So basically, you hook up the adapter to a spare eBus connection on the control unit, plug in the adapter via USB into a device (RPI in my case), launch ebusd, et voilà, you are seeing all commands that are being sent on the bus. The eventual schema looks like this:
Some background: The "Room controllers" are parts of the original Bulex system (in my case, bulex exacontrol wireless). They are the wireless thermostats that we are trying to replace. They communicate with the heating controller (in my case, Bulex examaster) using some wireless protocol that is not important anyway as our goal is to eliminate them. The outside temperature sensor is important, as this is a weather-dependent regulation. As an extra, we will also be reading out its value so we can show the outside temperature without having to buy a standalone temperature unit.
Now, the tricky part is the commands. While eBus might be a standard, the commands used by heating vendors are not. Ebusd comes with config files for some devices that have already been discovered. Based on the device id (broadcast on the bus) ebusd will load a matching config file and display the representation of the commands.
Unfortunately for Bulex, there is no corresponding mapping to be found. But, playing around with other Vaillant mappings, the first command I found was the one for the outside temperature: 15b50903293c00
The address layout can be seen here: https://github.com/john30/ebusd/wiki/HowTos
Quote:
"QQZZPBSBNNDD / NNDD = count Here, QQ is the source address, ZZ is the destination address, PBSB is the primary and secondary command byte, NN is the number of data bytes DD following, and count is the number of times, the message was seen on the bus."
When sending this command using ebusctl (a CLI for ebusd, which you get after installing ebusd), the response is: 053c008a0000.
Based on a response without pre-existing mapping, one cannot know which byte is which. However, since the outside temperature is fairly standard, I found out in the existing mapping files that the two bytes 0x8a00 represent: the temperature value in the data2c datatype.
Given the eBus standard, we can convert this to decimal as follows: reverse byte order 0x8a00 becomes 0x008a. Convert high byte to decimal and multiple by 16. 0x00 = 0 and 0 * 16 = 0. Convert the high nibble of the low byte to decimal (0x8 = 8 decimal) and add it to result: 0 + 8 = 8. Convert the low nibble of the low byte to decimal (0xa = 10) and divide it by 16 (4/16= 0.625), then add it to the result: 8 + 0.625 = 8.625°C
This can also be verified by looking at the ebusd log file. Since I started ebusd with a Vaillant config file, it will try to decode every command it sees. The outside temperature is one of the commands that are apparently a bit universal over Vaillant products and is one of the few that is recognized: ootb. The temperature is also broadcasted by the control unit on a regular basis (you can actively query for it using the command above, or wait for it to pass by on the bus via the broadcast). So here we see ebusd decoding the value of the temperature broadcast. As can be seen, this matches.
So what we have now is an interface into the heating system. As ebusd runs a TCP server ootb, we can now simply write some code to send the commands and receive the value:
public List < string > send(EbusCommand ebusCommand) throws Exception {
try (Socket clientSocket = new Socket(getInstance().getEbusdIp(), getInstance().getEbusdPort())) {
List < string > results = new ArrayList < > ();
DataOutputStream out = new DataOutputStream(clientSocket.getOutputStream());
BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
for (String command: ebusCommand.getEbusCommands()) {
logger.debug("Writing to ebus: hex " + command);
out.writeBytes("hex " + command + "\n");
String result = in .readLine();
logger.debug(" Result:" + result);
if (!ebusCommand.withResult() && !result.equals("00")) {
logger.error("Command hex " + command + " resulted in " + result + " should have been 00");
}
results.add(result); in .readLine();
sleep(200);
}
return results;
}
}
The code above can be found here ( https://github.com/koen-serneels/HomeAutomation) but the bottom line is it sends "hex " + command, in the above example " hex 15b50903293c00" and it receives " 15b50903293c00". The only thing left to do is extract the payload and convert the datatype. So far so good!
Unfortunately, this is where the warm cozy feeling went cold and dark. The assumption was that when changing the temperature on the wireless thermostat, the corresponding command would be visible on the bus. After capturing it, it should have been possible to simply replay the commands using ebusd, creating an index of useful commands. Unfortunately, this was not working. The most likely explanation was that the wireless thermostat would communicate directly with the control unit, but the control unit would be smart enough to not put these command on the wired bus, as it is the final receiver.
After giving it some thought, I decided to place a small bet and get myself a wired eBus thermostat instead (the normal Bulex Exacontrol). Making the schema look like this:
Luckily, this worked. Commands issued by the wired thermostat became visible on the bus. In hindsight, getting a wired thermostat was not really required. What happens is that when the control unit receives a command (no matter the source, wireless or wired) it changes an internal register based on the received the command. So the command basically addresses a register with a given value. Scanning all of the registers before changing the temperature and scanning them again after the change would also reveal the changed registers without having to intercept the actual command.
This is a bit more hassle, as reading all the registers takes some time (~2 minutes) and one also needs to filter out the changes done by the system in the meantime (these changes would be triggered by internal housekeeping, time depended stuff, updates from other eBus connected devices such as the heater, etc.). To be sure, one has the real register related to the command the procedure needs to be repeated a couple of times, diffing the files each time and eliminating the not related ones. Anyway, this technique works and it is also required to find out registers for the internal state, which are never read or sent to/from thermostats in the first place — and works no matter the type of thermostat.
Below is a snippet (https://github.com/koen-serneels/HomeAutomation) from a small tool that scans the range for 0x29 from 0x00 through 0xFF:
public class EbusRegisterReader {
public static void main(String[] args) throws Exception {
String template = "15b5090329%s00";
try (Socket clientSocket = new Socket("192.168.0.10", 8888)) {
List < String > results = new ArrayList < > ();
DataOutputStream out = new DataOutputStream(clientSocket.getOutputStream());
BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
for (int i = 0; i < 255; i++) {
String cmd = String.format(template, leftPad(new BigInteger("" + i).toString(16), 2, "0"));
out.writeBytes("hex " + cmd + "\n");
String result = in .readLine();
System.err.println(cmd + " - " + result); in .readLine();
}
out.close(); in .close();
}
}
}
There seem to be two ranges: 0x29 (as shown in the example) and 0x0D. Both ranges can be read. The following bytes denote the rest of the register. I'm not really sure what the 0x29/0x0D is: It's either an instruction for reading or simply a part of the register. Anyway, scanning both 0x29 and 0x0D from 0x0000 through 0xFFFF should scan most if not all available registers. Note that scanning all of these addresses takes a lot of time, especially since only a small portion of the address can actually be read (the others produce a read error). To fix this, one can simply write the addresses that returned something meaningful to a file and use that file the next time instead of re-scanning everything.
Another important aspect is that the control unit identifies the heating circuit (HC) that needs to be controlled using the base or source address. In my case, there are two heating circuits. One for the floor heating at the ground floor and one for the floor heating at the upper floor. When thermostats are first linked with the control unit, each gets a specific base address, which is then later on used to identify the HC to be operated.
So, in my case, sending a command with source 0x30 operates HC1, sending that same command with source 0x70 operates HC2. The command in combination with the source address determines which HC will be operated. These source addresses seem to be fixed, at least for my configuration (if there are more heating circuits or other components, these address could be different). Thankfully, ebusd implemented my change request to support changing source addresses on the fly (https://github.com/john30/ebusd/issues/50), so now it is possible to send along the source address with the command, as opposed to the source address only being statically configurable at ebusd startup.
Note that depending on the HC, registers can have different meanings. So, a register that accepts the desired temperature (for example) could be different for HC1 and HC2. To be sure, you have to test with both circuits to verify the actual registers to use. In my case, this makes sense, as HC1 and HC2 are of a different type. I won't go into detail here, but by default, the system only allows one low-temperature circuit or 'mixed circuit' (for floor heating) and two where needed, so we came with a workaround by using a second HC but of type 'high temperature', but configure it to not exceed 38°C water temperature, making it virtually equal to a low or mixed circuit. While the control unit operates the mixing valve for HC1 (as being a genuine low temperature/mixed circuit), it does not for HC1. This is, however, not a problem as the heater modulates, so it does not need the mixing valve in the first place. It would be needed if there was an actual high-temperature circuit (for traditional radiators, for example.) but this is not the case, so all good!
At this point, we have created an interface into the heating system. With some more playing with said techniques, following commands were found:
- Get the outside temperature
- Get heating demand status of heating circuits 1 and 2
- Get the target water temperature
- Get heating circuit enabled status
- Set the desired temperature
- Set the current temperature
- Set heating circuit enabled
All the commands are available here.
Things still left to do are read/set the hot water temperature. This will be fairly easy to do. And we have to implement a way for detecting if there is any error in the system. This will probably be harder, as I will first have to trigger an error, but it is nice to have and not really mandatory.
Finally, we need something for independent room temperature control. Whether or not this makes sense depends on the situation and type of house. In my case, the ground floor is a more or less open space. It would not make sense to control individual floor heating circuits. However, on the upper floor, there are different bedrooms and a bathroom, each having different temperature requirements. In case you're not familiar with floor heating, it is basically different circuits of tubes connected to a manifold. Normally, there is at least one circuit per room (possibly more if the surface requires it). So if a room has two circuits, one could control the temperature by opening or closing valves on the manifold. This is exactly what we are going to do. Each valve on the manifold is operated by an electronic valve. In my case, it is a 230v NC valve (in German: stellantrieb).
The valves are controlled by an MDT KNX heating actuator . The actuator can function in different modes. In automatic mode, you pass it the current room temperature and, based on the set-point temperature, it will, in PWM style, drive the valve so that it obtains the required temperature. There is also manual mode in which it simply opens or closes the valve. The manual mode is just acting as a relay, turning the valves on or off. In my setup, the actuator is in manual mode. The actual controller is self-written. In this case, I could just have used 'simple' relays instead of an actuator, but relays also need to be controlled — space and energy efficient — so you need to know which 'position' they are in — durable and so forth. In the end, the KNX controller is not that much more expensive, and it can still do some important housekeeping stuff that I do not need to program. For example, it can operate the valves from time to time in summer mode to make sure they don't get stuck. Ok, thanks to these valves, it is now possible to control each room independently. Each room corresponds to at least one valve, and some rooms have multiple circuits. In that case, the combined valves for that room will be operated as one.
The only thing we need to do now is glue everything together. For this I have written a heating controller that gathers the current/required temperatures, communicates them to the control unit via eBus, and operates the valves.
The current and required temperatures are obtained from a sensor in each room. In my case, these are built-in into the KNX wall switches. Even though most rooms have motion sensors, where it makes sense, there is also a switch in every room allowing an elegant way (among others) to do these measurements. Aside from setting the desired temperature via the web app, it can also be done on the switches directly.
So what basically happens is that the room temperature collector receives the current and desired temperature (from the wall switches and/or web app) and sends them to the heating controller. Based on this, it knows if there is heating demand (there is at least one room of which the desired temp is greater than the current temp) and which room has the highest heating demand. This software is running on the same RPI as ebusd is running.
But now comes the trick. Remember, the heating system in my case has only two circuits: one for the ground floor and one for the upper floor. This means it was originally designed for two thermostats and two circulation pumps. On the upper floor, there are now four rooms that require individual temperature control (and actually four thermostats). To solve this, the heating controller simply communicates the values of the room with the highest heating demand to the heating control unit via eBus. It basically simulates a thermostat that we (virtually) move around all the time into the room with the highest demand. By doing this, the heating circuit will only be enabled when there is at least one room with heating demand. From there on, everything is controlled with the valves by the room valve controller
For example, there are two rooms in demand of heating. The data from the room with the highest demand is communicated via eBus. The heater operates HC1. In this case, the valves for room 1 and 2 open. The valves for the other room remain closed as they don't have heating demand. Suppose room 1 reaches its desired temperature: The valves for room 1 now close. The heating controller will now communicate the temperatures for room 2, which still has heating demand. Finally, if room 2 reaches its desired temperature, the valves for room 2 close, and if no other heating demand exists, the heater will turn off HC1.
Note: It is important that the manifold that is being used has a "bypass" that still allows water to circulate if all valves are closed. Since the heater is not in control of the valves (we are with our software), there is no real guarantee in what will happen. For example, in case of heating demand, the heater might switch a heating circuit to 'on' when all valves are still closed. Opening a valve is slow (~1-2 minutes), so there might be a short time in which the circulation pump operates with all valves closed. This is not a problem at all, as long as the water can circulate. Although modern pumps normally have all kinds of protection, it's better to be safe than sorry.
Finally, some screenshots how the control looks from the web app (the web app is currently created via Loxone). On the right, the general status and the temperature control from the bathroom is shown. Currently, it's summer and the heating is turned off. In that case, all rooms go to"frost protect" mode, meaning they are set to 10°C. On the left, there is the schedule that is activated from the moment the heating goes out of frost protection
In the end, I'm happy with the result. The system has been running stable for over a year now, and nearly all my requirements were fulfilled — more than I had bargained for, to be honest. However, was this worth all the energy? Yes, as long as you like taming heating systems. Should you do this? Probably not. Get yourself a Viessmann with KNX module and live long and prosper!
Published at DZone with permission of Koen Serneels, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments