Automation/Orchestration Design Patterns

I’m willing to accept others have differing opinions. I’m even willing to accept I may be wrong. :slight_smile:

So what I mean by this statement is that approaching the Rules DSL from the mental framework of an Actor based programming paradigm is helpful. If one things about Rules as Actors I find, at least for me, it leads to better rules. I’m well aware that the Rules DSL falls far short of a true Actor Based programming language.

Perhaps my insistence of comparing it to an Actor based language is more confusing than helpful, despite how much it has helped me reason about my rules.

If one uses the old maxum “I can program Fortran in any langauge”…

One rule per file and a global var.

Actors _can_vprocess messages sequentially. They can also process them in parallel.

True.

I’d argue the entire “when” clause is the address for sending a message to an Actor. See this. The “address” of the “Dispatch Info Notification” is the Notification_Proxy_Info Item.

They can; see above.

All of which I think are very applicable to the Home Automation problem domain. And I think my argument is, even if the Rules DSL falls short of being a true Actor based langauge approaching it as if it were can result in rules that are more asynchronous, support concurrency, and fault tolerant.

That is a very interesting bit of history I was unaware of. I agree, I wonder if the Rules DSL would have gone in the same direction had it not had this history. Honestly, though I’m a bit of a Rules DSL champion, I am aware of its flaws. And I think I agree with @watou, it would be far better if the Rules DSL were based on a more common and well documented language (Python, JavaScript, Groovy, Luau, etc.) if for no other reason than we wouldn’t be responsible for writing the language documentation.

But it is the default language and so I try to come up with ways to approach the language so users and avoid some of the pitfalls they might encounter if they were to try and treat the DSL as if it were an OO or procedural language. I hope you would agree, the Rules DSL is neither one of these. And trying to code in it as if it were will lead to trouble. That is truly the problem I’m trying to address. I tend to try to explain things using similis and metaphores. If using the Actor Based paradigm as a simili is not helpful I’m very willing to use something else.

I don’t think I’ve ever argued against this. I completely agree: one has more libraries, better documentation, and more overall language support with any of the languages in the JSR223 languages than ever will exist with the Rules DSL, or the Experimental DSL for that matter. None of my defenses of the Rules DSL is intended to be in any way mean that these languages are less capable.

Where I’m coming from is that:

  1. The Rules DSL is the default so for most users it will be THE way to write rules
  2. I find, if approached with a certain frame of mind, the Rules DSL is particularly well suited to event-based programming

So I guess my main point in defending the Rules DSL is not that other languages are worse but that the Rules DSL is pretty good in its own right.

Point taken. I agree.

Actually, I find that using Groups like I describe here there is very rarely a need for lambdas at all. That is one reason I promote their use so heavily. If you have lambdas I treat that as a slight code smell. If you have nested lambdas I start to look for the skunk. That doesn’t mean those are never appropriate, but I’ve found that more often than not using Groups leads you to a place where you don’t need lambdas at all. It is all in the rules themselves and there is no need to call out to a lambda.

And I think that is my point. When programmers approach the rule as a procedural or OO language they end up with HashMaps and Lambdas all over the place. You might even be passing whole HashMap data structures to lambdas to get over the 7 argument limitation in lambdas. I say this because that is what my rules looked like once upon a time.

But by tagging like Items with Groups one can centralize their processing into the Rule, no more need to lambdas or data structures.

Granted, this does move some complexity from your rules to the items files because the number of Groups can proliferate quite a bit. And there are some limitations with the OH architecture that make this approach a little clunky (e.g. the Thread::sleep that is required to give persistence a chance to catch up when figuring out which Item triggered a rule).

So on this point, I disagree. One would create a single Group, applying this, and have a single chunk of code in one rule to process that specific purpose. When one uses Groups, one can treat their Groups as if they were Databases (a NoSQL database to be specific, there is even a map/reduce methods).

For the second instance, one would create a second Group and, if the first rule was complex enough, move the common parts into a lambda called by these two rules, if not duplicate the two to three lines of common code and just have the two rules. Or one might simply add and if/else to their existing rule to handle the two cases. It all depends on how different the two are and how complex the behaviors are.

