Rule to pick up last group update not triggering

Raspberry pi running V2.2 and working in VScode

Im trying to set up a strategy were I require setpoints to have a deadband between them ie not the same number and one needs to always be above the other, and not the inverse.

I was going to write a whole long list of if else statements to see if one setpoints was within range of another but the problem there is that if say im raising the lower setpoint but the upper setpoint is first in the if statement, that will always get caught first. If the outcome of that statement is to drop the lower setpoint, I would get stuck because the rule would drop the setpoint as I raise it to its limit, whereas dropping the upper setpoint would continually force the lower setpoint down.

Hope that makes sense.

Anyway, I was instead going to grab the last updated item in the group then apply the logic to the relevant setpoint. But I cant seem to get the last item.

Heres the items I added the SUM option just to try to see if the rule would pick it up as opposed to only having it set as a group with no other parameters.

Group:Number:SUM gSetpoints

Number Heating_OnTemp_Occupied    "Heating_Setpoint Occupied ON    [%.1f'C]" (gSetpoints)
Number Heating_OffTemp_Occupied   "Heating_Setpoint Occupied OFF   [%.1f'C]" (gSetpoints)
Number Heating_OnTemp_Reduced     "Heating_Setpoint Reduced ON     [%.1f'C]" (gSetpoints)
Number Heating_OffTemp_Reduced    "Heating_Setpoint Reduced OFF    [%.1f'C]" (gSetpoints)
Number Heating_OnTemp_Unoccupied  "Heating_Setpoint Unoccupied ON  [%.1f'C]" (gSetpoints)
Number Heating_OffTemp_Unoccupied "Heating_Setpoint Unoccupied OFF [%.1f'C]" (gSetpoints)
Number Heating_OnTemp_Setback     "Heating_Setpoint Base ON        [%.1f'C]" (gSetpoints)
Number Heating_OffTemp_Setback    "Heating_Setpoint Base OFF       [%.1f'C]" (gSetpoints)

Heres the rule

rule "Setpoint Offset"
when
	Item gSetpoints received update
then
	//maintain 0.5degC gap betwen setpoints
	//if(Heating_OnTemp_Occupied.state as Number > (Heating_OffTemp_Occupied.state as Number - 0.5)){
	//	Heating_OffTemp_Occupied.sendCommand((Heating_OnTemp_Occupied.state as Number + 0.5))
	//}
	Thread::sleep(150)
	val lastupdate = gSetpoints.members.sortBy[lastUpdate].last

	logInfo("Setpoint changed","Last setpoint changed - "+lastupdate.name)

end

The commented out lines was the start of the if else way of doing it.

in the karaf console, I can see the item being updated, and this is reflected on the sitemap.
But I cant see the group being updated as the rule doesnt appear to fire.

I did see in the console that the rules file was ‘empty or not parsed correctly’ but I dont understand this, I see it every now and then for various rules files which continue to work fine.
I tried adding a also at the start of the rule before anything else but still no sign of it.

Not sure if im missing something.

Thanks

This is one of my rules for a radiator valve

rule "Thermostat Master Bedroom"
when
    Item MasterBedroom_ThermostatAmbientTemp changed or
    Item MasterBedroom_ThermostatTarget changed
then
    val offset = House_HeatingOffset.state as Number
    val target = MasterBedroom_ThermostatTarget.state as Number
    val ambient = MasterBedroom_ThermostatAmbientTemp.state as Number
    var turnOnTemp = target - (offset / 2)
    var turnOffTemp = target + (offset / 2)
    if (ambient <= turnOnTemp) {
        if (MasterBedroomWindows.state == CLOSED) {
            if (MasterBedroom_RadiatorValve.state == OFF) {
                MasterBedroom_RadiatorValve.sendCommand(ON)
            }
        }
    } else if (ambient >= turnOffTemp) {
        if (MasterBedroom_RadiatorValve.state == ON) {
            MasterBedroom_RadiatorValve.sendCommand(OFF)
        }
    }
end

I define a turn on heating temperature as setpoint + 1/2 offset
and a turn off as setpoint - 1/2 offset

Hope that helps
I also check if the window is opened before turning on the heating

You have persistence set up for all the members of gSetpoints?

Before I continue though, do you really need two setpoints? Most of the time I see this implemented with a single setpoint and a hysteresis value. For example, I’d set the target temp to 68 and use a hysteresis of 1 and then the code only turns on the cooling if the temp is target+hysteresis and only turn on the heat if the target-hysteresis is less than 67. I think this is what Vincent is demonstrating.

When I solve these sorts of problems I usually break it down. For example, first I would calculate the difference between the two setpoints. If the diff is less than a certain amount you know that the the two setpoints have come too close together and you can adjust or react accordingly.

val diff = Heating_OnTemp_Occupied.state as Number - Heating_OffTemp_Occupied.state as Number

if(diff < .5) {
    val last = gSetpoints.members.filter[sp | sp.lastUpdate !== null].sortBy[lastUpdate].last
    last.postUpdate(last.historicState(now.minusMillis(500)).state) // you may have to experiment with how far to look back
}

Now you only have one if statement and you only have to sleep and mess with lastUpdate when you know you have a problem.

If you change your Rule trigger and list all of the members of gSetpoints individually (or when you move to 2.3 you can use the new Member of Rule trigger) then you don’t need persistence or lastUpdate because the implicit variable triggeringItem will be the Item that triggered the rule.

rule "Setpoint Offset"
when
    Item Heating_OnTemp_Occupied changed or
    Item Heating_OffTemp_Occupied changed or
    ...
then
    // Get the triggering setpoint's partner
    val onOroff = if(triggeringItem.name.contains("On") "On" else "Off"
    val opposite = if(triggeringItem.name.contains("On") "Off" else "On"
    val otherSetpoint = gSetpoints.members.findFirst[sp | sp.name == triggeringItem.name.replace(onOrOff, opposite)]

    // Calculate the difference by subtracting the Off setpoint from the On setpoint
    val diff = if(onOrOff == "On") triggeringItem.state - otherSetpoint.state else otherSetpoint.state - triggeringItem.state

    // reset the triggeringItem because it is too close to its partner
    if(diff <= 1) triggeringItem.postUpdate(previousState)
end

The above takes advantage of a lot of implicit variables. triggeringItem will be the Item that triggered the Rule. previousState is the state that triggeringItem had before the change that triggered the Rule (it only exists for rules with a changed trigger).

I use some string manipulation to see whether it was the upper or lower setpoint that triggered the rule and to pull it’s partner from the Group by name.

Rich, thanks for the input. Is 2.3 available if I update via apt?

I would give that a go.

I had hysteritic strategy in place but through various ideas and changes I ended up going this way, and I also like to see the value at which it will occur rather than to manipulate a deadband and setpoint to get the right offset. I know I dont really need to do it this way but I like to try it out and understand how it works.

I havent set up any persistence at the minute. Im currently just settings values at start up so there are no empty variables.

I couldnt understand why the group update or group changed wasnt triggering the rule either way though?

2.3 is under development. You will need to use the nightly snapshots which I wouldn’t recommend unless you are willing to debug problems. See the installation docs for how to switch repos.

lastUpdate will not work without persistence.

In all likelihood your Rule was triggering and erroring out before it could execute the log statement. You can’t sort by null and when you don’t have persistence set up lastUpdate always returns null.

ok thanks. Persistence is the next thing to iron out then Ill come back to this.

Thanks

If my guess is the case, you would be seeing errors in openhab.log.

no errors. Only in the karaf console there were references to the values being changed but nothing in the log.