Design Pattern: Separation of Behaviors

designpattern
Tags: #<Tag:0x00007f6ce3f0b570>

(Rich Koshak) #1

Another transfer of a Design Pattern from the great big Design Pattern thread. I move them over to a new separate thread as I need to reference them in an answer to a question. NOTE: the rules have been extensively reworked to make them shorter and simpler.

Problem Statement

Often one will have certain cross cutting code that is used across .rules files by a diverse and varied collection of Rules. Such things can include things like calculating TimeOfDay to control the behavior of a rule, sending alerts, calculating state to drive a state machine, etc. For example, Lighting, blind/rollershutter controls and HVAC may all care whether it is cloudy or not.

Sometimes one can implement this sort of thing in a lambda but lambdas are limited to only be callable from Rules in the same .rules file, forcing you to put everything in one file or duplicating your lambdas across multiple .rules files, violating DRY (Don’t Repeat Yourself). Another alternative is to use Scripts but one cannot pass arguments to Scripts which limits their usefulness to this use case.

Concept

Set up a proxy Item and send commands to that Item to update its state and kick off a Rule to implement the cross cutting logic. The value sent to the Item represents the argument(s) for the cross cutting logic to operate on.

A String Item makes the best choice for the Proxy Item as it is able to represent a lot of different types of data and it is easily parsable into multiple arguments if needed.

There are two sides to this design pattern. One side implements something akin to a function call where a Rule initiates by sending a command to the Proxy Item. This would be useful for cases like alerting where the Rules have a message they send to the Proxy to be dealt with. The first simple example below is this sort of implementation.

The second is to have a rule that triggers on its own which populates the Proxy Item with the result of a calculation. The second example below which determines if it is cloudy is this sort of implementation. In this case the Rules check the Proxy Item to determine what the current state is and adjust its behavior based on the current state.

Simple Example 1

This is an alerting example. It allows Rules to distinguish between different types of alerts and do something different based on the alert type and the TimeOfDay. Some ancillary Items are not shown below.

Items:

String Notification_Proxy_Info
String Notification_Proxy_Alarm

Rules

rule "Dispatch Info Notification"
when
        Item Notification_Proxy_Info received update
then
        val String msg = Notification_Proxy_Info.state.toString
        logInfo("InfoNotif", msg)
        if(TimeOfDay.state != "Night") notifyMyAndroid(msg)
end

rule "Dispatch Alarm Notification"
when
        Item Notification_Proxy_Alarm received update
then
        val String msg = Notification_Proxy_Alarm.state.toString
        logError("AlarmNotif", msg)
        if(TimeOfDay.state == "Night") notifyMyAndroid(msg) else sendNotification("email", msg)
end

rule "Some random rule"
when
    // something happens
then
    // do some stuff
    Notification_Proxy_Alarm.sendCommand("I have a serious alert!")
end

In the above, Info alerts only get sent to the default device registered with Notify my Android’s configuration and even then only when it isn’t night. Alarm alerts get sent to all devices registered with my.openhab unless it is night in which case they only get sent out by Notify My Android.

Simple Example 2

Item

Switch      Weather_Cloudy       "Conditions are Cloudy [%s]" <rain>

Rules

rule "Update Cloudy Switch"
when
        Item Condition_Id changed // updated by weather
then
    // Yahoo cloudy weather condition IDs
    val Set<String> yahooCloudyIds = newImmutableSet("0",   "1",  "2",  "3",  "4",  "5",  "6",  "7",  "8",
                                                         "9",  "10", "11", "12", "13", "14", "15", "16", "17",
                                                         "18", "19", "20", "26", "28", "35", "41", "43", "45",
                                                         "46", "30", "38", "29", "30", "44")
    // https://www.wunderground.com/weather/api/d/docs?d=resources/phrase-glossary
        val Set<String> wundergroundCloudyIds = newImmutableSet("2",  "3",   "4",  "6", "9", "10", "11", "13",
                                                                "14", "15", "16", "18","19", "20", "21", "22",
                                                                "23", "24")
        val isCloudy = yahooCloudyIds.contains(Condition_Id.state.toString)
        if(isCloudy && Weather_Cloudy.state != ON){
                Weather_Cloudy.sendCommand(ON)
        }
        else if(!isCloudy && Weather_Cloudy.state != OFF) {
                Weather_Cloudy.sendCommand(OFF)
        }
end

rule "Some random rule that cares about cloudy"
when
    Item Weather_Cloudy received command
then
    if(Weather_Cloudy.state == ON) {
        // do stuff now that it is cloudy
    }
    else {
        // do stuff now that it isn't cloudy
    }
end

##Advantages and Limitations
The major advantage this Design pattern provides is centralizing of otherwise cross cutting logic. As a result the cross cutting logic can be more complex yet easier to maintain. If, for example, I decided to use a different notification service for my alerts I would only have to change it in one place.

