Design Pattern: Separation of Behaviors

designpattern
Tags: #<Tag:0x00007f1824554ef0>

(Rich Koshak) #1

Please Design Pattern: What is a Design Pattern and How Do I Use Them for more information on what a DP is and how to use them.

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 time of day 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 can only be called from Rules in the same .rules file as the lambda, 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 Design Pattern: 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 that 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: Alerting

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 time of day. Some ancillary Items are not shown below.

Items

String Notification_Proxy_Info
String Notification_Proxy_Alarm

Rules

rule "Dispatch Notification"
when
    Item Notification_Proxy_Info received command or
    Item Notification_Proxy_Alarm received command
then
    logInfo("alert", receivedCommand.toString)
    val night = vTimeOfDay.state.toString == "NIGHT" || vTimeOfDay.state.toString == "BED"
    val alert = triggeringItem.name.contains("Alarm") && !night

    if(alert) sendBroadcastNotification(receivedCommand.toString)
    else sendNotification("justme@gmail.com", receivedCommand.toString)
end

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

Theory of Operation

In the above, Info alerts only get sent to my phone. Alarm alerts get sent to all devices unless it is night in which case they only get sent out to my phone.

Simple Example 2: Is it Cloudy?

This example checks the current weather conditions to determine wether it is cloudy or not. See Comprehensive Wunderground using HTTP Binding Example for how the Items below get populated.

Item

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

Rules

rule "Is it cloudy outside?"
when
	Item vWeather_Conditions changed
then
	logDebug(logName, "New weather conditions: " + vWeather_Conditions.state.toString)
		    
	val isCloudy = transform("MAP", "weather.map", vWeather_Conditions.state.toString)
	val newState = if(isCloudy === null || isCloudy == "false") OFF else ON
		
	if(newState != vIsCloudy.state) logInfo(logName, "Setting isCloudy to " + newState.toString) 

	vIsCloudy.postUpdate(newState)
end

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

Theory of Operation

When the current weather conditions changes, use a map file which returns true if the contidion indicates it is cloudy (snow, rain, partly cloudy, etc) and false otherwise. Then, if the current cloudy contitions are different from vIsCloudy, update vIsCloudy.

Any rule that needs to do something different if it is cloudy just need to check vIsCloudy.

Advantages and Limitations

The major advantage this DP 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 because I used this DP.

Related Design Patterns

Design Pattern How It’s Used
Design Pattern: Unbound Item (aka Virtual Item) Notification_Proxy_Info and Notification_Proxy_Alarm are both examples of Virtual Items.
Design Pattern: Proxy Item Notification_Proxy_Info and Notification_Proxy_Alarm are both examples of Proxy Items
Comprehensive Wunderground using HTTP Binding Example Not a DP but the cloudy rule depends on this code
Design Pattern: Gate Keeper Gate Keeper is a specific implementation of this DB
Design Pattern: Time Of Day Time of Day is a specific implementation of this DP
Design Pattern: Human Readable Names in Messages Used to map the current conditions to true/false depending on whether the conditions indicated that it is cloudy or not
Design Pattern: Sensor Aggregation ois a specific implementation of this DP

Comprehensive Wunderground using HTTP Binding Example
Reusable Functions: A simple lambda example with copious notes
Design Pattern: Gate Keeper
Design Pattern: Time Of Day
Design Pattern: Human Readable Names in Messages
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?
Keep 'when Item received update' rules from running during RestoreOnStartup
Support to distinguish between human- and machine "agent" induced events
How to access to .cfg defined vars?
Passing parameters to a script
Inactivity of items // no updates // automatically tracking
myopenHAB Mobile Notifications Temporarily Disabled
Lambda Procedure error: java.lang.NullPointerException
Stop timer and reset to null. Structure of rule okay?
Use variable for item
Rule to activate another rule
Presence detection via iPhone's mDNS Entry. How to integrate into OH2?
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?
Help with Rules please
Using Thread to wait until some condition happens
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
Current and Correct Documentation of Syntax for Things and Items Needed
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?
"First Time" Rule
Getting rule name in rule
Lambda functions fail (not thread safe?)
Lambda functions fail (not thread safe?)
Arraylist
Timers in functions not possible?
[SOLVED] MQTT populating temperature values - no it is not!
Design Pattern: Associated Items
Rules to only fire when its :00 or :30
File Include
Difference between scripts and rules
Rules / functions / scripts: best approach
OpenHAB or HAAS
Openhab 2 Contact Sensor Time Rule
Notifymyandroid is dead - alternatives?
[SOLVED] Rules Syntax : Select into Select
[SOLVED] Is there a better way to code this?
Android openhab app quick question
What are your top 3 automations
Rule for Rollershutters - with proxy items and reed-contacts
(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?


[SOLVED] Problem with case and switch in rules
(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.