Design Pattern: Working with Groups in Rules

groups
designpattern
Tags: #<Tag:0x00007fd313faf968> #<Tag:0x00007fd313faf5f8>

(Kapil) #61

Hi Rich

This is exactly I wanted. Thanks a lot.
But there are 2 things that i want to do.

  1. If door is already open since evening and after sometime time becomes DARK, then there should be notification of door open.
  2. Is it possible to know the ‘hour’ repeated notification of persistently open door (eg first notification like this ‘door has been open for 1 hour’, second notification like ‘door has been open for 2 hour’, so on.
    I would be very thankful if you help me out.
    I am not using group since i am having only 1 sensor. I am using your code given in starting point.
    Best regards,
    Kapil

(Rich Koshak) #62
rule "Notify doors open after dark"
when
    Item vTimeOfDay changed
then
    if(vTimeOfDay.state != "DARK") return;

    gDoors.members.filter[ door | door.state == OPEN ].forEach[ door |
        // Send alert for door open when DARK starts
    ]
end
  1. This one is a little more tricky. You need to set a timestamp when the first alert is generated then calculate how many hours it has been since that timestamp for each subsequent alerts. I don’t have easy access to VSCode right now to quickly look up the methods available but I’m certain there are ways to do it. Perhaps now.getHours - (timestamp.state as DateTimeType).hours) with some additional code to handle the transition over midnight.

(Kapil) #63

Thank you so much.

