Is it possible to create a Modbus 'bundle', to avoid having loads of individual Things?

Is it possible to create a Modbus ‘bundle’, to avoid having loads of individual Things? Similar to how the SunSpec bundle works?

I have a few Modbus bits of equipment in my house. Creating individual Modbus Data Things creates hundreds of Things in my Things list.

No. Noone but you knows which data you want to see.

And you don’t need to read them all.
Just setup things for those registers you really need, that’s usually not more than a handful.

Ah, another one of your classic dismissive, unhelpful comments.

It would be valid to anyone using the same equipment. If I and other people could spend the time to do the boring data input, it would create a library of compatible devices without the tricky setup from scratch each time.

Even if this wasn’t the case, It still makes sense to want this tidier and not have a separate thing for each channel.

I am only using the registers I use, but it still adds up to a lot. I have two heat pumps, each one requires 6 pollers to cover the types and range of values needed, and about 40 registers per heat pump for the ones I am currently using. I would also like to add the error code registers which would be another 30ish each. I have a heat recovery ventilation currently using about 60 registers. And I have two SolarEdge inverters which do have the sunspec bundle, so these are thankfully tidy and don’t take up another 20-30ish registers each.

Ah, another one of your classic dismissive, ungrateful responses for no good reason when you do not get right away what you want for free, discouraging about anyone willing to help you.
But you’re lucky you now got rid of me, I won’t attempt helping you any more.
Good luck with solving your very own problems on your very own or finding others that don’t care about your ingratitude.

modbus registers and mqtt topics are somewhat familiar. Some devices spit out massive information points and you only need a handful of those. Within MQTT you can simply use channels for topics or even JSON-payloads within one thing, if you’d like. With modbus, that’s not possible, there’s basically one register - one thing.

That’s one way to look at it, but modbus in openHAB is indeed “one thing - one register”, which makes it tiring to define and you’ll end up with loads of things. I don’t know, if a “bundle” of registers all having the same configuration would be even possible within the architecture of openHab…
So, what I did was:

  1. use nodeRed for modbus readouts, which is a tiny bit less complex to do
  2. send all values via MQTT over my network for everyone to consume
  3. add my modbus devices (here: PV inverter) into one MQTT-Thing having multiple channels

I must say, I use nodeRed heavily for things like that, especially if I have to do some calculations importing data from devices and even more so, if there are multiple consumers of that data - the inverter data gets imported into openHAB and evcc, same goes for my BEVs and their data, and so on.

@MartOs can you show me what your modbus thing config looks like? I’m unfamiliar with modbus, just curious.

Sure, here is my heat pumps config currently. 866 lines

  • There could be some errors in here as I test each bit of the system, so be warned if anyone wants to copy this and expect it to be perfect.
// BRIDGES
Bridge modbus:tcp:thermia1 "Heat Pump 1 - Modbus TCP Slave" [ 
    host="10.0.5.10", 
    port=502, 
    id=1, 
    maxTries=1, 
    timeBetweenTransactionsMillis=60, 
    reconnectAfterMillis=600000, 
    timeBetweenReconnectMillis=100, 
    afterConnectionDelayMillis=150, 
    rtuEncoded=false
]

Bridge modbus:tcp:thermia2 "Heat Pump 2 - Modbus TCP Slave" [ 
    host="10.0.5.11", 
    port=502, 
    id=1, 
    maxTries=1, 
    timeBetweenTransactionsMillis=60, 
    reconnectAfterMillis=600000, 
    timeBetweenReconnectMillis=100, 
    afterConnectionDelayMillis=150, 
    rtuEncoded=false
]

// POLLERS
Thing modbus:poller:thermia1:inputregisters "Heat Pump 1 - Poller Input" (modbus:tcp:thermia1) [
    length=100,
    start=1,
    refresh=5000,
    maxTries=3,
    cacheMillis=50,
    type="input"
]

Thing modbus:poller:thermia1:holdingregisters "Heat Pump 1 - Poller Holding" (modbus:tcp:thermia1) [
    length=123,
    start=0,
    refresh=5000,
    maxTries=3,
    cacheMillis=50,
    type="holding"
]
Thing modbus:poller:thermia1:holdingregistersb "Heat Pump 1 - Poller Holding B" (modbus:tcp:thermia1) [
    length=20,
    start=123,
    refresh=5000,
    maxTries=3,
    cacheMillis=50,
    type="holding"
]

Thing modbus:poller:thermia1:holdingregistersc "Heat Pump 1 - Poller Holding C" (modbus:tcp:thermia1) [
    length=10,
    start=321,
    refresh=5000,
    maxTries=3,
    cacheMillis=50,
    type="holding"
]

Thing modbus:poller:thermia1:coilregisters "Heat Pump 1 - Poller Coil" (modbus:tcp:thermia1) [
    length=24,
    start=3,
    refresh=5000,
    maxTries=3,
    cacheMillis=50,
    type="coil"
]

Thing modbus:poller:thermia1:discreteregisters "Heat Pump 1 - Poller Discrete" (modbus:tcp:thermia1) [
    length=2,
    start=0,
    refresh=5000,
    maxTries=3,
    cacheMillis=50,
    type="discrete"
]

Thing modbus:poller:thermia2:inputregisters "Heat Pump 2 - Poller Input Registers" (modbus:tcp:thermia2) [
    length=100,
    start=1,
    refresh=5000,
    maxTries=3,
    cacheMillis=50,
    type="input"
]

Thing modbus:poller:thermia2:holdingregisters "Heat Pump 2 - Poller Holding" (modbus:tcp:thermia2) [
    length=120,
    start=0,
    refresh=5000,
    maxTries=3,
    cacheMillis=50,
    type="holding"
]
Thing modbus:poller:thermia2:holdingregistersb "Heat Pump 2 - Poller Holding B" (modbus:tcp:thermia2) [
    length=20,
    start=123,
    refresh=5000,
    maxTries=3,
    cacheMillis=50,
    type="holding"
]

Thing modbus:poller:thermia2:holdingregistersc "Heat Pump 2 - Poller Holding C" (modbus:tcp:thermia2) [
    length=10,
    start=321,
    refresh=5000,
    maxTries=3,
    cacheMillis=50,
    type="holding"
]


Thing modbus:poller:thermia2:coilregisters "Heat Pump 2 - Poller Coil" (modbus:tcp:thermia2) [
    length=25,
    start=3,
    refresh=5000,
    maxTries=3,
    cacheMillis=50,
    type="coil"
]

Thing modbus:poller:thermia2:discreteregisters "Heat Pump 2 - Poller Discrete" (modbus:tcp:thermia2) [
    length=2,
    start=0,
    refresh=5000,
    maxTries=3,
    cacheMillis=50,
    type="discrete"
]


// CHANNELS

// HEAT PUMP 1

Thing modbus:data:thermia1:operationalmode "Heat Pump 1 - Operational Mode" (modbus:poller:thermia1:holdingregisters) [
    readValueType="int16",
    readTransform="MAP(modbus_thermia_operationalMode.map)",
    readStart=0,
    updateUnchangedValuesEveryMillis=1000,
    writeTransform="MAP(modbus_thermia_operationalMode.map)",
    writeType="holding",
    writeValueType="int16",
    writeMultipleEvenWithSingleRegisterOrCoil=false,
    writeMaxTries=3,
    writeStart=0
]


Thing modbus:data:thermia1:comfortwheel "Heat Pump 1 - Comfort Wheel" (modbus:poller:thermia1:holdingregisters) [
    readValueType="int16",
    readTransform="JS(modbus_scale100.js)",
    writeTransform="JS(config:js:5af66aadef)",
    writeType="holding",
    readStart=5,
    updateUnchangedValuesEveryMillis=1000,
    writeValueType="int16",
    writeMultipleEvenWithSingleRegisterOrCoil=false,
    writeMaxTries=3,
    writeStart=5
]



Thing modbus:data:thermia1:enabletapwater "Heat Pump 1 - Enable Tap Water" (modbus:poller:thermia1:coilregisters) [
    writeType="coil",
    readStart=8,
    updateUnchangedValuesEveryMillis=1000,
    writeMultipleEvenWithSingleRegisterOrCoil=false,
    writeMaxTries=3,
    writeStart=8
]