The code remains actually very simple and easy to understand.

That is what I am trying to get across with my Design Pattern postings, my defenses of the Rules DSL, and trying to bring in the mental framework of Actor Based Programming. Applying Groups and the well-defined operations on Groups one can move A LOT of rule complexity into the Items files and out of the Rules. Honestly, I think these approaches also would apply very well to JSR233 languages as well and when/if that plugin ever makes it into OH 2 I plan on revisiting all of my Design Patterns and adding at least a Jython (I like Python) and JavaScript versions of those where it is applicable.

And at some point, I would like to combine all of those into a unified document for how to approach the Rules DSL. I’ll probably get around to it six months after the Rules DSL becomes deprecated. :wink:

An example may be informative. Here is my lighting rule:

val logName = "lights"

rule "Set lights based on Time of Day"
when
  Item vTimeOfDay changed
then
  val offGroupName = "gLights_OFF_"+vTimeOfDay.state.toString
  val onGroupName = "gLights_ON_"+vTimeOfDay.state.toString

  logInfo(logName, "Turning off lights in " + offGroupName)
  val GroupItem offItems = gLights_OFF.members.filter[g|g.name == offGroupName].head as GroupItem
  offItems.members.filter[l|l.state != OFF].forEach[l | l.sendCommand(OFF)
  ]

  logInfo(logName, "Turning on lights for " + onGroupName)
  val GroupItem onItems = gLights_ON.members.filter[g|g.name == onGroupName].head as GroupItem
  onItems.members.filter[l|l.state != ON].forEach[l | l.sendCommand(ON)
  ]

end

It is a basic timer based rule that turns on and off lights based on the time of day.

The Items are:

Group:Switch:OR(ON,OFF) gLights_ALL "All Lights"

Group:Switch:OR(ON, OFF) gLights_ON
Group:Switch:OR(ON, OFF) gLights_OFF
Group:Switch:OR(ON, OFF) gLights_ON_MORNING    (gLights_ON)
Group:Switch:OR(ON, OFF) gLights_OFF_MORNING   (gLights_OFF)
Group:Switch:OR(ON, OFF) gLights_ON_DAY        (gLights_ON)
Group:Switch:OR(ON, OFF) gLights_OFF_DAY       (gLights_OFF)
Group:Switch:OR(ON, OFF) gLights_ON_AFTERNOON  (gLights_ON)
Group:Switch:OR(ON, OFF) gLights_OFF_AFTERNOON (gLights_OFF)
Group:Switch:OR(ON, OFF) gLights_ON_EVENING    (gLights_ON)
Group:Switch:OR(ON, OFF) gLights_OFF_EVENING   (gLights_OFF)
Group:Switch:OR(ON, OFF) gLights_ON_NIGHT      (gLights_ON)
Group:Switch:OR(ON, OFF) gLights_OFF_NIGHT     (gLights_OFF)
Group:Switch:OR(ON, OFF) gLights_ON_BED        (gLights_ON)
Group:Switch:OR(ON, OFF) gLights_OFF_BED       (gLights_OFF)

Switch aFrontLamp "Front Room Lamp"
  (gLights_ALL, gLights_ON_MORNING, gLights_OFF_DAY, gLights_OFF_AFTERNOON, gLights_ON_EVENING, gLights_OFF_NIGHT, gLights_OFF_BED)
  { channel="zwave:device:dongle:node3:switch_binary" }

Switch aFamilyLamp "Family Room Lamp"
  (gLights_ALL, gLights_OFF_MORNING, gLights_OFF_DAY, gLights_ON_AFTERNOON, gLights_ON_EVENING, gLights_OFF_NIGHT, gLights_OFF_BED)
  { channel="zwave:device:dongle:node10:switch_binary" }

Switch aPorchLight "Front Porch"
  (gLights_ALL, gLights_OFF_MORNING, gLights_OFF_DAY, gLights_OFF_AFTERNOON, gLights_ON_EVENING, gLights_OFF_NIGHT, gLights_OFF_BED)
  { channel="zwave:device:dongle:node6:switch_binary" }