The major disadvantage is that if Rules send commands to the Proxy in rapid succession (i.e. tens of milliseconds apart) the state of the Proxy Item may already be set to the second command’s state when the first trigger of the rule starts executing. So, for example, in the Notification example above the first message would be lost and the second message would be sent twice. This behavior should be exceptionally rare unless you artificially create such a situation (e.g. iterating over a Group’s members and sending a command for each one without a 50-100 msec pause between commands).

Related Design Patterns

  • Virtual Item: Notification_Proxy_Info and Notification_Proxy_Alarm are both examples of Virtual Items.
  • Proxy Item: Notification_Proxy_Info and Notification_Proxy_Alarm are both examples of Proxy Items.

Reusable Functions: A simple lambda example with copious notes
Can a rule trigger another rule
Motion detection -> lights on - if it's dark and within time period, with timer - Help needed
How can I get the initial state of an MQTT switch?
Support to distinguish between human- and machine "agent" induced events
How to access to .cfg defined vars?
Comprehensive Wunderground using HTTP Binding Example
Keep 'when Item received update' rules from running during RestoreOnStartup
Passing parameters to a script
Design Pattern: Human Readable Names in Messages
Help with Rules please
"First Time" Rule
Best practice for .rules
sendHttpGetRequest - TimeoutException
Design Pattern: Manual Trigger Detection
Notifications in group design pattern
Design Pattern: Expire Binding Based Timers
Blinking LED - how to implement it correctly?
New Binding - Timer/Scheduling Binding
Effective use of Timers in Rules
OpenHab send sensor notification to iOS/Android using IFTTT
Best way to control timer from more than one .rules file?
Reusable Functions: A simple lambda example with copious notes
Automation/Orchestration Design Patterns
Whole house lighting
[OH2] Rule help to play a random sound file
Set last tripped/alarm date, or last action date to an item - best practice?
Boxcar Notification - rule to notify, can a rule execute a rule?
Is there a way to create functions/routines in openhab?
Presence detection via iPhone's mDNS Entry. How to integrate into OH2?
Current and Correct Documentation of Syntax for Things and Items Needed
How to find out if binding stopped working?
Rule optimization: Window OPEN reminder
Sending Notifications to Fire TV/Android TV
Can I get a Phone location within my house?
Can I call a rule from within a rule?
Rule Push Message to iOS App when Window longer Open
Iterating over a group, want to check an alternate item, sometimes
Send SMS via VoIP provider API
Global functions for use in rules
Include external script in rule file
How to get notification for sensor alarm using openhab2
Triggering a rule when user navigates (back to parent level) in Basic/Classic UI
Making Decisions Based on Time of Day
Decoupling notification service
Timed lights, Alerts to push notification?
(Udo) #2

@rlkoshak Thanks for all of your design patterns.
I like all these ideas, which make things easy to overlook and to maintain.

I adapted the pattern above and i found out that I get old messages during system start.
… yeah because of the update event from the persistence.

So I changed to the received command trigger.
… and accidently I got older messages, because of the delay between receive and update.

So once again the question was raised when to use a which trigger:

ItemCommandTrigger, ItemStateUpdateTrigger, ItemStateChangeTrigger

vice versa when to use what

postUpdate, sendCommand

I’ve looked for a comprehensive explanation but I didn’t found one.
I’ve made a homemade explanation … but struggling for the optimum.


(Rich Koshak) #3

It is a huge topic with lots of special cases and knowledge of the specifics of your system that simply cannot be generalized.

At a high level:

  • command when you want the Rule to trigger when the Item is specifically commanded
  • update when you want the Rule to trigger anytime the Item is “touched” whether or not the Item actually changed
  • changed when you want to Rule to trigger when the Item changes state

Seems simple enough but what about side effects?

  • command will only trigger if the Item is specifically commanded (from the sitemap, a binding, or another Rule) and if that Item is linked to a Channel or binding that command will almost certainly go out to the device as well. Is that acceptable in your case? I can’t say, only you know your system.

  • update will trigger anytime the Item is updated or commanded but this means the Rule will trigger when the Item is populated using restoreOnStartup as well as from updates caused by bindings, sitemaps, and rules. Does this matter? It depends on the specifics on this Item and how it is used and populated.

  • changed will trigger anytime the Item’s state changes but this means the Rule will trigger when the Item goes from NULL to a restoreOnStartup state on startup. Does this cause problems? I can’t say, it depends on how the Item is used.

So as you can see, given a very specific scenario, I can easily tell you what Rule trigger would be most appropriate, but I can’t give simple advice that is generally applicable because it depends on the specific details about how the Items in question are defined, how they are updated and commanded, and what the Rule is supposed to do.