Thing modbus:data:thermia1:enableheat "Heat Pump 1 - Enable Heat" (modbus:poller:thermia1:coilregisters) [
    writeType="coil",
    readStart=9,
    updateUnchangedValuesEveryMillis=1000,
    writeMultipleEvenWithSingleRegisterOrCoil=false,
    writeMaxTries=3,
    writeStart=9
]





Thing modbus:data:thermia1:currentlyrunning "Heat Pump 1 - Currently Running" (modbus:poller:thermia1:inputregisters) [
    readValueType="int16",
    readStart=1,
    readTransform="MAP(config:map:Modbus_Thermia_Currently_Running)",
    updateUnchangedValuesEveryMillis=1000
]

Thing modbus:data:thermia1:compressorspeedrpm "Heat Pump 1 - Compressor Speed RPM" (modbus:poller:thermia1:inputregisters) [
    readValueType="int16",
    readStart=5,
    updateUnchangedValuesEveryMillis=1000
]


Thing modbus:data:thermia1:condenserintemperature "Heat Pump 1 - Condenser In Temperature" (modbus:poller:thermia1:inputregisters) [
    readValueType="int16",
    readStart=8,
    readTransform="JS(modbus_scale100.js)",
    updateUnchangedValuesEveryMillis=1000
]

Thing modbus:data:thermia1:condenserouttemperature "Heat Pump 1 - Condenser Out Temperature" (modbus:poller:thermia1:inputregisters) [
    readValueType="int16",
    readStart=9,
    readTransform="JS(modbus_scale100.js)",
    updateUnchangedValuesEveryMillis=1000
]

Thing modbus:data:thermia1:brineintemperature "Heat Pump 1 - Brine In Temperature" (modbus:poller:thermia1:inputregisters) [
    readValueType="int16",
    readStart=10,
    readTransform="JS(modbus_scale100.js)",
    updateUnchangedValuesEveryMillis=1000
]

Thing modbus:data:thermia1:brineouttemperature "Heat Pump 1 - Brine Out Temperature" (modbus:poller:thermia1:inputregisters) [
    readValueType="int16",
    readStart=11,
    readTransform="JS(modbus_scale100.js)",
    updateUnchangedValuesEveryMillis=1000
]

Thing modbus:data:thermia1:systemsupplylinetemperature "Heat Pump 1 - System Supply Line Temperature" (modbus:poller:thermia1:inputregisters) [
    readValueType="int16",
    readStart=12,
    // readTransform="JS(modbus_scale100.js)",
    updateUnchangedValuesEveryMillis=1000
]

Thing modbus:data:thermia1:outdoortemperature "Heat Pump 1 - Outdoor Temperature" (modbus:poller:thermia1:inputregisters) [
    readValueType="int16",
    readStart=13,
    readTransform="JS(modbus_scale100.js)",
    updateUnchangedValuesEveryMillis=1000
]

Thing modbus:data:thermia1:tapwatertoptemperature "Heat Pump 1 - Tap Water Top Temperature" (modbus:poller:thermia1:inputregisters) [
    readValueType="int16",
    readStart=15,
    readTransform="JS(modbus_scale100.js)",
    updateUnchangedValuesEveryMillis=1000
]

Thing modbus:data:thermia1:tapwaterlowertemperature "Heat Pump 1 - Tap Water Lower Temperature" (modbus:poller:thermia1:inputregisters) [
    readValueType="int16",
    readStart=16,
    readTransform="JS(modbus_scale100.js)",
    updateUnchangedValuesEveryMillis=1000
]

Thing modbus:data:thermia1:tapwaterweightedtemperature "Heat Pump 1 - Tap Water Weighted Temperature" (modbus:poller:thermia1:inputregisters) [
    readValueType="int16",
    readStart=17,
    readTransform="JS(modbus_scale100.js)",
    updateUnchangedValuesEveryMillis=1000
]

Thing modbus:data:thermia1:systemsupplylinecalculatedsetpoint "Heat Pump 1 - System Supply Line Calculated Set Point" (modbus:poller:thermia1:inputregisters) [
    readValueType="int16",
    readStart=18,
    readTransform="JS(modbus_scale100.js)",
    updateUnchangedValuesEveryMillis=1000
]

Thing modbus:data:thermia1:compressorspeedpercent "Heat Pump 1 - Compressor Speed Percent" (modbus:poller:thermia1:inputregisters) [
    readValueType="int16",
    readStart=54,
    readTransform="JS(modbus_scale100.js)",
    updateUnchangedValuesEveryMillis=1000
]

Thing modbus:data:thermia1:compressorcurrentgear "Heat Pump 1 - Compressor Current Gear" (modbus:poller:thermia1:inputregisters) [
    readValueType="int16",
    readStart=61,
    readTransform="JS(modbus_scale100.js)",
    updateUnchangedValuesEveryMillis=1000
]


Thing modbus:data:thermia1:comfortmode "Heat Pump 1 - Comfort Mode" (modbus:poller:thermia1:inputregisters) [
    readValueType="int16",
    readTransform="MAP(modbus_thermia_comfortMode.map)",
    writeTransform="MAP(modbus_thermia_comfortMode.map)",
    writeType="holding",
    readStart=82,
    updateUnchangedValuesEveryMillis=1000,
    writeValueType="int16",
    writeMultipleEvenWithSingleRegisterOrCoil=true,
    writeMaxTries=3,
    writeStart=82
]

Thing modbus:data:thermia1:heatcurvesystem "Heat Pump 1 - Heat Curve System" (modbus:poller:thermia1:inputregisters) [
    readValueType="int16",
    readTransform="JS(modbus_scale100.js)",
    readStart=19,
    updateUnchangedValuesEveryMillis=1000,
    writeTransform="JS(config:js:5af66aadef)",
    writeType="holding",
    writeValueType="int16",
    writeMultipleEvenWithSingleRegisterOrCoil=true,
    writeMaxTries=3,
    writeStart=19
]
Thing modbus:data:thermia1:heatcurveX1 "Heat Pump 1 - Heat Curve X-1" (modbus:poller:thermia1:inputregisters) [
    readValueType="int16",
    readTransform="JS(modbus_scale100.js)",
    readStart=20,
    updateUnchangedValuesEveryMillis=1000
]
Thing modbus:data:thermia1:heatcurveX2 "Heat Pump 1 - Heat Curve X-2" (modbus:poller:thermia1:inputregisters) [
    readValueType="int16",
    readTransform="JS(modbus_scale100.js)",
    readStart=21,
    updateUnchangedValuesEveryMillis=1000
]

Thing modbus:data:thermia1:heatcurveX3 "Heat Pump 1 - Heat Curve X-3" (modbus:poller:thermia1:inputregisters) [
    readValueType="int16",
    readTransform="JS(modbus_scale100.js)",
    readStart=22,
    updateUnchangedValuesEveryMillis=1000
]

Thing modbus:data:thermia1:heatcurveX4 "Heat Pump 1 - Heat Curve X-4" (modbus:poller:thermia1:inputregisters) [
    readValueType="int16",
    readTransform="JS(modbus_scale100.js)",
    readStart=23,
    updateUnchangedValuesEveryMillis=1000
]

Thing modbus:data:thermia1:heatcurveX5 "Heat Pump 1 - Heat Curve X-5" (modbus:poller:thermia1:inputregisters) [
    readValueType="int16",
    readTransform="JS(modbus_scale100.js)",
    readStart=24,
    updateUnchangedValuesEveryMillis=1000
]

Thing modbus:data:thermia1:heatcurveX6 "Heat Pump 1 - Heat Curve X-6" (modbus:poller:thermia1:inputregisters) [
    readValueType="int16",
    readTransform="JS(modbus_scale100.js)",
    readStart=25,
    updateUnchangedValuesEveryMillis=1000
]

Thing modbus:data:thermia1:heatcurveX7 "Heat Pump 1 - Heat Curve X-7" (modbus:poller:thermia1:inputregisters) [
    readValueType="int16",
    readTransform="JS(modbus_scale100.js)",
    readStart=26,
    updateUnchangedValuesEveryMillis=1000
]