It’s very simple but i don’t know how to take variables in openhab. Anyhow i can write a algorithm using int.
When door closes int count = 0;
And in the notification segment of code:
count = count + 1;
Send Notification (“Door has been open for” + count + “hours”;
And when the door closes reset this count to 0.

I have only 1 door sensor. So code would be even more easier.

Thanking you.


(Nicholas Waterton) #64

Thanks,

I will plan an upgrade, however I think I have traced part of the delay.

It seems as if rules do not get triggered while persistence is storing values. This could just be a coincidence, but I have a significant number of items that are persisted every minute (for graphs), I can see these in the logs, every minute:

2018-04-23 15:42:00.029 [DEBUG] [sistence.rrd4j.internal.RRD4jService] - Stored 'RGBIndicator_153_rssi' with state '58' in rrd4j database
2018-04-23 15:42:00.059 [DEBUG] [sistence.rrd4j.internal.RRD4jService] - Stored 'RGBIndicator_153_memory' with state '22360' in rrd4j database
2018-04-23 15:42:00.090 [DEBUG] [sistence.rrd4j.internal.RRD4jService] - Stored 'Weather_Temperature' with state '20.90' in rrd4j database
2018-04-23 15:42:00.122 [DEBUG] [sistence.rrd4j.internal.RRD4jService] - Stored 'DToutsidetemperature' with state '18.44' in rrd4j database
2018-04-23 15:42:00.152 [DEBUG] [sistence.rrd4j.internal.RRD4jService] - Stored 'Weather_Temp_Min' with state '0.200000000000000011102230246251565404236316680908203125' in rrd4j database
2018-04-23 15:42:00.181 [DEBUG] [sistence.rrd4j.internal.RRD4jService] - Stored 'Weather_Temp_Max' with state '21' in rrd4j database
2018-04-23 15:42:00.214 [DEBUG] [sistence.rrd4j.internal.RRD4jService] - Stored 'ousidetemperature' with state '17.20' in rrd4j database
2018-04-23 15:42:00.243 [DEBUG] [sistence.rrd4j.internal.RRD4jService] - Stored 'casetemperature' with state '34.06' in rrd4j database
2018-04-23 15:42:00.273 [DEBUG] [sistence.rrd4j.internal.RRD4jService] - Stored 'RGBDIRECT_or_Pi' with state 'ON' in rrd4j database (again)
2018-04-23 15:42:00.274 [DEBUG] [sistence.rrd4j.internal.RRD4jService] - Stored 'RGBDIRECT_or_Pi' with state '1' in rrd4j database
2018-04-23 15:42:00.316 [DEBUG] [sistence.rrd4j.internal.RRD4jService] - Stored 'psu2temperature' with state '32.18' in rrd4j database
2018-04-23 15:42:00.345 [DEBUG] [sistence.rrd4j.internal.RRD4jService] - Stored 'psu5volttemperature' with state '28.31' in rrd4j database
2018-04-23 15:42:00.377 [DEBUG] [sistence.rrd4j.internal.RRD4jService] - Stored 'psu1temperature' with state '27.02' in rrd4j database
2018-04-23 15:42:00.406 [DEBUG] [sistence.rrd4j.internal.RRD4jService] - Stored 'leds_mA' with state '-150' in rrd4j database
2018-04-23 15:42:00.437 [DEBUG] [sistence.rrd4j.internal.RRD4jService] - Stored 'outsidepsu5volttemperature' with state '19.12' in rrd4j database

and so on - there are a lot of these, and it seems to take about 10 seconds every minute to store them. First - this seems like a huge amount of time to store about 400 items, and second, while watching my rules log, no rules are triggered for the whole 10 seconds (which is unusual, I usually have one rule or another triggering all the time).

I have set org.eclipse.smarthome.threadpool:RuleEngine to 30, so that rules can run more threads, but that doesn’t help.Is it possible that while the rrd4j persistence service is storing it’s values, no rules can be triggerd - they all seem to trigger at once, once persistence has finished storing. look at this (Rules output log):

23-Apr-2018 15:49:00.001 [INFO ] [smarthome.model.script.Testing Rules] - rules_operating status: True
23-Apr-2018 15:49:10.486 [DEBUG] [ntime.internal.engine.RuleEngineImpl] - Executing rule 'Roomba Last Update Timestamp'
23-Apr-2018 15:49:10.487 [INFO ] [e.smarthome.model.script.Last Update] - roomba_lastheardfrom DateTimeItem updated at: Mon 15:49
23-Apr-2018 15:49:10.489 [DEBUG] [ntime.internal.engine.RuleEngineImpl] - Executing rule 'Calculate energy cost'
23-Apr-2018 15:49:10.489 [INFO ] [clipse.smarthome.model.script.Energy] - Current kW being used: 2.03593
23-Apr-2018 15:49:10.490 [INFO ] [clipse.smarthome.model.script.Energy] - Current kWh used since last update: 0.0101101169088
23-Apr-2018 15:49:10.492 [INFO ] [clipse.smarthome.model.script.Energy] - Instanteneous Energy Cost is: 2.03593kWh @9.5c/kWh = 19.341335c/h
23-Apr-2018 15:49:10.557 [DEBUG] [ntime.internal.engine.RuleEngineImpl] - Executing rule 'Monitor LED Current'
23-Apr-2018 15:49:10.558 [DEBUG] [ntime.internal.engine.RuleEngineImpl] - Executing rule 'HEM Update Timer'
23-Apr-2018 15:49:10.558 [INFO ] [se.smarthome.model.script.HEM Update] - Updating HEM Reset Timer
23-Apr-2018 15:49:10.558 [INFO ] [se.smarthome.model.script.HEM Update] - Rescheduled HEM Timer
23-Apr-2018 15:49:10.617 [DEBUG] [ntime.internal.engine.RuleEngineImpl] - Executing rule 'Compute garden humidex'
23-Apr-2018 15:49:10.789 [DEBUG] [ntime.internal.engine.RuleEngineImpl] - Executing rule 'Update In Use Light Sensor Back Garden'
23-Apr-2018 15:49:11.039 [DEBUG] [ntime.internal.engine.RuleEngineImpl] - Executing rule 'Monitor LED Current'
23-Apr-2018 15:49:11.082 [DEBUG] [ntime.internal.engine.RuleEngineImpl] - Executing rule 'basement Register Main Control'
23-Apr-2018 15:49:11.654 [DEBUG] [ntime.internal.engine.RuleEngineImpl] - Executing rule 'Convert Sensorpuck 2 Lux to text value'
23-Apr-2018 15:49:11.655 [DEBUG] [ntime.internal.engine.RuleEngineImpl] - Executing rule 'Update In Use Light Sensor SP2 Back Garden'
23-Apr-2018 15:49:11.710 [DEBUG] [ntime.internal.engine.RuleEngineImpl] - Executing rule 'Roomba Last Update Timestamp'
23-Apr-2018 15:49:11.713 [INFO ] [e.smarthome.model.script.Last Update] - roomba_lastheardfrom DateTimeItem updated at: Mon 15:49
23-Apr-2018 15:49:12.059 [DEBUG] [ntime.internal.engine.RuleEngineImpl] - Executing rule 'Vent 4 (Nick's Office) Received Pressure update'
23-Apr-2018 15:49:12.060 [INFO ] [se.smarthome.model.script.Flair Vent] - Nick's Office Pressure Diff: 60.000
23-Apr-2018 15:49:13.151 [DEBUG] [ntime.internal.engine.RuleEngineImpl] - Executing rule 'Vent 6 (Hallway) Received Pressure update'
23-Apr-2018 15:49:13.152 [INFO ] [se.smarthome.model.script.Flair Vent] - Hallway Pressure Diff: 30.000
23-Apr-2018 15:49:14.301 [DEBUG] [ntime.internal.engine.RuleEngineImpl] - Executing rule 'Vent 2a (Family Room - dining area) Received Pressure update'
23-Apr-2018 15:49:14.302 [INFO ] [se.smarthome.model.script.Flair Vent] - Family Room (dining) Pressure Diff: 20.000
23-Apr-2018 15:49:14.839 [DEBUG] [ntime.internal.engine.RuleEngineImpl] - Executing rule 'Compute garden humidex'
23-Apr-2018 15:49:15.258 [DEBUG] [ntime.internal.engine.RuleEngineImpl] - Executing rule 'Vent 5 (Washroom) Received Pressure update'
23-Apr-2018 15:49:15.259 [INFO ] [se.smarthome.model.script.Flair Vent] - Washroom Pressure Diff: 40.000