This sentence as written does not make sense. Care to elaborate?


(Udo) #4

First of all: very nice and understandable explanation.
(Maybe it could be added to the general documentation.)

Second - some deeper dive in my logfiles -hopefully not to detailed…

I refer to your:

The sequence was:

  1. use the washing machine
  2. ready
  3. dispatch info: washing is ready
  4. use the dryer
  5. ready
  6. dispatch info: dryer is ready --> this went wrong.
    I received two times “washing is ready”.

The essence of my logfiles …
openhab.log says:

2018-01-23 09:16:43.496 [INFO ] [.smarthome.model.script.Notification] - Dispatch info: washing ready!
2018-01-23 09:18:33.433 [INFO ] [eclipse.smarthome.model.script.dryer] - dryer_StateMachine: STATE_OFF -> STATE_ACTIVE [11.696A]
2018-01-23 09:21:39.602 [INFO ] [marthome.model.script.washingMachine] - washingMachine_StateMachine: STATE_FINISHED -> STATE_OFF [0.057A]
2018-01-23 10:17:36.020 [INFO ] [eclipse.smarthome.model.script.dryer] - dryer_StateMachine: STATE_ACTIVE -> STATE_FINISHED [0.843A]
2018-01-23 10:17:36.058 [INFO ] [.smarthome.model.script.Notification] - Dispatch info: washing ready!

here the events.log:

2018-01-23 10:17:36.047 [ome.event.ItemCommandEvent] - Item 'Notification_Info' received command dryer is ready!
2018-01-23 10:17:36.051 [vent.ItemStateChangedEvent] - dryer_OpStateDebounceTimer changed from OFF to ON
2018-01-23 10:17:36.063 [vent.ItemStateChangedEvent] - Notification_Info changed from washing ready! to dryer is ready!

By the timestamps there is a small gap between
’Notification_Info’ received command
and the change of the state
Notification_Info changed

So my rule, which was triggered by the “received command”, fetched the old item.state at this time.

That confuses me because if this happens every now and then …???


(Rich Koshak) #5

I have never seen nor heard of this happening or being reported on the log. It makes me think that either there is something else going on or this is a very new bug.

Are you on the snapshot?

Does it consistently get the wrong state or only sometimes?

What happens when you put a Thread::sleep for 20 msec or so in your alerting rule, does that fix it?

When using received command (which IMHO is the correct trigger in this case) what is the value of the implicit variable receivedCommand?

All things considered, were I to write Example 1 again today I would have:

rule "Dispatch Info Notification"
when
        Item Notification_Proxy_Info received command
then
        val String msg = receivedCommand.toString
        logInfo("InfoNotif", msg)
        if(TimeOfDay.state != "Night") notifyMyAndroid(msg)
end

rule "Dispatch Alarm Notification"
when
        Item Notification_Proxy_Alarm received command
then
        val String msg = receivedCommand.toString
        logError("AlarmNotif", msg)
        if(TimeOfDay.state == "Night") notifyMyAndroid(msg) else sendNotification("email", msg)
end

rule "Some random rule"
when
    // something happens
then
    // do some stuff
    Notification_Proxy_Alarm.sendCommand("I have a serious alert!")
end

If there is a timing problem that should fix it because receivedCommand gets populated before the Rule itself gets triggered. Calling Notification.state would fail sometimes as populating the Item state happens in parallel with the Rule triggering and apparently in this case the Rule runs before the Item gets populated (again, assuming the problem really is one of timing).


(Cody) #6

I too experienced the same thing recently with the notification sending the previous value. I’m not positive, but I believe it was after updating to the latest snapshot a couple weeks ago. The rule had been working fine for some time.
I added a 25ms sleep before parsing the string and that seems to have fixed it.


(Rich Koshak) #7

Since you can pinpoint much more specifically when the problem showed up and have a minimal configuration that works, would you mind posting an Issue?

Search first to make sure there isn’t one already.

I believe this is a regression and it either needs to be addressed or the new behavior needs to be documented.

Just to verify, is receivedCommand correct or does it contain the old value as well?


(Cody) #8

Like I said, it has been a couple weeks at least since these changes. I had updated my rules to use receivedCommand rather than update, around the same time as updating to the latest snapshot I believe.

I went back and tried a few different version of the rules to narrow down the issue.

Using post/received update, both the event log and the sent notification are correct without a sleep.

Using send/received command, the events log shows the proxy item both receiving the command and changing from the previous to new value correctly. The notification that gets sent contains whatever was in the proxy item previously, however.

Adding a 25ms sleep to the beginning of the notification rule fixes the issue and the correct notification is sent. I tested each case several times and it was 100% repeatable.