Thing modbus:data:thermia1:heatcurveY1 "Heat Pump 1 - Heat Curve Y-1" (modbus:poller:thermia1:holdingregisters) [
    readValueType="int16",
    readTransform="JS(modbus_scale100.js)",
    writeTransform="JS(config:js:5af66aadef)",
    writeType="holding",
    readStart=6,
    updateUnchangedValuesEveryMillis=1000,
    writeValueType="int16",
    writeMultipleEvenWithSingleRegisterOrCoil=true,
    writeMaxTries=3,
    writeStart=6
]
Thing modbus:data:thermia1:heatcurveY2 "Heat Pump 1 - Heat Curve Y-2" (modbus:poller:thermia1:holdingregisters) [
    readValueType="int16",
    readTransform="JS(modbus_scale100.js)",
    writeTransform="JS(config:js:5af66aadef)",
    writeType="holding",
    readStart=7,
    updateUnchangedValuesEveryMillis=1000,
    writeValueType="int16",
    writeMultipleEvenWithSingleRegisterOrCoil=true,
    writeMaxTries=3,
    writeStart=7
]
Thing modbus:data:thermia1:heatcurveY3 "Heat Pump 1 - Heat Curve Y-3" (modbus:poller:thermia1:holdingregisters) [
    readValueType="int16",
    readTransform="JS(modbus_scale100.js)",
    writeTransform="JS(config:js:5af66aadef)",
    writeType="holding",
    readStart=8,
    updateUnchangedValuesEveryMillis=1000,
    writeValueType="int16",
    writeMultipleEvenWithSingleRegisterOrCoil=true,
    writeMaxTries=3,
    writeStart=8
]Thing modbus:data:thermia1:heatcurveY4 "Heat Pump 1 - Heat Curve Y-4" (modbus:poller:thermia1:holdingregisters) [
    readValueType="int16",
    readTransform="JS(modbus_scale100.js)",
    writeTransform="JS(config:js:5af66aadef)",
    writeType="holding",
    readStart=9,
    updateUnchangedValuesEveryMillis=1000,
    writeValueType="int16",
    writeMultipleEvenWithSingleRegisterOrCoil=true,
    writeMaxTries=3,
    writeStart=9
]
Thing modbus:data:thermia1:heatcurveY5 "Heat Pump 1 - Heat Curve Y-5" (modbus:poller:thermia1:holdingregisters) [
    readValueType="int16",
    readTransform="JS(modbus_scale100.js)",
    writeTransform="JS(config:js:5af66aadef)",
    writeType="holding",
    readStart=10,
    updateUnchangedValuesEveryMillis=1000,
    writeValueType="int16",
    writeMultipleEvenWithSingleRegisterOrCoil=true,
    writeMaxTries=3,
    writeStart=10
]
Thing modbus:data:thermia1:heatcurveY6 "Heat Pump 1 - Heat Curve Y-6" (modbus:poller:thermia1:holdingregisters) [
    readValueType="int16",
    readTransform="JS(modbus_scale100.js)",
    writeTransform="JS(config:js:5af66aadef)",
    writeType="holding",
    readStart=11,
    updateUnchangedValuesEveryMillis=1000,
    writeValueType="int16",
    writeMultipleEvenWithSingleRegisterOrCoil=true,
    writeMaxTries=3,
    writeStart=11
]

Thing modbus:data:thermia1:tapwaterstart "Heat Pump 1 - Tap Water Start" (modbus:poller:thermia1:holdingregisters) [
    readValueType="int16",
    readTransform="JS(modbus_scale100.js)",
    writeTransform="JS(config:js:5af66aadef)",
    writeType="holding",
    readStart=22,
    updateUnchangedValuesEveryMillis=1000,
    writeValueType="int16",
    writeMultipleEvenWithSingleRegisterOrCoil=true,
    writeMaxTries=3,
    writeStart=22
]

Thing modbus:data:thermia1:tapwaterstop "Heat Pump 1 - Tap Water Stop" (modbus:poller:thermia1:holdingregisters) [
    readValueType="int16",
    readTransform="JS(modbus_scale100.js)",
    writeTransform="JS(config:js:5af66aadef)",
    writeType="holding",
    readStart=23,
    updateUnchangedValuesEveryMillis=1000,
    writeValueType="int16",
    writeMultipleEvenWithSingleRegisterOrCoil=true,
    writeMaxTries=3,
    writeStart=23
]

Thing modbus:data:thermia1:powerconsumptionmode "Heat Pump 1 - Power Consumption Mode" (modbus:poller:thermia1:holdingregistersb) [
    readValueType="int16",
    readTransform="MAP(modbus_thermia_powerConsumptionMode.map)",
    writeTransform="MAP(modbus_thermia_powerConsumptionMode.map)",
    writeType="holding",
    readStart=124,
    updateUnchangedValuesEveryMillis=1000,
    writeValueType="int16",
    writeMultipleEvenWithSingleRegisterOrCoil=true,
    writeMaxTries=3,
    writeStart=124
]

Thing modbus:data:thermia1:powerconsumptionlimit "Heat Pump 1 - Power Consumption Limit" (modbus:poller:thermia1:holdingregistersb) [
    readValueType="int16",
    readTransform="JS(modbus_scale10.js)",
    writeTransform="JS(modbus_scale10.js)",
    writeType="holding",
    readStart=125,
    updateUnchangedValuesEveryMillis=1000,
    writeValueType="int16",
    writeMultipleEvenWithSingleRegisterOrCoil=true,
    writeMaxTries=3,
    writeStart=125
]

Thing modbus:data:thermia1:enableimmersionheater "Heat Pump 1 - Enable Immersion Heater" (modbus:poller:thermia1:holdingregistersc) [
    readValueType="int16",
    writeType="holding",
    // readTransform="MAP(modbus_thermia_enableImmersion.map)",
    // writeTransform="MAP(modbus_thermia_enableImmersion.map)",
    readStart=321,
    updateUnchangedValuesEveryMillis=1000,
    writeValueType="int16",
    writeMultipleEvenWithSingleRegisterOrCoil=true,
    writeMaxTries=3,
    writeStart=321
]


// HEAT PUMP 2

Thing modbus:data:thermia2:operationalmode "Heat Pump 2 - Operational Mode" (modbus:poller:thermia2:holdingregisters) [
    readValueType="int16",
    readTransform="MAP(modbus_thermia_operationalMode.map)",
    readStart=0,
    updateUnchangedValuesEveryMillis=1000,
    writeTransform="MAP(modbus_thermia_operationalMode.map)",
    writeType="holding",
    writeValueType="int16",
    writeMultipleEvenWithSingleRegisterOrCoil=false,
    writeMaxTries=3,
    writeStart=0
]


Thing modbus:data:thermia2:comfortwheel "Heat Pump 2 - Comfort Wheel" (modbus:poller:thermia2:holdingregisters) [
    readValueType="int16",
    readTransform="JS(modbus_scale100.js)",
    writeTransform="JS(config:js:5af66aadef)",
    writeType="holding",
    readStart=5,
    updateUnchangedValuesEveryMillis=1000,
    writeValueType="int16",
    writeMultipleEvenWithSingleRegisterOrCoil=false,
    writeMaxTries=3,
    writeStart=5
]



Thing modbus:data:thermia2:enabletapwater "Heat Pump 2 - Enable Tap Water" (modbus:poller:thermia2:coilregisters) [
    writeType="coil",
    readStart=8,
    updateUnchangedValuesEveryMillis=1000,
    writeMultipleEvenWithSingleRegisterOrCoil=false,
    writeMaxTries=3,
    writeStart=8
]

Thing modbus:data:thermia2:enableheat "Heat Pump 2 - Enable Heat" (modbus:poller:thermia2:coilregisters) [
    writeType="coil",
    readStart=9,
    updateUnchangedValuesEveryMillis=1000,
    writeMultipleEvenWithSingleRegisterOrCoil=false,
    writeMaxTries=3,
    writeStart=9
]


Thing modbus:data:thermia2:currentlyrunning "Heat Pump 2 - Currently Running" (modbus:poller:thermia2:inputregisters) [
    readValueType="int16",
    readStart=1,
    readTransform="MAP(config:map:Modbus_Thermia_Currently_Running)",
    updateUnchangedValuesEveryMillis=1000
]