Notice the time difference between the first two entries - nearly 10.5 seconds.
Here is my other log showing what is happening at that time (which includes rules output):

2018-04-23 15:49:00.001 [INFO ] [smarthome.model.script.Testing Rules] - rules_operating status: True
2018-04-23 15:49:00.002 [DEBUG] [inding.expire.internal.ExpireBinding] - Item rules_operating will expire (with 'False' command) in 90000 ms
2018-04-23 15:49:00.002 [DEBUG] [sistence.rrd4j.internal.RRD4jService] - Stored 'RGBIndicator_153_rssi' with state '58' in rrd4j database
2018-04-23 15:49:00.035 [DEBUG] [sistence.rrd4j.internal.RRD4jService] - Stored 'RGBIndicator_153_memory' with state '22360' in rrd4j database
2018-04-23 15:49:00.065 [DEBUG] [sistence.rrd4j.internal.RRD4jService] - Stored 'Weather_Temperature' with state '20.90' in rrd4j database
2018-04-23 15:49:00.097 [DEBUG] [sistence.rrd4j.internal.RRD4jService] - Stored 'DToutsidetemperature' with state '18.44' in rrd4j database
2018-04-23 15:49:00.127 [DEBUG] [sistence.rrd4j.internal.RRD4jService] - Stored 'psu2temperature' with state '32.18' in rrd4j database
... 375 of these
...
2018-04-23 15:49:10.308 [DEBUG] [sistence.rrd4j.internal.RRD4jService] - Stored 'flairvent1Open' with state '0.99000000' in rrd4j database
2018-04-23 15:49:10.350 [DEBUG] [sistence.rrd4j.internal.RRD4jService] - Stored 'thunderboard4BatteryVolt' with state '3' in rrd4j database
2018-04-23 15:49:10.380 [DEBUG] [sistence.rrd4j.internal.RRD4jService] - Stored 'ButtonGreen2Battery' with state '100' in rrd4j database
2018-04-23 15:49:10.412 [DEBUG] [sistence.rrd4j.internal.RRD4jService] - Stored 'AeonMS65F' with state 'OFF' in rrd4j database (again)
2018-04-23 15:49:10.412 [DEBUG] [sistence.rrd4j.internal.RRD4jService] - Stored 'AeonMS65F' with state '0' in rrd4j database
2018-04-23 15:49:10.453 [DEBUG] [sistence.rrd4j.internal.RRD4jService] - Stored 'psu5volttemperature' with state '28.31' in rrd4j database
2018-04-23 15:49:10.486 [DEBUG] [ntime.internal.engine.RuleEngineImpl] - Executing rule 'Roomba Last Update Timestamp'
2018-04-23 15:49:10.487 [INFO ] [e.smarthome.model.script.Last Update] - roomba_lastheardfrom DateTimeItem updated at: Mon 15:49
2018-04-23 15:49:10.489 [DEBUG] [ntime.internal.engine.RuleEngineImpl] - Executing rule 'Calculate energy cost'
2018-04-23 15:49:10.489 [INFO ] [clipse.smarthome.model.script.Energy] - Current kW being used: 2.03593
2018-04-23 15:49:10.490 [INFO ] [clipse.smarthome.model.script.Energy] - Current kWh used since last update: 0.0101101169088
2018-04-23 15:49:10.492 [INFO ] [clipse.smarthome.model.script.Energy] - Instanteneous Energy Cost is: 2.03593kWh @9.5c/kWh = 19.341335c/h
2018-04-23 15:49:10.496 [DEBUG] [sistence.rrd4j.internal.RRD4jService] - Stored 'psu1temperature' with state '26.70' in rrd4j database
2018-04-23 15:49:10.527 [DEBUG] [sistence.rrd4j.internal.RRD4jService] - Stored 'Energy_Daily_MidPeak' with state '2.54180055674800' in rrd4j database
2018-04-23 15:49:10.557 [DEBUG] [ntime.internal.engine.RuleEngineImpl] - Executing rule 'Monitor LED Current'
2018-04-23 15:49:10.558 [DEBUG] [ntime.internal.engine.RuleEngineImpl] - Executing rule 'HEM Update Timer'
2018-04-23 15:49:10.558 [INFO ] [se.smarthome.model.script.HEM Update] - Updating HEM Reset Timer
2018-04-23 15:49:10.558 [INFO ] [se.smarthome.model.script.HEM Update] - Rescheduled HEM Timer