I’m not sure on the timing of when I changed to using update instead of command vs when I updated to the latest snapshot so I can’t say with certainty that the issue was introduced with the snapshot. I will still work on filing an issue though since it’s clearly not the expected behavior.


(Cody) #9

I did some additional investigating and would like to get your thoughts @rlkoshak whether this is an appropriate issue to file under ESH, or just an unfortunate effect of timings.

In this case the Item state is updating 19ms after the command was received. The rule is triggering on upon the command being received, however, which I guess is the expected behavior. The delay is sometimes as small as 1-3ms, but still enough to cause an issue.

What’s curious to me is that it doesn’t seem to manifest when using “update.”

Event log

24 15:23:29.072 [ome.event.ItemCommandEvent] - Item 'Notification_Proxy_Info' received command Laundry;Dryer Done
2018-01-24 15:23:29.091 [vent.ItemStateChangedEvent] - Notification_Proxy_Info changed from Laundry;Washer Done to Laundry;Dryer Done

Openhab log

2018-01-24 15:23:29.073 [INFO ] [pse.smarthome.model.script.InfoNotif] - Notification rule start
2018-01-24 15:23:29.073 [INFO ] [pse.smarthome.model.script.InfoNotif] - Washer Done

Rule

rule "Send Info Notification"
when
        Item Notification_Proxy_Info received command
then
        logInfo("InfoNotif", "Notification rule start")
//      Thread::sleep(25)
        val msg = Notification_Proxy_Info.state.toString.split(";")
        val String subject = msg.get(0)
        val String message = msg.get(1)
        logInfo("InfoNotif", message)
        pushNotification(subject, message)
end

(Rich Koshak) #10

I think it certainly is worth opening an issue to at least open the discussion. The expected behavior, and the observed behavior to this point, is that when the Rule triggers, the Item will already have the new state.

Or maybe this has been a problem all along and because it is a rare race condition it has been rarely seen or reported until now.


(Cody) #11

I agree with your description of the expected behavior. I’m still learning what components are part of ESH and which are OH specific so I wasn’t sure where rules fall in.

Edit: Issue has been raised https://github.com/eclipse/smarthome/issues/4981


(Rich Koshak) #12

Rules are ESH. These days there is very little that is part of the core that is OH 2 specific beyond Karaf. But it is always good to look and ask if you are unsure.


(Cody) #13

It doesn’t look like the issue is going anywhere. There was also another issue for it, but I forgot to search through closed issues. Woops.

I still think that, since sendCommand does trigger a state update, the expected behavior would be for the rule to act upon the new state. I guess knowing there is a problem at least lets us deal with it.

I remember now why I changed to received command instead of received update. With update I was getting notifications every time OH started due to restore on startup. I guess there’s a few ways to deal with this scenario.

  1. Use received command with a short sleep.
  2. Use received update, but only execute the rule if the previous state was not null.
  3. Don’t restore your notification proxy item.

(Simon Kaufmann) #14

…or

  1. use the Item <itemname> changed trigger for that purpose, if you are interested only in item state changes

@rlkoshak gave very to-the-point explanation above.

Just some additional words hopefully clarify the misunderstanding: a “command” event basically reflects the intention to change something in “real world” and is directed towards the bindings. It may take pretty long until the binding really reached out to the device and gets back the new, real state (for some devices even minutes). Once the binding receives back the new device state, it will then broadcast it as an item “state” event in openHAB. This is not a problem, but rather a design decision :smile:


(Rich Koshak) #15
  1. Use the receivedCommand implicit variable.

For this particular case that really won’t be a good solution because it is possible if not likely that the one will want to send the same notification twice or more in a row. That would be impossible with a changed trigger.


(Udo) #16

I’m on openHAB 2.2.0-1 (Release Build).

Even on a switch-item I get two different timestamps for the events.
(as pointed out by Kai in the github issue - there is an asynchronous state update.)

2018-01-25 20:36:58.613 [ome.event.ItemCommandEvent] - Item 'AlarmClock_Sunday_Active' received command ON

2018-01-25 20:36:58.633 [vent.ItemStateChangedEvent] - AlarmClock_Sunday_Active changed from OFF to ON

So a rule triggered by the “received command” and checking the state of that command in the rule could go wrong every now and then.
(As “user” I would expected a consistency when the rule is triggered but things are as they are. see here)

… That was a bit my question in the first place… how to use these different types of triggers.

…Yes

  • Now I would avoid a “received command” trigger without specification.
  • Only on switch items I would “received command ON” or "received command OFF"
    I wouldn’t recommend a sleep.
  • On analog values and strings I could easily live with the received update trigger
    … you only have to know. :hushed:

(Cody) #17

Kai’s explanation of the state being updated not by the command being received, but by auto update on the item makes a whole lot more sense of this. It may still catch some users by surprise, but at least we can hopefully point them to a better explanation of what is happening.