[SOLVED] Help me understand: Controlling Setpoints on one device with another one by linking items to multiple channels

Dear community,

after a few weeks of reading, testing and setting up I think it might be time for my first question to you.

First of all, I’m interested in how OpenHAB is intended to be used to tackle my problem in the first place. As soon as I understand this I might post a second thread going into detail about the physical things, bindings and so on that I want to use.

If there is any beginners guide that ansers my question - feel free to drop the link here - I prefere not to wasste anyone’s time.

So the setup is as follows:

  • I have one room sensor that can provide temperature measurement and a temperature setpoint as well.
  • Further I have two radiator control valves that can receive a setpoint as well as report when the manually changed setpoint on the device will change

Now my question: If I want the setpoint on all devices to be in sync and want to be able to change the setpoint on every one device with the others to follow them when changed - how should this be modeled in openHAB?

After I read a lot of documentation I came up with the following guess

--Control (Item)
  |
  +--Control_measure_temp  (Item)
  |
  +--Control_setpoint_temp (Item)
     +Channel_Link_ThingValve1_setpoint_temp (Link)
     +Channel_Link_ThingValve2_setpoint_temp (Link)
     +Channel_Link_ThingControl_setpoint_temp (Link)
     
--Valve 1 (Item)
  |
  +--Valve1_setpoint_temp (Item)
     |
     +Channel_Link_ThingValve1_setpoint_temp (Link)
     +Channel_Link_ThingValve2_setpoint_temp (Link)
     +Channel_Link_ThingControl_setpoint_temp (Link)

--Valve 2 (Item)
  |
  +--Valve2_setpoint_temp  (Item)
     |
     +Channel_Link_ThingValve1_setpoint_temp (Link)
     +Channel_Link_ThingValve2_setpoint_temp (Link)
     +Channel_Link_ThingControl_setpoint_temp (Link)

I would set all the links to the standard profil, not to follow - since I want to be able to control the setpoint from every device. Is this the way? Or do I need to use rules instead?

While OH does try to make some things easier if you do things a certain way, the list of not-intended uses is much shorter than intended uses.

In other words, if you come up with something that works it is probably a good way to do it.

There are several approaches and the best approach depends on details not provided.

I don’t think this will work.

Consider the difference between an update and a command to an Item. An update only changes the state of the Item. It does not go out to the binding and on to the end device. Only commands get passed from OH through the binding and on to the device.

When, for example, Channel_Link_ThingControl_setpoint_temp changes, it will update the Item. Therefore the command will never go out to Channel_Link_ThingValve1/2_setpoint_temp and therefore the valves will not change to the new setpoint.

That’s why the follow profile exists and why it’s needed.

It might be possible that if you use the follow profile you can make it work like this but only if there was only one Valve Channel. You can only have one profile per link. Because you have two channels that need to follow the one control channel, you can’t get there from here.

To further complicate matters, the follow profile is one direction so syncing the changes to the valves to the control setpoint and syncing the control Item to the valves isn’t going to work.

So you will have to use some other approach.

The most straight forward approach will be to put the valve Items into a Group. Then you can make a simple rule that commands the valve Group when the control Item changes.

But you can get a little more clever. Assuming you are already using the Semantic Model, the valves and the control setpoints should already be members of the same Equipment. Then you can use that to keep everything in sync. I’m assuming two-way sync here. It can be simpler if it’s one way sync. I’m also assuming that the tags on the three Items are “setpoint” and “temperature”.

  1. trigger the rule when a member of the Equipment Group changes, if you’ve more than one Equipment, add each as a trigger
  2. Create a script action along the following lines. I’ll show Js Scripting but this should be doable in any language. It could be a one liner but I’ll break it up for legibility.
// Get the equipment the change came from
var equip = items[event.itemName].semantics.equipment;
// Get the other setpoint Items that are not the one that triggered the rule
var syncItems = equip.members.filter( i => i.tags.includes('setpoint') && i.tags.includes('temperature') && i.name != event.itemName);
// Command the other setpoint items if they are not already in the same state
syncItems.forEach((i) => i.sendCommandIfDifferent(event.newState));