You see the timing of the ‘storing’ coincides exactly with the period of no rules being triggered (if a rule is triggered before this with a delay, it is still running).

I realize I can persist less items, but honestly why would persistence have anything to do with whether rules are triggered or not? and why does it take so long to write? (I have an SSD, and plenty of resources on my server).

I think this is partly the root of my issue, as every minute there is a 10 second delay on all rules, so it looks like there is a random ‘lag’ in response from the system.

Is this possible or am I going crazy?


(Rich Koshak) #65

That seems unlikely but I suppose anything is possible. My understanding of the architecture is that persistence operates in its own space with its own threads and would not even be able to block the Rules from running.

Unless…

If you have Rules that are querying rrd4j (e.g. lastUpdate, previousState, historicState, etc) I can see those method calls blocking while the persistence engine has a transaction lock on the DP updating the values. If you have rules triggered by Groups with lots of members, even at 30 runtime threads you could run out of threads pretty quickly.

I can’t say why it is taking rrd4j so long to save the values. That seems like a long time. I haven’t used rrd4j in a long time though so have no recent experience and even when I did I have never persisted that many Items. But I know others who have without problem.


(Kapil) #66

Can we use postUpdate in both cases (that is when door opens) ?


(Rich Koshak) #67

Why do you ask?

You could but you shouldn’t. When you are creating an event that will result in something happening (in this case starting the Expire Binding) you should use sendCommand.


(Kapil) #68

Okay. Thanks again !
I asked this just to get more clear about usage difference of postUpdate and sendCommand.


(Rich Koshak) #69

https://docs.openhab.org/configuration/rules-dsl.html#manipulating-item-states


(Nicholas Waterton) #70

Just updated to 2.3 release, and I’m using the new “Member of” syntax as a trigger.

this seems to work well for groups which are composed solely of Items, however I have found that if a group consists of other groups, then the triggeringItem contains the group that triggered the action, not the item that triggered the action, and only groups that have types associated with them.

This makes the trigger less useful, as you can only trigger on groups of items, and not groups of groups.

for example, if you have groups:

Group All
Group gGF 	        "Ground Floor"  <groundfloor>	(All)
Group GF_Family 	"Family Room" 	<television> 	(gGF)
Group GF_Kitchen  	"Kitchen" 		<kitchen> 	    (gGF)
Group GF_Washroom 	"Washroom" 		<toilet> 	    (gGF)
Group:Number:MAX    Max_Temperature "Max Temperature [%.2f °C]" <temperature> (All)

You could trigger a rule on “GF_Family” (if GF_Family consists of Items), but not on “All” like this:

rule "Test Item Changed"
when
    Member of All changed
then
    logDebug("Test", "Test Item Changed: " + triggeringItem.name)
end

As this gives you the name of the sub group that changed, not the item that triggered the change, and in this case only the group “Max_Temperature” gets reported - presumably as it’s the only group that has a type.

So the output of this rule is always:

Test Item Changed: Max_Temperature

This seems inconsistent, as you can trigger on a group of items that does not have a type, but not on a group that has subgroups that do not have a type. In the above example, you could trigger on “GF_Family”, but not on “gGF”. By requiring subgroups to have a type, this means that all the items in that group would have to be the same type, which in many cases is not realistic.

Just wondering is this intentional for this new trigger? or just an unintended consequence of the way triggers are designed?

Is there documentation somewhere of the behavior of the new “Member of” trigger?

Thanks.


(Rich Koshak) #71

That is the behavior I would expect. In the case you describe the Item isn’t directly a member of the parent Group, its Group is the member. You would need to flatten the Group hierarchy in this case or create a special purpose Group that includes all the Items you care about.

This is also known and expected. If the subGroup doesn’t have a type it doesn’t get updated when its members update so there would be nothing to trigger the parent Group as the subGroup never receives updates.