Thing modbus:data:thermia2:compressorspeedrpm "Heat Pump 2 - Compressor Speed RPM" (modbus:poller:thermia2:inputregisters) [
    readValueType="int16",
    readStart=5,
    updateUnchangedValuesEveryMillis=1000
]


Thing modbus:data:thermia2:condenserintemperature "Heat Pump 2 - Condenser In Temperature" (modbus:poller:thermia2:inputregisters) [
    readValueType="int16",
    readStart=8,
    readTransform="JS(modbus_scale100.js)",
    updateUnchangedValuesEveryMillis=1000
]

Thing modbus:data:thermia2:condenserouttemperature "Heat Pump 2 - Condenser Out Temperature" (modbus:poller:thermia2:inputregisters) [
    readValueType="int16",
    readStart=9,
    readTransform="JS(modbus_scale100.js)",
    updateUnchangedValuesEveryMillis=1000
]

Thing modbus:data:thermia2:brineintemperature "Heat Pump 2 - Brine In Temperature" (modbus:poller:thermia2:inputregisters) [
    readValueType="int16",
    readStart=10,
    readTransform="JS(modbus_scale100.js)",
    updateUnchangedValuesEveryMillis=1000
]

Thing modbus:data:thermia2:brineouttemperature "Heat Pump 2 - Brine Out Temperature" (modbus:poller:thermia2:inputregisters) [
    readValueType="int16",
    readStart=11,
    readTransform="JS(modbus_scale100.js)",
    updateUnchangedValuesEveryMillis=1000
]

Thing modbus:data:thermia2:systemsupplylinetemperature "Heat Pump 2 - System Supply Line Temperature" (modbus:poller:thermia2:inputregisters) [
    readValueType="int8",
    readStart=12.0,
    // readTransform="JS(modbus_scale100.js)",
    updateUnchangedValuesEveryMillis=1000
]

Thing modbus:data:thermia2:outdoortemperature "Heat Pump 2 - Outdoor Temperature" (modbus:poller:thermia2:inputregisters) [
    readValueType="int16",
    readStart=13,
    readTransform="JS(modbus_scale100.js)",
    updateUnchangedValuesEveryMillis=1000
]

Thing modbus:data:thermia2:tapwatertoptemperature "Heat Pump 2 - Tap Water Top Temperature" (modbus:poller:thermia2:inputregisters) [
    readValueType="int16",
    readStart=15,
    readTransform="JS(modbus_scale100.js)",
    updateUnchangedValuesEveryMillis=1000
]

Thing modbus:data:thermia2:tapwaterlowertemperature "Heat Pump 2 - Tap Water Lower Temperature" (modbus:poller:thermia2:inputregisters) [
    readValueType="int16",
    readStart=16,
    readTransform="JS(modbus_scale100.js)",
    updateUnchangedValuesEveryMillis=1000
]

Thing modbus:data:thermia2:tapwaterweightedtemperature "Heat Pump 2 - Tap Water Weighted Temperature" (modbus:poller:thermia2:inputregisters) [
    readValueType="int16",
    readStart=17,
    readTransform="JS(modbus_scale100.js)",
    updateUnchangedValuesEveryMillis=1000
]

Thing modbus:data:thermia2:systemsupplylinecalculatedsetpoint "Heat Pump 2 - System Supply Line Calculated Set Point" (modbus:poller:thermia2:inputregisters) [
    readValueType="int16",
    readStart=18,
    readTransform="JS(modbus_scale100.js)",
    updateUnchangedValuesEveryMillis=1000
]

Thing modbus:data:thermia2:compressorspeedpercent "Heat Pump 2 - Compressor Speed Percent" (modbus:poller:thermia2:inputregisters) [
    readValueType="int16",
    readStart=54,
    readTransform="JS(modbus_scale100.js)",
    updateUnchangedValuesEveryMillis=1000
]

Thing modbus:data:thermia2:compressorcurrentgear "Heat Pump 2 - Compressor Current Gear" (modbus:poller:thermia2:inputregisters) [
    readValueType="int16",
    readStart=61,
    readTransform="JS(modbus_scale100.js)",
    updateUnchangedValuesEveryMillis=1000
]


Thing modbus:data:thermia2:comfortmode "Heat Pump 2 - Comfort Mode" (modbus:poller:thermia2:inputregisters) [
    readValueType="int16",
    readTransform="MAP(modbus_thermia_comfortMode.map)",
    writeTransform="MAP(modbus_thermia_comfortMode.map)",
    writeType="holding",
    readStart=82,
    updateUnchangedValuesEveryMillis=1000,
    writeValueType="int16",
    writeMultipleEvenWithSingleRegisterOrCoil=false,
    writeMaxTries=3,
    writeStart=82
]

Thing modbus:data:thermia2:heatcurvesystem "Heat Pump 2 - Heat Curve System" (modbus:poller:thermia2:inputregisters) [
    readValueType="int16",
    readTransform="JS(modbus_scale100.js)",
    readStart=19,
    updateUnchangedValuesEveryMillis=1000,
    writeTransform="JS(modbus_scale100.js)",
    writeType="holding",
    writeValueType="int16",
    writeMultipleEvenWithSingleRegisterOrCoil=true,
    writeMaxTries=3,
    writeStart=19
]
Thing modbus:data:thermia2:heatcurveX1 "Heat Pump 2 - Heat Curve X-1" (modbus:poller:thermia2:inputregisters) [
    readValueType="int16",
    readTransform="JS(modbus_scale100.js)",
    readStart=20,
    updateUnchangedValuesEveryMillis=1000
]
Thing modbus:data:thermia2:heatcurveX2 "Heat Pump 2 - Heat Curve X-2" (modbus:poller:thermia2:inputregisters) [
    readValueType="int16",
    readTransform="JS(modbus_scale100.js)",
    readStart=21,
    updateUnchangedValuesEveryMillis=1000
]

Thing modbus:data:thermia2:heatcurveX3 "Heat Pump 2 - Heat Curve X-3" (modbus:poller:thermia2:inputregisters) [
    readValueType="int16",
    readTransform="JS(modbus_scale100.js)",
    readStart=22,
    updateUnchangedValuesEveryMillis=1000
]

Thing modbus:data:thermia2:heatcurveX4 "Heat Pump 2 - Heat Curve X-4" (modbus:poller:thermia2:inputregisters) [
    readValueType="int16",
    readTransform="JS(modbus_scale100.js)",
    readStart=23,
    updateUnchangedValuesEveryMillis=1000
]

Thing modbus:data:thermia2:heatcurveX5 "Heat Pump 2 - Heat Curve X-5" (modbus:poller:thermia2:inputregisters) [
    readValueType="int16",
    readTransform="JS(modbus_scale100.js)",
    readStart=24,
    updateUnchangedValuesEveryMillis=1000
]

Thing modbus:data:thermia2:heatcurveX6 "Heat Pump 2 - Heat Curve X-6" (modbus:poller:thermia2:inputregisters) [
    readValueType="int16",
    readTransform="JS(modbus_scale100.js)",
    readStart=25,
    updateUnchangedValuesEveryMillis=1000
]

Thing modbus:data:thermia2:heatcurveX7 "Heat Pump 2 - Heat Curve X-7" (modbus:poller:thermia2:inputregisters) [
    readValueType="int16",
    readTransform="JS(modbus_scale100.js)",
    readStart=26,
    updateUnchangedValuesEveryMillis=1000
]

