Cannot figure out how to `sendCommand` to UoM item linked to a modbus thingy with a gainOffset

  • Platform information:
    • openHAB version: 4.1.0

I’m trying to set up a rule that would average the room temperature drawn from a few items and write it back to another device that speaks modbus. Here’s the item for the modbus item that I’m writing to:

Number:Temperature RAT_Manual_Override "RAT Manual Override" (Recuperator) ["Control"] {
    unit = "°C",
    channel = "modbus:data:recuperator:manual_override:rat_value:number"[ profile="modbus:gainOffset", gain="0.1°C" ]
}

NB: for now note that the °C unit in gain is required. Otherwise when the modbus binding reads out the register it fails to update the Item state with warnings along the line of

19:21:00.601 [WARN ] [openhab.core.library.items.NumberItem] - Failed to update item 'RAT_Manual_Override' because '21.7' could not be converted to the item unit '°C'

Then I wrote the following rule to achieve my end goal:

rule "Maintain Room Temperature value"
when
    Item Temperature_BossRoom changed or
    Item Temperature_GuestRoom changed or
    Item Temperature_KidsRoom changed or
    Item Temperature_LivingRoom changed
then
    val temps = newArrayList(
        Temperature_BossRoom,
        Temperature_GuestRoom,
        Temperature_KidsRoom,
        Temperature_LivingRoom
    );
    var QuantityType<KELVIN> sum;
    for (i: temps) {
        val QuantityType<KELVIN> v = (i.state as QuantityType<KELVIN>).toUnit("K");
        sum += v;
    }
    val avg = sum / temps.size();
    RAT_Manual_Override.sendCommand(avg.toUnit(RAT_Manual_Override.getUnit))
    RAT_Manual_Override.sendCommand(REFRESH)
end

Quick summary: It grabs temperature from 4 sensors in kelvin, computes the average and then calls sendCommand with the average converted back to Celsius.

Except it does not work, this time with the following error:

19:21:21.900 [WARN ] [rnal.profiles.ModbusGainOffsetProfile] - Cannot apply gain ('0.1 °C') and pre-gain-offset ('0') to state ('21.796875') (formula '21.796875'/'0.1 °C' - '0') because types do not match (towardsItem=false): Cannot process command '21.796875', unit should compatible with gain

I also tried changing the code to sendCommand in one of these other ways among others:

    RAT_Manual_Override.sendCommand(avg.toUnit("°C")) // same
    RAT_Manual_Override.sendCommand(avg.toUnit("°C^2")) // idea: unit="°C" only strips one of the celcius, leaving the second one for gainOffset ==> no updates at all, I imagine the conversion probably fails and returns a null? 
    RAT_Manual_Override.sendCommand(21.7) // no work

From my experiments it appears like the unit = "°C" in the item definition is stripping the unit off before it gets passed along to the modbus:gainOffset mechanism. However gainOffset also expects the unit to be present still.

So far the only thing that has worked at all is to remove all units altogether, but that’s not a solution I am keen on. Is there a way to get this to work while keeping the units?

First of all, your rule is a little overly complex. Create a Group:Number:Temperature:AVG, add the states of the five Items to the Group and the state of the Group will be the average of them. Then your rule just needs to trigger when the Group Item changes and to command that to Override Item.

The whole point of UoM is that you should not have to convert back and forth between compatible units. Temperature is a little weird because 0 °C, 0 °F and K do not line up so strange result can occur if you are not careful. But in this case there shouldn’t be a problem, at least in the rule.

Sending a REFRESH command immediately after sending another command is likely to not work as expected. I don’t think that there is a guarantee that commands are processed in order and since the first command needs to go through a profile the REFRESH might get processed first.

And the REFRESH really isn’t need unless: 1. Autoupdate is not enabled and 2. modbus doesn’t report the new state after receiving the command on it’s own.

According to the docs, you must provide the gain and pre-gain offset to the profile:

Profile has two parameters, gain (bare number or number with unit) and pre-gain-offset (bare number), both of which must be provided.

With or without the unit I’m not sure this calculation makes sense. Let’s say the state and the gain have units:

21.8 °C / 0.1 °C - 0 = 218

Note that the units cancel each other out.

21.8 / 0.1 °C - 0 = 218 per °C 

Is per °C even a thing? meters per °C?

Because of this I’m not sure the Gain profile is usable for Temperature. But you can use a JS transformation profile instead. In JS and jRuby it can even be inline scripts, you don’t even need to define them separately.

channel to item:

JS: | Quantity(Math.parseFloat(input) * 0.1, "°C") 

item to channel:

JS: | (Quantity(input).divide(0.1)).number