For you particular design perhaps it is less useful. But other designs may depend on exactly the current behavior where one wants the subGroup to be the triggeringItem. The individual who coded this chose to support the latter design rather than the former. Personally, I agree with that decision as the latter approach handles more use cases, is IMHO more consistent with other Group behaviors (e.g. if you iterate through MyGroup.members you get the subGroup Items, not the members of the subGroups), and there are workarounds to support your approach (e.g. create a special purpose Group).

Not exactly. Groups are Items too. The subGroup is the member of the Group that caused the rule to trigger. So the subGroup IS the Item that caused the rule to trigger. But, as was the case prior to the Member of trigger, Groups do not change or receive updates unless they have a type.

That is debatable and I could be convinced of this. But the solution would be requiring a Type for any Group used as a Rule trigger even when using Member of. The solution would not be going back to the case where a Group gets updated and changed even when it has no type (this caused all sorts of problems which is why it was changed) and the solution would not be doing recursive Member of triggers as that would be a breaking change and IMHO be less flexible over all. It would also be inconsistent with other Group behaviors.

Probably a little of both. The Member of trigger was always designed to only support a single level. From the posting that introduced the new trigger (see link below):

“Member of” only works on items that are direct members of a group. It does not traverse nested groups. In the rule below the “triggeringItem” implicit variable will be one of the groups that are nested within AllLights (SwitchLights or DimmerLights) and not the base items (SwitchLight01, SwitchLight02, DimmerLight01, or DimmerLight02).

The unintended consequence part is a result of the fact that Groups do not receive updates when they don’t have a type. This was actually a breaking change in OH 2.2 from before there was a Member of trigger and the change was made to fix some pretty significant problems. But as a result, subGroups will never update when their members update so those updates will never trickle up to the parent Groups.

No it doesn’t. It’s perfectly legal to do something like:

Group:Number AllStrings

And have all the members of AllStrings be String Items. AllStrings will still get updates. It just will never have a useful state itself. I’ve never tried it but I think AllStrings will remain NULL.

In a more useful case you can do

Group:Number:SUM AllSwitches

which will set the state of AllSwitches to the number of its members that are ON.

There are all sorts of combinations of Group types and member types that are possible and useful and even in those cases where the combination is not useful, the Group will still receive updates.

The posting Features for Rules that work with Groups is the most comprehensive short of looking at the source code for now.


(Nicholas Waterton) #72

Thanks @rlkoshak very useful as always.

Now I understand how this works, I have figured out some great new possibilities. For instance I can create a group (with no members) and then dynamically add members to this group, this can then be used as a trigger for a rule.

Using this great new feature, i have reduced my homekit code to 125 lines (including comments and extraneous “stuff”) down from 250 or so, eliminated most global variables/Maps, removed all my locks, and made the code much more readable.

Thanks for the explanation.


(Markus S.) #73

Hi Rich.
Perhaps you can help me.
I have 5 number items in a group, which will be updated every hour. All 5 items get an update with a brake of a few milliseconds. The group should give me the lowest, (here the cheapest value). And then after comparing the 5 items, the rule should start a command.
The problem is that the rule starts 5 times, by every update of every item in this milliseconds area. So the command will start in worst case 5 times. Is there a possibility to wait or get a delay time for a second, so that all 5 items are updated after each other an the group item gives me the cheapest value and start then the command?
Thanks and greetings,
Markus


(Rich Koshak) #74

Thread::sleep(1000) will block the rule for 1 second.

A better approach would be to use a timer.

var Timer timer = null

rule "my rule"
when
    Member of MyGroup changes
then
    if(timer !== null) {
        timer = createTimer(now.plusSeconds(1), [
            //Find the lowest and do what ever you do
            timer = null
        ])
    }
end

(Markus S.) #75

Thanks, i will test it.
Why is the timer better than the sleep method?
Greetings,
Markus


(Udo Hartmann) #76

The Timer won’t block a thread. The timer is scheduled, the rule ends.
The next call of the rule will find an already scheduled timer and will end without doing anything.
A Second later, the Scheduler will execute the code in a new thread, and it will be executed only once.

If using a Thread::sleep(1000), the rule will be started 5 times and every rule start will block a thread of the thread pool (default is 5 threads, so a sixth rule won’t start at all). Moreover, the Thread::sleep(1000) would not suffice, you would have to lock the rule or take care, that the code is executed only once.
When it’s guaranteed that all 5 items will be updated in a very short time slot, the timer is for sure very elegant to solve this.


(Markus S.) #77

Hi Rich.
It works perfect and with the help of Harry and Udo, i understand the problem and the function now.
I have seen the same problem in a post of you:
https://community.openhab.org/t/solved-error-rule-rulename-null/37359
It’s the same problem here by updating items every 0,002 seconds.
After using the timer the error rule:null is gone.
Thank you very much.
Greetings,
Markus