Thing modbus:data:thermia2:heatcurveY1 "Heat Pump 2 - Heat Curve Y-1" (modbus:poller:thermia2:holdingregisters) [
    readValueType="int16",
    readTransform="JS(modbus_scale100.js)",
    writeTransform="JS(config:js:5af66aadef)",
    writeType="holding",
    readStart=6,
    updateUnchangedValuesEveryMillis=1000,
    writeValueType="int16",
    writeMultipleEvenWithSingleRegisterOrCoil=true,
    writeMaxTries=3,
    writeStart=6
]
Thing modbus:data:thermia2:heatcurveY2 "Heat Pump 2 - Heat Curve Y-2" (modbus:poller:thermia2:holdingregisters) [
    readValueType="int16",
    readTransform="JS(modbus_scale100.js)",
    writeTransform="JS(config:js:5af66aadef)",
    writeType="holding",
    readStart=7,
    updateUnchangedValuesEveryMillis=1000,
    writeValueType="int16",
    writeMultipleEvenWithSingleRegisterOrCoil=true,
    writeMaxTries=3,
    writeStart=7
]
Thing modbus:data:thermia2:heatcurveY3 "Heat Pump 2 - Heat Curve Y-3" (modbus:poller:thermia2:holdingregisters) [
    readValueType="int16",
    readTransform="JS(modbus_scale100.js)",
    writeTransform="JS(config:js:5af66aadef)",
    writeType="holding",
    readStart=8,
    updateUnchangedValuesEveryMillis=1000,
    writeValueType="int16",
    writeMultipleEvenWithSingleRegisterOrCoil=true,
    writeMaxTries=3,
    writeStart=8
]Thing modbus:data:thermia2:heatcurveY4 "Heat Pump 2 - Heat Curve Y-4" (modbus:poller:thermia2:holdingregisters) [
    readValueType="int16",
    readTransform="JS(modbus_scale100.js)",
    writeTransform="JS(config:js:5af66aadef)",
    writeType="holding",
    readStart=9,
    updateUnchangedValuesEveryMillis=1000,
    writeValueType="int16",
    writeMultipleEvenWithSingleRegisterOrCoil=true,
    writeMaxTries=3,
    writeStart=9
]
Thing modbus:data:thermia2:heatcurveY5 "Heat Pump 2 - Heat Curve Y-5" (modbus:poller:thermia2:holdingregisters) [
    readValueType="int16",
    readTransform="JS(modbus_scale100.js)",
    writeTransform="JS(config:js:5af66aadef)",
    writeType="holding",
    readStart=10,
    updateUnchangedValuesEveryMillis=1000,
    writeValueType="int16",
    writeMultipleEvenWithSingleRegisterOrCoil=true,
    writeMaxTries=3,
    writeStart=10
]
Thing modbus:data:thermia2:heatcurveY6 "Heat Pump 2 - Heat Curve Y-6" (modbus:poller:thermia2:holdingregisters) [
    readValueType="int16",
    readTransform="JS(modbus_scale100.js)",
    writeTransform="JS(config:js:5af66aadef)",
    writeType="holding",
    readStart=11,
    updateUnchangedValuesEveryMillis=1000,
    writeValueType="int16",
    writeMultipleEvenWithSingleRegisterOrCoil=true,
    writeMaxTries=3,
    writeStart=11
]

Thing modbus:data:thermia2:tapwaterstart "Heat Pump 2 - Tap Water Start" (modbus:poller:thermia2:holdingregisters) [
    readValueType="int16",
    readTransform="JS(modbus_scale100.js)",
    writeTransform="JS(config:js:5af66aadef)",
    writeType="holding",
    readStart=22,
    updateUnchangedValuesEveryMillis=1000,
    writeValueType="int16",
    writeMultipleEvenWithSingleRegisterOrCoil=true,
    writeMaxTries=3,
    writeStart=22
]

Thing modbus:data:thermia2:tapwaterstop "Heat Pump 2 - Tap Water Stop" (modbus:poller:thermia2:holdingregisters) [
    readValueType="int16",
    readTransform="JS(modbus_scale100.js)",
    writeTransform="JS(config:js:5af66aadef)",
    writeType="holding",
    readStart=23,
    updateUnchangedValuesEveryMillis=1000,
    writeValueType="int16",
    writeMultipleEvenWithSingleRegisterOrCoil=true,
    writeMaxTries=3,
    writeStart=23
]

Thing modbus:data:thermia2:powerconsumptionmode "Heat Pump 2 - Power Consumption Mode" (modbus:poller:thermia2:holdingregistersb) [
    readValueType="int16",
    readTransform="MAP(modbus_thermia_powerConsumptionMode.map)",
    writeTransform="MAP(modbus_thermia_powerConsumptionMode.map)",
    writeType="holding",
    readStart=124,
    updateUnchangedValuesEveryMillis=1000,
    writeValueType="int16",
    writeMultipleEvenWithSingleRegisterOrCoil=true,
    writeMaxTries=3,
    writeStart=124
]

Thing modbus:data:thermia2:powerconsumptionlimit "Heat Pump 2 - Power Consumption Limit" (modbus:poller:thermia2:holdingregistersb) [
    readValueType="int16",
    readTransform="JS(modbus_scale10.js)",
    writeTransform="JS(modbus_scale10.js)",
    writeType="holding",
    readStart=125,
    updateUnchangedValuesEveryMillis=1000,
    writeValueType="int16",
    writeMultipleEvenWithSingleRegisterOrCoil=true,
    writeMaxTries=3,
    writeStart=125
]


Thing modbus:data:thermia2:enablecurrentlimit "Heat Pump 2 - Enable Current Limit" (modbus:poller:thermia2:coilregisters) [
    writeType="coil",
    readStart=26,
    updateUnchangedValuesEveryMillis=1000,
    writeMultipleEvenWithSingleRegisterOrCoil=true,
    writeMaxTries=3,
    writeStart=26
]

Thing modbus:data:theria2:enableimmersionheater "Heat Pump 2 - Enable Immersion Heater" (modbus:poller:thermia2:holdingregistersc) [
    readValueType="int16",
    writeType="holding",
    // readTransform="MAP(modbus_thermia_enableImmersion.map)",
    // writeTransform="MAP(modbus_thermia_enableImmersion.map)",
    readStart=321,
    updateUnchangedValuesEveryMillis=1000,
    writeValueType="int16",
    writeMultipleEvenWithSingleRegisterOrCoil=true,
    writeMaxTries=3,
    writeStart=321
]

And this is my HRV. 500+ lines

// Modbus table
// https://blaubergventilatoren.de/uploads/download/b55_8_1en_01preview.pdf

Bridge modbus:tcp:hrv "HRV - Modbus Slave"  [ 
    rtuEncoded=false,
    timeBetweenTransactionsMillis=500,
    connectMaxTries=3,
    reconnectAfterMillis=180000,
    port=502,
    timeBetweenReconnectMillis=250,
    host="10.0.5.14",
    connectTimeoutMillis=10000,
    afterConnectionDelayMillis=250,
    id=1,
    enableDiscovery=false
]

// POLLERS
Thing modbus:poller:hrv:coilregisters "HRV - Regular Poll Coil" (modbus:tcp:hrv) [
    length=24,
    start=0,
    refresh=2000,
    maxTries=3,
    cacheMillis=50,
    type="coil"
]

Thing modbus:poller:hrv:discreteregisters "HRV - Regular Poll Discrete" (modbus:tcp:hrv) [
    length=71,
    start=0,
    refresh=2000,
    maxTries=3,
    cacheMillis=50,
    type="discrete"
]


Thing modbus:poller:hrv:holdingregisters "HRV - Regular Poll Holding" (modbus:tcp:hrv) [
    length=100,
    start=0,
    refresh=2000,
    maxTries=3,
    cacheMillis=50,
    type="holding"
]

Thing modbus:poller:hrv:inputregisters1 "HRV - Regular Poll Input 1" (modbus:tcp:hrv) [
    length=100,
    start=0,
    refresh=2000,
    maxTries=3,
    cacheMillis=50,
    type="input"
]

Thing modbus:poller:hrv:inputregisters2 "HRV - Regular Poll Input 2" (modbus:tcp:hrv) [
    length=86,
    start=100,
    refresh=2000,
    maxTries=3,
    cacheMillis=50,
    type="input"
]


// CHANNELS

Thing modbus:data:hrv:onoff "HRV - On/Off" (modbus:poller:hrv:coilregisters) [
    readValueType="bit",
    readTransform="default",
    readStart=0,
    updateUnchangedValuesEveryMillis=1000,
    writeTransform="default",
    writeType="coil",
    writeValueType="bit",
    writeMultipleEvenWithSingleRegisterOrCoil=false,
    writeMaxTries=3,
    writeStart=0
]