I just typed in the above, there might be typos. You’ll need to adjust as necessary if any assumptions are not correct.

If you are not using the Semantic Model, you can use plain old Groups.

I think they meant that they want to be able to use any of the three channels to change the temperature.

If this is the case, they could set each channel to follow the others in a loop (1 → 2 → 3 → 1), and they’ll chase each other until the end up at the same value. But would this potentially cause a race condition?

In any case, I prefer the rule approach you’ve described, purely because they may discover new scenarios in which they don’t want the thermostats to be synchronized. If so, it’ll be easy to modify the rule to add conditions.

Two of the Channels control the valves, one is the “thermostat”. Based on their “chart” they want to keep all three in sync (i.e. if one changes the other two are commanded to the same setpoint).

I would only link one item to one channel, each item representing what it’s linked to.

Group TemperatureControls

Number:Temperature Control_setpoint_temp (TemperatureControls) { channel="Channel_Link_ThingControl_setpoint_temp", autoupdate="false" }
Number:Temperature Valve1_setpoint_temp (TemperatureControls) { channel="Channel_Link_ThingValve1_setpoint_temp", autoupdate="false" }
Number:Temperature Valve2_setpoint_temp (TemperatureControls) { channel="Channel_Link_ThingValve2_setpoint_temp", autoupdate="false" }

File Based Rule in JRuby:

rule "Synchronize temperature controls" do
  changed TemperatureControls.members, for: 10.seconds
  run do |event|
    TemperatureControls.members.ensure.command event.state
  end
end

What that does:

  • When any member of the TemperatureControls group changed and stayed stable for at least 10 seconds:
  • propagate the new state to all members of the group whose state aren’t already the same (this is what ensure above does. It’s the same as sendCommandIfDifferent in jsscripting/jython). In this case because the item that triggered the event already had the same state, the command won’t be sent to it.

The for: 10.seconds gives you a free timer that does all the checks for you behind the scenes, so that if the state changes rapidly e.g. someone changing their mind within 10 seconds, the other items won’t get noisy commands that keep changing.

UI Based Rule in JRuby

You can implement something similar in a UI rule, by setting a group member trigger as usual, and write this in your rule body:

debounce_for 10.seconds do
  TemperatureControls.members.ensure.command event.state  
end

Again, with these few simple lines, you are getting for free, a lot of of features that you don’t have to code yourself. Traditionally you’d have to write a whole bunch of timer code and checks to achieve this but in jruby this is all part of the standard helper library.

Whilst the Semantic model can help with “finding” the other radiators, it doesn’t help you set up the trigger. For that, the plain “trigger on group member” approach is still the best way to do this.

Wow thanks. Your answers where enlightening.

Ah. Yes. I read this but I think no longer had it on my mind. That puts it into perspective.

Yes. Then this solution is out.

Thank you. After you even provided the rule in code I will just click it into blockly. I love how compact this can be done when using the Semantic Model. Bit more LOC with blockly, but still great.

That is exactly was what I was hoping for. And the question you bring up about introducin a race condition was the final strike to drop the idea and going for rules instead. Thanks.

Still I feel a bit sad not being able to solve this using the model itself - I thought the issue might be so wide spread that there might be a way to use the model to solve it.

Almost exclusively using blockly I rarely used waiting for a change to be in effect over a specific time. I will be doing this more often. Thanks for the puzzle piece.

I started using OpenHAB at version 4 - didn’t need to use a ‘non-semantic’ group so far. I might give it a go. Thanks a lot.

It is wide spread but it’s also surprisingly complicated to support in all use cases. In the end, anything that would work at the Link level (which is actually independent of the semantic model) is going to be as complicated if not more so than writing a rule.

Besides, doing stuff like this is what rules are made for. When event X happens, do Y. In your case, when any setpoint changes, make sure the change is synced to all the setpoints.

That’ type of trigger is only going to be supported in jRuby text file defined rules.

I posted the UI version that does the same thing above.

I am checking back in. Thanks for your help - I understood why my idea was not working and implemented the rules that make everything work. This part was as easy as expected - thanks everyone.