Thermia Calibra Eco 8 heat pump and Modbus


I’m trying to integrate my new Thermia Calibra Eco 8 heat pump with Modbus binding.

I have read the Modbus binding documents more than twice, but I might as well be reading Chinese. Can someone help walk me through the process please?

I have found this PDF that seems to describe all the functions of the heat pump here

And I found this video

I have enabled BMS (Building Management System), set it to TCP (instead of RTU) and the heat pump is successfully connected to the LAN, and I have access through the Thermia Online app.

Any help would be much appreciated.

I happened to work with Thermia magna several years ago. I didn’t track all of it, my interest was primarily alarm related elements. The document you linked describes registers available on heat pump which you can scrap using any modbus tool. The openhab binding might look scary, but what it does it basically mapping of data available in these registers to openhab concepts.

The connection part you sorted, now you can try with modbus:tcp bridge by giving it valid IP address of Thermia. Port number in most of cases is 502.

Due to how protocol work you read byte array (data), this is what modbus:poller do. Just look into document and specific area of HP operations you are interested in, and look at its address. This is where you get your “start” argument for poller and length - how many registers you want to read.

Now, there is second factor to be aware of - there are 4 primary register kinds within modbus which influence length and data kind. for de-facto address 1xxxx I used poller with discrete type and data elements with readValueType=bit. For de-facto addresses 3xxxxx I think it should be holding and for 4xxxxx input. Both holding and input registers are “data” registers, each index counts for 16 bits (2 bytes) of data.
The last detail is scale column within the docs which says if you should divide read value. You do nothing if scale is 1, otherwise data you retrieve within modbus binding needs one more touch. You can use read transformation at data element. This is very basic technique allow to introduce two decimal places if divider is 100, its optimization to use basic values with smaller overhead.

Does above explanation make it any better?


Thanks for helping out.
I think I need a bit more hand holding though.

Step 1. Connect heatpump to network and enable BMS etc DONE

Step 2. Add Modbus TCP slave

Step 3. Setup TCP Slave with Heat pump network settings. I don’t see any reference to an “ID” in my heatpump settings, don’t know what this is supposed to be. Thing shows as ONLINE even if I use wrong details.

Step 4. Add “Regular Poll”

Step 5. Setup “Regular Poll”. This is where my brain starts to melt. Can we take a few examples from the Thermia doc? Say “Outdoor temperature” on page 19, and “Enable Tap Water” on page 18, and one of the power meters that use MSB and LSB on page 32.

How would I enter the details into the Regular Poll for some of these examples?




Step 6?? What happens next? Add ‘Modbus Data’? Am I right in thinking that this is the one that shows the available channels?

Thanks again for the help.

Regular poll - start address 13, length 2, type holding.
Data - readValueType int16, readStart 13.

For transformation you have to look for js or ruby or smarthomej which offers DIVIDE transformation.
The MSB and LSB information is about order of bytes in data. Don’t worry about it, just try reading data first and then look if it needs swapping.

OK, this is great! I’m starting to get some numbers. But they don’t seem right when compared to the values I can read from the app or the heat pump display.

The Thermia doc is a little confusing. There are lots of functions listed more than once and with the same info. Not sure whats going on here.

  • Modbus TCP Slave - Set to heat pump network setings. Seems to be working, I am getting numbers. Happy with this.
  • Modbus Regular Poll.
    • Seems to be doing something as I am getting numbers back, but I’m not sure I understand exactly what is happening. The length field says ‘Maximum number of registers is 125’. As there are more than 125 addresses for the Thermia, would I need two ‘Regular Poll’ things to be able to access all addresses? For example, Regular Poll thing 1 would be start 1 length 125, and Regular Poll thing 2 would be start 126 length 125? Or am I completely wrong?
    • How can you tell that the readValue type is int16? I can’t find a reference to this in the Thermia doc.
    • How can you tell what the ‘Type’ is? Coil, discrete input, holding register, or input register
  • Modbus Data - I think this is where I’m having the most trouble.
    • Is the ‘Read Address’ the actual item from the list that I want? Therefor I need a different Modbus Data Thing for every line of information I want?
    • Almost all of the interesting data points are a scale of 100, but I haven’t used transformations before and don’t really understand. Should I be making a transformation in the ‘Transformations’ section of the Main UI for this?
      • Taking ‘Outdoor Temperature’ as an example (Address 13), my app says 9 deg, but I get 0 as a number
      • ‘Comfort Wheel Setting’ (Address 5) is 17 deg on my app and Modbus Data gives me back 1700 and changes to 1800 when I change the setting on my app. Looks like it needs dividing by 100
      • ‘Compressor operating hours’ has two lines MSB and LSB, Addresses 48 and 49. Address 48 gives me 4500 (and hasn’t changed all day), and Address 49 gives me 0. Neither of which seem related to the App’s number of 1656h
      • ‘Brine In Temperature’, Address 10, gives me 3300, while the app shows me 1 deg.
      • ‘System supply line temperature’, Address 12, gives me 4500, while the app shows 25 deg.
      • ‘Weighted Tap Water Top’, Address 17, gives me 0, while my App says 15 deg