Thing modbus:data:hrv:boost "HRV - Boost" (modbus:poller:hrv:coilregisters) [
    readValueType="bit",
    readTransform="default",
    readStart=13,
    updateUnchangedValuesEveryMillis=1000,
    writeTransform="default",
    writeType="coil",
    writeValueType="bit",
    writeMultipleEvenWithSingleRegisterOrCoil=false,
    writeMaxTries=3,
    writeStart=13
]

Thing modbus:data:hrv:fireplacemode "HRV - Fireplace Mode" (modbus:poller:hrv:coilregisters) [
    readValueType="bit",
    readTransform="default",
    readStart=14,
    updateUnchangedValuesEveryMillis=1000,
    writeTransform="default",
    writeType="coil",
    writeValueType="bit",
    writeMultipleEvenWithSingleRegisterOrCoil=false,
    writeMaxTries=3,
    writeStart=14
]

Thing modbus:data:hrv:resetalarms "HRV - Reset Alarms" (modbus:poller:hrv:coilregisters) [
    writeTransform="default",
    writeType="coil",
    writeValueType="bit",
    writeMultipleEvenWithSingleRegisterOrCoil=false,
    writeMaxTries=3,
    writeStart=18
]


Thing modbus:data:hrv:speed "HRV - Speed" (modbus:poller:hrv:holdingregisters) [
    readValueType="int16",
    readTransform="default",
    readStart=2,
    updateUnchangedValuesEveryMillis=1000,
    writeTransform="default",
    writeType="holding",
    writeValueType="int16",
    writeMultipleEvenWithSingleRegisterOrCoil=false,
    writeMaxTries=3,
    writeStart=2
]

Thing modbus:data:hrv:manualspeed "HRV - Manual Speed" (modbus:poller:hrv:holdingregisters) [
    readValueType="int16",
    readTransform="default",
    readStart=17,
    updateUnchangedValuesEveryMillis=1000,
    writeTransform="default",
    writeType="holding",
    writeValueType="int16",
    writeMultipleEvenWithSingleRegisterOrCoil=false,
    writeMaxTries=3,
    writeStart=17
]

Thing modbus:data:hrv:supplyfanspeed1 "HRV - Supply Fan Speed 1" (modbus:poller:hrv:holdingregisters) [
    readValueType="int16",
    readTransform="default",
    readStart=7,
    updateUnchangedValuesEveryMillis=1000,
    writeTransform="default",
    writeType="holding",
    writeValueType="int16",
    writeMultipleEvenWithSingleRegisterOrCoil=false,
    writeMaxTries=3,
    writeStart=17
]

Thing modbus:data:hrv:extractfanspeed1 "HRV - Extract Fan Speed 1" (modbus:poller:hrv:holdingregisters) [
    readValueType="int16",
    readTransform="default",
    readStart=8,
    updateUnchangedValuesEveryMillis=1000,
    writeTransform="default",
    writeType="holding",
    writeValueType="int16",
    writeMultipleEvenWithSingleRegisterOrCoil=false,
    writeMaxTries=3,
    writeStart=8
]

Thing modbus:data:hrv:supplyfanspeed2 "HRV - Supply Fan Speed 2" (modbus:poller:hrv:holdingregisters) [
    readValueType="int16",
    readTransform="default",
    readStart=9,
    updateUnchangedValuesEveryMillis=1000,
    writeTransform="default",
    writeType="holding",
    writeValueType="int16",
    writeMultipleEvenWithSingleRegisterOrCoil=false,
    writeMaxTries=3,
    writeStart=9
]

Thing modbus:data:hrv:extractfanspeed2 "HRV - Extract Fan Speed 2" (modbus:poller:hrv:holdingregisters) [
    readValueType="int16",
    readTransform="default",
    readStart=10,
    updateUnchangedValuesEveryMillis=1000,
    writeTransform="default",
    writeType="holding",
    writeValueType="int16",
    writeMultipleEvenWithSingleRegisterOrCoil=false,
    writeMaxTries=3,
    writeStart=10
]

Thing modbus:data:hrv:supplyfanspeed3 "HRV - Supply Fan Speed 3" (modbus:poller:hrv:holdingregisters) [
    readValueType="int16",
    readTransform="default",
    readStart=11,
    updateUnchangedValuesEveryMillis=1000,
    writeTransform="default",
    writeType="holding",
    writeValueType="int16",
    writeMultipleEvenWithSingleRegisterOrCoil=false,
    writeMaxTries=3,
    writeStart=11
]

Thing modbus:data:hrv:extractfanspeed3 "HRV - Extract Fan Speed 3" (modbus:poller:hrv:holdingregisters) [
    readValueType="int16",
    readTransform="default",
    readStart=12,
    updateUnchangedValuesEveryMillis=1000,
    writeTransform="default",
    writeType="holding",
    writeValueType="int16",
    writeMultipleEvenWithSingleRegisterOrCoil=false,
    writeMaxTries=3,
    writeStart=12
]

Thing modbus:data:hrv:supplyintemperature "HRV - Supply In Temperature" (modbus:poller:hrv:inputregisters1) [
    readValueType="int16",
    readTransform="default",
    readStart=1,
    updateUnchangedValuesEveryMillis=1000
]
Thing modbus:data:hrv:supplyouttemperature "HRV - Supply Out Temperature" (modbus:poller:hrv:inputregisters1) [
    readValueType="int16",
    readTransform="default",
    readStart=2,
    updateUnchangedValuesEveryMillis=1000
]

Thing modbus:data:hrv:extractintemperature "HRV - Extract In Temperature" (modbus:poller:hrv:inputregisters1) [
    readValueType="int16",
    readTransform="default",
    readStart=3,
    updateUnchangedValuesEveryMillis=1000
]

Thing modbus:data:hrv:extractouttemperature "HRV - Extract Out Temperature" (modbus:poller:hrv:inputregisters1) [
    readValueType="int16",
    readTransform="default",
    readStart=4,
    updateUnchangedValuesEveryMillis=1000
]

Thing modbus:data:hrv:totalworkingtime "HRV - Total Working Time" (modbus:poller:hrv:inputregisters1) [
    readValueType="int16",
    readTransform="default",
    readStart=29,
    updateUnchangedValuesEveryMillis=1000
]


// Thing modbus:data:hrv:humidity "HRV - Humidity" (modbus:poller:hrv:inputregisters1) [
//     readValueType="int16",
//     readTransform="default",
//     readStart=12,
//     updateUnchangedValuesEveryMillis=1000
// ]

Thing modbus:data:hrv:alarm "HRV - Alarm/warning indicator" (modbus:poller:hrv:inputregisters1) [
    readValueType="int16",
    readTransform="default",
    readStart=38,
    updateUnchangedValuesEveryMillis=1000
]

Thing modbus:data:hrv:alarm0 "HRV - Alarm with code 0" (modbus:poller:hrv:discreteregisters) [
    
    readTransform="default",
    readStart=19,
    updateUnchangedValuesEveryMillis=1000
]