So the theory of operation is I add a Light to the “ON” group that corresponds to the time period I want the light ON and the “OFF” group that corresponds to the time period I want the light to be OFF. So all that complicated if/then/else logic goes away simply through prudent naming of my Items and use of Groups. If I want to add a new time period or dozens of new Lights and never have to touch this 20 LOC or so Rule. No lambdas. No data structures (outside of the Groups). Very simply rules.

And when I go back to using the weather to drive my lighting (i.e. turn them on when the weather is cloudy) I’ll add those lights to a Group and add a Rule that gets triggered by the weather changing and figure out which lights to turn on and off based on group membership.

Actually, I would end up having one Rule with 20 or 30 triggers and maybe a switch statement. I’d need to see a concrete use case to say how I would implement it. To add a mode to only enable some schedules when the house is in vacation mode I would either have some if/else or switch statements or potentially one additional rule. So this is why I challenge the OO paradigm. You have one additional function and so do I. Mox nix, no?

And I just add the new camera to my “gCameras” group and I’m done. I might need to add some additional code in the form of Rules if the Item based interfaces are different enough (see Separation of Behaviors). I will grant that the Rules DSL code may not be packaged up into as convenient of a package as a class, but I don’t think it takes any more code to implement. You have to override certain methods and I may have to write a new Rule for certain behaviors.

I completely agree. But I’ll also say that Groups are exceptionally powerful in this regard. They are akin to the way Perl makes everything a hashmap. The language only gives you one really big hammer but wow its a really powerful hammer. Maybe closer to Dr. Who’s Sonic Screwdriver. :wink:

Global is global to a single file, not global to all Rules. You can divide your rules such that “global” vals and vars are only available to the rule or rules that should have access to them. I put “global” in quotes because it is somewhat of a misnomer. They are only global to the one file. So from that perspective, depending on how one organizes their rules files, “global” vals and vars can be every bit as private as a private member of a class. This is one reason I recommend users divide their rules by function rather than geographic location. It more naturally leads to the vals and vars being only available to those rules that should have access to them.

I do agree, the Rules DSL does not enforce this or make it obvious (e.g. like Java’s one class per file). That is one of the many flaws in the Rules DSL. But one can still encapsulate data.

Virtual Items serve a different purpose. And I actually hate that name and prefer to call them “Unbound Items”. Virtual Items are intended to:

  • act as a summary Item, combining the states from multiple disparate Items
  • act as a proxy to provide a layer of separation between the rest of the system and a device (e.g. provide a mechanism to override the controlling of a light under certain conditions)
  • store some public state that may be needed across the system (e.g. Time of Day

If you have private state you want to store, it is best stored as a “global” var or val in a rules file containing only those rules that need that data. Virtual Items are not meant to store “private” state.

From the beginning I wanted to be a resource on the forums to new users. That meant using a pretty standard OH config and being comfortable with the default Rules DSL. And it took some time of fighting with the language to realize that if I bend to what the way the language wants to do things and take advantage of what the OH architecture provides (persistence, Groups) rather than trying to impose my own OO/Procedural (I even tried functional but the 7 arguments to a lambda put the kaibosh on that) I found that the language really does work. I can’t remember specifics (I could maybe look in my git history to see) but across the board, I achieved at least an order of magnitude reduction in my LOC by doing things the way the langauge wants. I have the same or more functionalty, vastly more maintainability, and more expandability. But each of my rules fits on a single Putty screen, none of my rules files are more than few pages, All of them are pretty easy to understand. I have a single lambda across all my rules and I could probably eliminate that if I took a little time to just sit down and do it.

But I had to bend to the langauge.

Had I not chosen to contribute to these forums as my way to give back to the community and I knew about the JSR233 binding I probably would have gone with Jython from the start. I’m kind of glad I did because I think it did make me grow as a programmer. I’m cautiously optimistic about the Experimental DSL. If it lives up to its vision, I’ll be able to just write a library of resusable code instead of these Design Pattern postings (I’d love to write a generic state-machine). I’m hopeful also about JSR support in OH 2 as I do believe options and alternatives are a good thing in this domain. Like I said above, I don’t think OO is necessarily inferior (though upon rereading I do come across as if I do, and I apologize for that). I just think that the Rules DSL is worth consideration.

1 Like