You’re right, you have split requests in order to poll whole register range. That’s beauty of modbus, its quite labor intensive and tool can make it better only a tiny bit basing on assumptions which, for sure not work across multiple devices.
The binding will raise error if you try to define a data for outside of read range or exceeding a range. A primary example is seeing an error when read length is set to 1 (data register) but parse attempt assumes uint32 type, which translates to 4 bytes/2 registers/32 bits.

This is another “but” on modbus protocol, you have to interpret data because at the protocol level its just a byte sequence. So device does not declare types, meaning you need to determine that from vendor documentation. If data register addresses are increased by 1 it means that you have 16 bits… then based on description you need to assume valid data encoding - for values where you expect negative values you have to use int types (int16 - one register, int32 - two registers, int64 - four registers). Places where values are only positive (i.e pressure) you can use unsigned variants (uint16, uint32 etc.).
You have to check docs at page 5 - everywhere where you have (MSB) and (LSB) it will fall into uint32 or int32 type. You can expect 32 bit encoding for all counter values. Once you reach maximum value it will probably begin next cycle from 0. :slight_smile:
If data you see doesn’t match try read value type. Thermia address field can be used as direct input to openHAB binding configuration. If you have any doubts focus on one register first which you can confirm is working i.e. outside temperature and once you confirm you have working configuration replicate it towards other. I usually start with mbpoll command line util to scan address range, confirm functions/offsets and only then start preparation of openHAB configuration.

You can read this for de-facto address, there is a table at page 6 (section 2.2). What you see there as first digit is function code (coil = 0xxxx, discrete = 1xxxx, holding = 3xxxx, holding = 4xxxx). You can find similar tip in modbus binding documentation. Note that Thermia folks decided that addresses 3xxxx use function code 4 which makes them seen as holding register, instead of input registers used elsewhere.

Yes, read address is a data within poll result. If you define poller with read start 30 then first data element can start from that index. Generally speaking you need one data element for each register you want to read, unless you go into bitwise operators and byte encoding of status fields.

I integrated Thermia with OH 3.0.x and haven’t done much with 4.x line yet, so I don’t know much about how it works nowadays. You can look at examples in tutorials section which consist divide10.js and divide100.js transformations.

Make sure you use proper poller type, if result looks fishy double check read start. Its not relative to result array, it uses absolute offset, so readValueStart=13. Worst case - try +1 or -1 address and see if its any better.

Documentation on page 5 describes procedure for summing these two registers. From description it looks like it should match uint32 encoding. Be aware that when you read fragment of an digit it might give you strange results.

If you ask me - modbus is still being used to punish people not only for past but also future sins. :wink:

Amazing. Thank you. I think I have most of this sussed now. I’ll do some experimentation and try to put some complete instructions here for future sinners.

This seems like a prime example for creating a new binding. A combination of these Modbus things and some relabelling, a few transformations, and a preset list of functions taken from the Thermia doc.

Not sure if I’m doing something wrong or if this is a bug.

When trying to get info from the Holding Register, I only get proper numbers when the Regular Poll for Holding Register has ‘start’ set to 1 or higher.
If I set ‘start’ to 0 before I click create on a new ‘Modbus Data’ thing, I get weird or no numbers. If I change the Regular Poll start to 1, after creation, the numbers change to the right ones. I can then decrease the Regular Poll start back to 0 and it seems to hold the right values.
This is quite cumbersome to test, so I’m not very confident that this is the whole story. ‘Input Registers’ don’t seem to suffer from this behaviour in my limited testing.
Any idea what’s going on here?

I’ve got the transformations figured out. At least for reading data. Which leads me onto my next question - How can I send commands back to the heat pump?
The first one to tackle is the ‘Comfort Wheel’ (address 5, holding register) which sets the main temperature of the house. I thought I would just fill in the same info in the ‘write’ fields of the Modbus Data Thing, but If I change my Comfort Wheel item from 17deg to 18deg, it just jumps back to 17deg again. I have tried it without a transformation in case that was an issue - ie sending 1800 to try to change 1700 (17deg) to 18deg, but it still jumps back to the previous setting.

For my transformation, I found the forum post you mentioned and I used this to divide by 100 and add ’ °C’

// Wrap everything in a function (no global variable pollution)
// variable "input" contains data passed by openHAB
(function(inputData) {
    // on read: the polled number as string
    // on write: openHAB command as string
    var DIVIDE_BY = 100;
    return (parseFloat(inputData) / DIVIDE_BY).toFixed(1) + ' °C';