Thing modbus:data:hrv:alarm1 "HRV - Alarm with code 1" (modbus:poller:hrv:discreteregisters) [
    
    readTransform="default",
    readStart=20,
    updateUnchangedValuesEveryMillis=1000
]
Thing modbus:data:hrv:alarm2 "HRV - Alarm with code 2" (modbus:poller:hrv:discreteregisters) [
    
    readTransform="default",
    readStart=21,
    updateUnchangedValuesEveryMillis=1000
]
Thing modbus:data:hrv:alarm3 "HRV - Alarm with code 3" (modbus:poller:hrv:discreteregisters) [
    
    readTransform="default",
    readStart=22,
    updateUnchangedValuesEveryMillis=1000
]
Thing modbus:data:hrv:alarm4 "HRV - Alarm with code 4" (modbus:poller:hrv:discreteregisters) [
    
    readTransform="default",
    readStart=23,
    updateUnchangedValuesEveryMillis=1000
]
Thing modbus:data:hrv:alarm5 "HRV - Alarm with code 5" (modbus:poller:hrv:discreteregisters) [
    
    readTransform="default",
    readStart=24,
    updateUnchangedValuesEveryMillis=1000
]
Thing modbus:data:hrv:alarm6 "HRV - Alarm with code 6" (modbus:poller:hrv:discreteregisters) [
    
    readTransform="default",
    readStart=25,
    updateUnchangedValuesEveryMillis=1000
]
Thing modbus:data:hrv:alarm7 "HRV - Alarm with code 7" (modbus:poller:hrv:discreteregisters) [
    
    readTransform="default",
    readStart=26,
    updateUnchangedValuesEveryMillis=1000
]
Thing modbus:data:hrv:alarm8 "HRV - Alarm with code 8" (modbus:poller:hrv:discreteregisters) [
    
    readTransform="default",
    readStart=27,
    updateUnchangedValuesEveryMillis=1000
]
Thing modbus:data:hrv:alarm9 "HRV - Alarm with code 9" (modbus:poller:hrv:discreteregisters) [
    
    readTransform="default",
    readStart=28,
    updateUnchangedValuesEveryMillis=1000
]
Thing modbus:data:hrv:alarm10 "HRV - Alarm with code 10" (modbus:poller:hrv:discreteregisters) [
    
    readTransform="default",
    readStart=29,
    updateUnchangedValuesEveryMillis=1000
]
Thing modbus:data:hrv:alarm11 "HRV - Alarm with code 11" (modbus:poller:hrv:discreteregisters) [
    
    readTransform="default",
    readStart=30,
    updateUnchangedValuesEveryMillis=1000
]
Thing modbus:data:hrv:alarm12 "HRV - Alarm with code 12" (modbus:poller:hrv:discreteregisters) [
    
    readTransform="default",
    readStart=31,
    updateUnchangedValuesEveryMillis=1000
]
Thing modbus:data:hrv:alarm13 "HRV - Alarm with code 13" (modbus:poller:hrv:discreteregisters) [
    
    readTransform="default",
    readStart=32,
    updateUnchangedValuesEveryMillis=1000
]
Thing modbus:data:hrv:alarm14 "HRV - Alarm with code 14" (modbus:poller:hrv:discreteregisters) [
    
    readTransform="default",
    readStart=33,
    updateUnchangedValuesEveryMillis=1000
]
Thing modbus:data:hrv:alarm15 "HRV - Alarm with code 15" (modbus:poller:hrv:discreteregisters) [
    
    readTransform="default",
    readStart=34,
    updateUnchangedValuesEveryMillis=1000
]
Thing modbus:data:hrv:alarm16 "HRV - Alarm with code 16" (modbus:poller:hrv:discreteregisters) [
    
    readTransform="default",
    readStart=35,
    updateUnchangedValuesEveryMillis=1000
]
Thing modbus:data:hrv:alarm17 "HRV - Alarm with code 17" (modbus:poller:hrv:discreteregisters) [
    
    readTransform="default",
    readStart=36,
    updateUnchangedValuesEveryMillis=1000
]
Thing modbus:data:hrv:alarm18 "HRV - Alarm with code 18" (modbus:poller:hrv:discreteregisters) [
    
    readTransform="default",
    readStart=37,
    updateUnchangedValuesEveryMillis=1000
]
Thing modbus:data:hrv:alarm19 "HRV - Alarm with code 19" (modbus:poller:hrv:discreteregisters) [
    
    readTransform="default",
    readStart=38,
    updateUnchangedValuesEveryMillis=1000
]
Thing modbus:data:hrv:alarm20 "HRV - Alarm with code 20" (modbus:poller:hrv:discreteregisters) [
    
    readTransform="default",
    readStart=39,
    updateUnchangedValuesEveryMillis=1000
]
Thing modbus:data:hrv:alarm21 "HRV - Alarm with code 21" (modbus:poller:hrv:discreteregisters) [
    
    readTransform="default",
    readStart=40,
    updateUnchangedValuesEveryMillis=1000
]
Thing modbus:data:hrv:alarm22 "HRV - Alarm with code 22" (modbus:poller:hrv:discreteregisters) [
    
    readTransform="default",
    readStart=41,
    updateUnchangedValuesEveryMillis=1000
]
Thing modbus:data:hrv:alarm23 "HRV - Alarm with code 23" (modbus:poller:hrv:discreteregisters) [
    
    readTransform="default",
    readStart=42,
    updateUnchangedValuesEveryMillis=1000
]
Thing modbus:data:hrv:alarm24 "HRV - Alarm with code 24" (modbus:poller:hrv:discreteregisters) [
    
    readTransform="default",
    readStart=43,
    updateUnchangedValuesEveryMillis=1000
]
Thing modbus:data:hrv:alarm25 "HRV - Alarm with code 25" (modbus:poller:hrv:discreteregisters) [
    
    readTransform="default",
    readStart=44,
    updateUnchangedValuesEveryMillis=1000
]
Thing modbus:data:hrv:alarm26 "HRV - Alarm with code 26" (modbus:poller:hrv:discreteregisters) [
    
    readTransform="default",
    readStart=45,
    updateUnchangedValuesEveryMillis=1000
]
Thing modbus:data:hrv:alarm27 "HRV - Alarm with code 27" (modbus:poller:hrv:discreteregisters) [
    
    readTransform="default",
    readStart=46,
    updateUnchangedValuesEveryMillis=1000
]
Thing modbus:data:hrv:alarm28 "HRV - Alarm with code 28" (modbus:poller:hrv:discreteregisters) [
    
    readTransform="default",
    readStart=47,
    updateUnchangedValuesEveryMillis=1000
]
Thing modbus:data:hrv:alarm29 "HRV - Alarm with code 29" (modbus:poller:hrv:discreteregisters) [
    
    readTransform="default",
    readStart=48,
    updateUnchangedValuesEveryMillis=1000
]
Thing modbus:data:hrv:alarm30 "HRV - Alarm with code 30" (modbus:poller:hrv:discreteregisters) [
    
    readTransform="default",
    readStart=49,
    updateUnchangedValuesEveryMillis=1000
]
Thing modbus:data:hrv:alarm31 "HRV - Alarm with code 31" (modbus:poller:hrv:discreteregisters) [
    
    readTransform="default",
    readStart=50,
    updateUnchangedValuesEveryMillis=1000
]
Thing modbus:data:hrv:alarm32 "HRV - Alarm with code 32" (modbus:poller:hrv:discreteregisters) [
    
    readTransform="default",
    readStart=51,
    updateUnchangedValuesEveryMillis=1000
]
Thing modbus:data:hrv:alarm33 "HRV - Alarm with code 33" (modbus:poller:hrv:discreteregisters) [
    
    readTransform="default",
    readStart=52,
    updateUnchangedValuesEveryMillis=1000
]
Thing modbus:data:hrv:alarm34 "HRV - Alarm with code 34" (modbus:poller:hrv:discreteregisters) [
    
    readTransform="default",
    readStart=53,
    updateUnchangedValuesEveryMillis=1000
]
Thing modbus:data:hrv:alarm35 "HRV - Alarm with code 35" (modbus:poller:hrv:discreteregisters) [
    
    readTransform="default",
    readStart=54,
    updateUnchangedValuesEveryMillis=1000
]
Thing modbus:data:hrv:alarm36 "HRV - Alarm with code 36" (modbus:poller:hrv:discreteregisters) [
    
    readTransform="default",
    readStart=55,
    updateUnchangedValuesEveryMillis=1000
]
Thing modbus:data:hrv:alarm37 "HRV - Alarm with code 37" (modbus:poller:hrv:discreteregisters) [
    
    readTransform="default",
    readStart=56,
    updateUnchangedValuesEveryMillis=1000
]
Thing modbus:data:hrv:alarm38 "HRV - Alarm with code 38" (modbus:poller:hrv:discreteregisters) [
    
    readTransform="default",
    readStart=57,
    updateUnchangedValuesEveryMillis=1000
]
Thing modbus:data:hrv:alarm39 "HRV - Alarm with code 39" (modbus:poller:hrv:discreteregisters) [
    
    readTransform="default",
    readStart=58,
    updateUnchangedValuesEveryMillis=1000
]
Thing modbus:data:hrv:alarm40 "HRV - Alarm with code 40" (modbus:poller:hrv:discreteregisters) [
    
    readTransform="default",
    readStart=59,
    updateUnchangedValuesEveryMillis=1000
]
Thing modbus:data:hrv:alarm41 "HRV - Alarm with code 41" (modbus:poller:hrv:discreteregisters) [
    
    readTransform="default",
    readStart=60,
    updateUnchangedValuesEveryMillis=1000
]
Thing modbus:data:hrv:alarm42 "HRV - Alarm with code 42" (modbus:poller:hrv:discreteregisters) [
    
    readTransform="default",
    readStart=61,
    updateUnchangedValuesEveryMillis=1000
]