Note that to do addition or subtraction both operands must have units for Temperature. However, to multiply or divide, only one of the operand should have units. The gain offset profile makes this impossible because, according to the docs quoted above, the pre-gain-offset must be a bare number and that’s the one that’s added or subtracted from the value with the unit.

Thank you for taking time to respond!

First thing that matters here is that modbus rarely deals with floats or doubles. It does sometimes, but in my case the situation follows the common case and the underlying data type is an integer. If I send a 12.345 to the item, the actual value that should get written is 123, and unless I manually refresh the item I don’t get 12.3 back – item state stays wrong for a little while until poll period lapses.

(Another thing is that registers aren’t always idempotent – it isn’t required for a register to read back to the same value as what was written. Poor design, sure, but most devices aren’t made with an expectation that a curious person will be probing around its insides T_T)

Providing the pre-gain-offset changes absolutely nothing, unfortunately. It also would seem that the wording in the documentation is stronger than the implementation bothers to enforce (and the offset gets defaulted to 0.)

This part makes perfect sense to me. modbus does not deal with units, and 218 is exactly the value I would expect to be written into a register. But this is not happening in the first place. In practice the dividend is already coming in unitless, no matter what I specify inside my rule code, so…

I agree that this equation is non-sensical. But it is not my intent to compute this. I would be more than happy if somehow it was the first equation that was executed.

The need to add an unit to the gain also makes a ton of sense on “read from modbus” direction. If modbus reads out a 219, then the computation goes like (0 + 219) * 0.1 °C = 21.9 °C. In fact, unless something with °C comes out of modbus, then OH will refuse to stomach the value at all and will complain with a warning I posted above, making it required:


In my mind everything would actually fit together just fine if only the value that was supplied to gainOffset in the write direction did not have its unit stripped off. Is it really expected for things to be that way? If so, then gainOffset is not only useless for temperatures – it is actually useless for anything involving UoM and write operations. It is a shame though, because scaling like that is pervasively common in modbus and having to write the javascript transformations for every single Item is going to be a major chore (this recuperator I’m dealing with has already required me to define, like, hundreds of them; perhaps writing a proper binding would make more sense at this point, but it really does not yield itself to incremental changes as well, and I dread the idea of trying to write java in a headless environment).

The Item state should always be 12.345. That’s what profiles and transformations are for. Device specific stuff like this shouldn’t be accounted for at the Item level. It should be sorted before that.

But my main point is I don’t think there REFRESH command is even going to work. None of what you describe will happen if the REFRESH happens first.

But the modbus binding does it’s own processing after the calculation to handle stripping off the unit. You’ve stripping off the unit before that and therefore it’s not there when the binding expects it to be there.

But this is expressly the calculation the gain profile performs. If you use the gain profile, this is the calculation that happens.

If you use a plain Number Item, you don’t need to use units at all. Units are not required.

I’m skeptical that that is what’s actually happening because if that were the case it wouldn’t work for any units, not just fall for temperatures. And if that were the case I’d expect there to be open issues and lots of posts on the forum. Despite what the log says, I don’t see how it can work at all without the unit.

JS transforms support passing arguments… You can write it once and call it with different gains and offsets making it exactly the same as the gain offset profile once the transforms are written.

See JavaScript Scripting - Automation | openHAB

Just to make sure I understand. Are you saying that the algorithm on the write direction is roughly the following:

  1. OH/modbus binding executes the gainOffset profile with the provided value;
    • And that this is here I make a mistake of stripping the unit before…
  2. OH/modbus binding then takes the result of that value and expects it to have UoM that matches the unit property of the Item so that it can strip the unit off?

The warning message from the binding (grep for “Cannot apply” in the first post) definitely suggests that the unit is already gone by the time step 1 above gets reached and I have a hard time seeing where else could I be making a mistake of stripping the unit prematurely.

That’s true, and this is the work-around I use currently. However it has other implications, such as for example the “control” widget choosing a worse method of presentation in the MainUI. I imagine there might be a way to convince MainUI to use the desired widget, but that’d be piling more workarounds on top of what is already a workaround.

So what I’m reading here is that I need a more minimal reproducer, potentially also one with a UoM that’s not temperature (and probably also not one)… I guess it would definitely help my case if I had a smoking gun to show :slight_smile:

That’s right, but using transformations require to describe them in both the toItem direction and the toHandler direction. Arguments or not, that implies the duplication of the scale factors, etc. which makes the already barely maintainable blob of items all that much less mainainable.

That warning could be coming from the 0 offset. The docs indicate that that offset cannot have a unit and you cannot do an addition with temperatures without both operands having a unit.