// Thing modbus:data:hrv:motorhours "HRV - Motor Hours" (modbus:poller:hrv:holdingregisters) [
//     readValueType="bit",
//     readTransform="default",
//     readStart=2,
//     updateUnchangedValuesEveryMillis=1000,
//     writeTransform="default",
//     writeType="holding",
//     writeValueType="bit",
//     writeMultipleEvenWithSingleRegisterOrCoil=false,
//     writeMaxTries=3,
//     writeStart=2
// ]

Yes, this could be an option alright. But I’d be reluctant to put another layer of translation in. Seems like just another thing to go wrong. And I don’t have something already in place like you use NodeRed, so i’d have to choose and create and manage something new.

I would assume we could follow one of the existing modbus bundles like a template, like the sunspec one.

Here are some of the modbus docs I use

SolarEdge

Thermia

Thermia sent me a newer on for V16 which has a few features not listed here, but I can’t find an online version.

Blaugerg

Do you think that the “data” Things could become channels of the poller thing instead of the poller thing acting as a Bridge?

@ssalonen is the code owner, he might be able to provide some insights in the design of the binding.

1 Like

I also think that it would be nice to rather define channels for a thing similar to the generic mqtt thing.

I only have my inverter in my current setup with a rather small subset of registers (only the ones I really need to control it). Still I have 22 modbus things in my installation (mainly modbus:data for registers but also 6 pollers just because registers are so far apart).

I think that the granularity of the thing definition does not match the majority of the other bindings that I use. At least modbus:data should definitely be a channel and not a thing from my point of view.

The best approach from a users point of view would be to create pollers completely behind the scenes by configuring polling settings per register instead. This would allow getting rid of pollers altogether so that I could only have my inverter thing with individual channels for every required register. I think this should be doable but I also understand that this is not trivial.

Do you think that the “data” Things could become channels of the poller thing instead of the poller thing acting as a Bridge?

@ssalonen is the code owner, he might be able to provide some insights in the design of the binding.

I explored this in some time ago in my backwards-incompatible thing hierarchy:

README shows few examples how the configuration would look like openhab2-addons/bundles/org.openhab.binding.modbus at modbus-14058 · ssalonen/openhab2-addons · GitHub

(the branch is relatively feature complete, and even the tests were ported to a degree)

The thinking was to have separate channels in order to easily parse the data, to make the binding more easy to use. For example, turning ON/OFF commands into 0/1 numbers in modbus.

In the end, the design still was quite odd, and did not match any other binding, see notes in the issue.

In addition, the Web UI (at least the time) was very much making it hard to build a “generic” binding like modbus, since the UI is enforcing channel type to match the item type [1]. This leads to explosion of channels as you have to duplicate the same channel for each item type (example: write coil from ON/OFF with Color item, write coil from ON/OFF with Dimmer item, write coil from ON/OFF with Switch item). See comments in the issue.

Later I have also learned that with 4.x a openhab-core change lead to a situation where a non-UoM Number channel is somewhat challenging for user to use with UoM items, see comment Modbus thermostat : stepper don't work ( - #20 by ssalonen . The bidirectional unit conversion/scaling profile provided by the modbus binding (gain-offset) broke as a result in 4.x, and has been broken since. In other words, openhab-core discourages use of plain Number channels with UoM channels. Unfortunately, in a generic binding like modbus, I don’t find it too nice to have channels for each QuantityType (consider e.g. writeRegisterFromNumber channel permutated for Power, Energy, …)

All this above lead to a situation where the “simplified” thing structure was not actually simple anymore. There were too many channels and “odd” design choices remained. Overall I ran out of energy, as I noticed the changes are quite invasive, and the design is not so obvious for the user.

As a summary, I felt that the openhab core and WebUI is making it very hard to build a binding like this one where we have low level data (essentially collection bits stuffed into registers), and want to facilitate transformation typical encodings of data (e.g. 0/1 to ON/OFF, or 0…255 into percentage, or 0…1000 into 0…100 Celsius) into openHAB items. Vast majority of bindings are “specific”, and can provide the data in processed format, such as “room temperature in celsius” etc - obviously this is not the case with modbus.

It is likely that I might be missing some key design angle here, but this is as far as I could get.

I guess alternative would have been to provide the raw data as hex or numbers, but leave even the simplest of data conversions for the user to figure out. Even then, due to the WebUI issues, we would have to have multiple channels for the items types and possibly for different quantity types.

The best approach from a users point of view would be to create pollers completely behind the scenes by configuring polling settings per register instead. This would allow getting rid of pollers altogether so that I could only have my inverter thing with individual channels for every required register. I think this should be doable but I also understand that this is not trivial.

This is has come up sometimes in the forum. Respectfully, I do not think this works very well in the generic binding, as

  • some devices refuse to answer to read queries unless the registers is set just right. For example, reading 2 registers is allowed but not reading them in pieces.
  • we have many users with high polling frequency and flooding the modbus server with requests is unlikely to work in practice (we have had many cases where the server refuses to respond to queries).
  • Finally, sometimes registers are connected (e.g. 32 bit number spread into two registers), and it’s important to read them together as one to get the right value out.

Many of these points have come up in the forum in one way or another over the years.

So the my preference with a low level protocol as modbus has been to enable maximum flexibility, as there is no strict compliance from the devices point of view, and in order to stay efficient.

For certain use cases (longer poll interval, not so many registers, server more lenient on the way the registers are called) I do agree it would make the configuration easier for the user. We could even offer this kind of “auto mode” as an alternative.

1 Like

It would be valid to anyone using the same equipment. If I and other people could spend the time to do the boring data input, it would create a library of compatible devices without the tricky setup from scratch each time.

This is exactly what the modbus extensions are for. At this moment, those bundles need to be coded in java - there is no general purpose config format to define equipment if that is what you are asking.

In a way, they are built “on top of” the generic modbus functionality, and allowing the extension developer to focus on parsing and processing the binary data from modbus data to openHAB channels that represent the physical device.

image

‘extentions’. Ive been using the wrong word ‘bundle’. This is exactly what I’m talking about.
I’d be happy to do the boring data entry if someone else will guide me and stick it all together

How about a tool that creates an extension? That way, the existing binding need not change at all?

Thank you for elaborating. I can partly understand what you are talking about and I guess you are right and I’m pretty sure that you are also right with the part I did not understand. I was primarily looking at it from a users perspective but I understand that there are just too many ways to break this or that for a certain device type or user…

I agree that the modbus extensions are probably the best approach, a similar approach is also used with the mqtt binding. However while in general I would be willing to contribute such an extension it would be a lot more work than simply creating things (especially since mine are already there :smiling_face:). Therefore I would only contribute this if I knew that I can help somebody in the community. Maybe a poll for interested users would be good to see which devices are used by the community members?! I have an Alpha ESS inverter btw if someone would be interested :wink:

1 Like

I’ll do any data entry donkey work for your Alpha ESS, if you show me how to do the job, so that I can also do my Thermia Heat Pump and Blauberg HRV extension that I’d be happy to share with the community

In my opinion it’s difficult to divide labour here because we have to deal with such a low level interface. Even though the code of the existing extensions does not seem to be very complex and we could reuse quite a few bits and pieces here and there it still seems that there is so much variety in how manufacturers use registers to represent information that it seems to be hard to come up with an easy to understand universal descriptive file format to generate these kinds of extensions. The decoupling of the code and format descriptions would be a prerequisite though for an efficient division of labour here I guess.

EDIT: And in the case of Alpha ESS I already realised that the functionality that they offer in the app can only partly be realized by setting a few registers. For example: the app allows to set different time slots to charge the battery from the grid and allows to specify a different target percentage for every time slot. However there is only one single register in which the desired charge percentage can be written. So I assume that most part of the logic happens in the cloud (adjusting the desired charge level when a time slot starts), so one would also need to replicate this functionality if required. This cannot be solved by purely describing a data mapping.

1 Like