Tips for state-machine-like rule

Hi all. After getting my rule working, I'd like to share it in the hope of:

  • learning tips/tricks for improving it
  • sharing some of the tricks I cooked up.

I realize this rule is similar to the lights app but I wanted to set things up myself and because I wanted some unsupported differences.

Design goals:

  • Use a variable ("Family Room Lights - Pending") to structure the code in function-like blocks.
  • Have all the triggers in one place so I can easily see how things interact.
  • Have wait delays resume were they left off.
  • Door triggers lights upon return home and not presence so that way I can see the lights turn on.
  • Play nicely with manual overrides.

Things I wish RM supported:

  • An enum for the trigger; this would prevent the clunky storing/reading as a string.
  • wait which can easily take False
  • The ability to set a boolean variable to a condition; this would turn five lines into one. (Note: I didn't need that in this rule but I have in others.)
  • Triggers supported "first" rather than "all"; eg, rather than trigger on every illuminance<60 I wish it could be made to trigger only on the first. (I achieved this by creating a Virtual Illuminance sensor.)
  • A version of wait for event which short-circuited if the event was already true; this would mitigate needing a conditional "guard."
  • The ability to move multiple lines up or down.
  • The ability to have multiple numeric operations and all in an assignment, eg, set variable to 60*max(0,30-other_variable ). This would mean I dont have to create excess variables for what otherwise could be an inline operation.

Well, I hope you find the following useful. Looking forward to hearing you thoughts.


Triggers:

  Family Room Lights - Pending reports variable *changed*
  Family Room Motion motion *changed*
  Front Door contact open
  Illuminance of Virtual Illuminance reports <= 60
  Someone departs

Variables:

  datetime_current   DateTime  2022-10-18 11:30 PM
  datetime_motion    DateTime  2022-10-19 8:22 PM
  datetime_pending   DateTime  2022-10-19 5:06 PM
  seen_door_trigger  Boolean   true
  trigger            String    Family Room Motion
  wait_delay         Number    300
  was_evening_time   Boolean   true

Rule:

IF (Variable Family Room Lights - Pending = 'Turn Off') THEN

  IF (NOT Couch Lamp 1, Torch Lamp, Couch Lamp 2 any is on) THEN
    Set Family Room Lights - Pending to 'Manual Off'
  END-IF

  IF (NOT Couch Lamp 1, Torch Lamp, Couch Lamp 2 all on) THEN
    Set Family Room Lights - Pending to 'Manual On'
  END-IF

  Off: Couch Lamp 1, Couch Lamps, Torch Lamp, Couch Lamp 2
  Set Family Room Lights - Pending to 'None'

END-IF

IF (NOT Couch Lamp 1, Torch Lamp, Couch Lamp 2 any is on  AND
    Variable Family Room Lights - Pending = 'Manual On') THEN
  Set Family Room Lights - Pending to 'None'
END-IF

Set trigger to '%device%'

IF (Variable trigger *contains* 'Motion'  AND
    NOT Family Room Motion motion active) THEN
  Set datetime_motion to current date and time
END-IF

IF (Variable trigger *contains* 'Pending') THEN
  Set datetime_pending to current date and time
  Set datetime_motion to datetime_pending
  Set was_evening_time to false
  IF (Variable Family Room Lights - Pending = 'None') THEN
    Exit Rule
  END-IF
END-IF

IF (Variable trigger = 'Someone') THEN
  Set seen_door_trigger to false
  Off: Couch Lamps, Torch Lamp
  Wait for event: Couch Lamp 1, Torch Lamp, Couch Lamp 2 all turn off --> timeout: 0:00:01
  Set Family Room Lights - Pending to 'None'
END-IF

IF (Variable Family Room Lights - Pending = 'None'  AND
    Couch Lamp 1, Torch Lamp, Couch Lamp 2 any is on) THEN
  Set Family Room Lights - Pending to 'Manual On'
END-IF

IF (Variable Family Room Lights - Pending = 'None'  AND
    Someone present  AND
    Illuminance of Family Room Illuminance is <= 60  AND
    (   ( Variable trigger *contains* 'Door'  AND
          NOT Variable seen_door_trigger = true
        )  OR
        ( Variable trigger *contains* 'Illuminance'  AND
          Mode is Evening  AND
          Variable seen_door_trigger = true
        )  OR
        ( Variable trigger *contains* 'Motion'  AND
          Family Room Motion motion active  AND
          Variable seen_door_trigger = true
        )
    )) THEN
  On: Couch Lamps, Torch Lamp
  Set Family Room Lights - Pending to '%trigger%'
END-IF

IF (Variable trigger *contains* 'Door'  OR
    Variable Family Room Lights - Pending *contains* 'Door') THEN
  Set seen_door_trigger to true
END-IF

IF (Variable Family Room Lights - Pending *contains* 'Manual') THEN
  Wait for event: Mode becomes *changed*
  IF (Couch Lamp 1, Torch Lamp, Couch Lamp 2 any is on) THEN
    Set Family Room Lights - Pending to 'Family Room Motion'
  ELSE
    Set Family Room Lights - Pending to 'None'
  END-IF
END-IF

IF (Variable Family Room Lights - Pending = 'None'  OR
    Family Room Motion motion active) THEN
  Exit Rule
END-IF

IF (Mode is Evening) THEN
  Set was_evening_time to true
  Wait for event: Mode becomes *changed*
  Set datetime_motion to current date and time
END-IF

IF (Mode is Night) THEN
  Set datetime_current to current date and time
  set wait_delay to minutes of difference between datetime_current) and datetime_motion
  IF (Variable Family Room Lights - Pending *contains* 'Door'  AND
  NOT Variable was_evening_time = true) THEN
    Set wait_delay to (30 - wait_delay)
  ELSE
    Set wait_delay to (5 - wait_delay)
  END-IF
  Set wait_delay to (60 * wait_delay)
  Wait for Expression: --> timeout: wait_delaynull                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               
ELSE-IF (Illuminance of Family Room Illuminance is <= 60) THEN                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 
  Wait for event: Illuminance of Family Room Illuminance is > 60 --> timeout: 12:00:00                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               
END-IF                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               
Set Family Room Lights - Pending to 'Turn Off'       

Fwiw, here's the "Virtual Illuminance"

Triggers:

  Illuminance of Family Room Illuminance *changed*

Code:

IF (Illuminance of Family Room Illuminance is < 60  XOR
    Illuminance of Virtual Illuminance is < 60) THEN
  setLux(%value%) on Virtual Illuminance
END-IF

I don't actually need to use this sensor but I thought it seemed like a good idea to have the big rule above triggered less often. (Either way it seems to be very fast; I guess this was just playing around more than anything.)

1 Like

There are quite a few things here, but I can offer a few now with features that should already do what you want:

This is basically "Wait for condition," with most of the same options being available for both (aside from some things that are only events and don't result in meaningful state changes).

Q required expression can do this. Set one for illimanance <= 60, then have a trigger event of illimanance > 60. The expression must be true for the rule to trigger, so it will only trigger once when it rises above this threshold.

Others may comment on the rest, or I'll come back when I have more time to think about it if not. :slight_smile:

2 Likes

I hadn't thought of wait for condition as operating like this but now that you say it, I see it. Thank you!

Your idea to use a required expression is very clever! This also makes sense to me now but I wouldnt/didn't think of it. Thanks again!

1 Like

Seems like you should learn to write Groovy apps. It's not hard. Doing so would open a whole world of possibilities to you. If you want to learn, take any of the example apps in the public repo, and change them in some way. This is a more fruitful path than expecting RM to grow with further features, it's already too large as it is.

3 Likes

Great advice above. And also….

Check out Simple State Machine. If your use case is amenable to a state machine, this app works great. Otherwise, you may want stick with Rule Machine.

1 Like

Hi everyone. I think I have things much simpler now. There were two main tricks in the end:

  1. Have multiple rules set a connector variable which triggers a handler.
  2. Use a new presence state 'Someone Entered' to gate actions on what is in english equivalent to "Someone recently returned and has either opened any door or cause family room motion."

Using these two tricks I now have 4 rules which set the "state", two rules to handle the "Someone Entered" state, and one rule to handle the lights turning off.

I'm sharing the result in the hope that it helps someone else who wanted something similar.

# -----------------------------------------------------------------------
# Family Room Lights - Door

Conditions:
  Variable Family Room Lights State = 'None'  AND 
  Illuminance of Family Room Illuminance is <= 60  AND 
  Couch Lamp 1, Floor Lamp, Couch Lamp 2 all off  AND
  Someone present  AND
  Someone Entered not present

Triggers:
  Someone Entered arrives

Actions:
  Set Family Room Lights State to 'Door'


# -----------------------------------------------------------------------
# Family Room Lights - Illuminance

Conditions:
  Variable Family Room Lights State = 'None'  AND 
  Illuminance of Family Room Illuminance is > 60  AND 
  Couch Lamp 1, Floor Lamp, Couch Lamp 2 all off  AND 
  Someone Entered present

Triggers:
  Illuminance of Family Room Illuminance reports <= 60

Actions:
  Set Family Room Lights State to 'Illuminance'


# -----------------------------------------------------------------------
# Family Room Lights - Manual Override

Conditions:
  Variable Family Room Lights State(Door) ≠ 'None'  AND
  Someone Entered present

Triggers:
  Couch Lamp 1, Floor Lamp, Couch Lamp 2 all turn off

Actions:
  Set Family Room Lights State to 'Manual Override'


# -----------------------------------------------------------------------
# Family Room Lights - Motion

Conditions:
  Variable Family Room Lights State = 'None'  AND 
  Illuminance of Family Room Illuminance is <= 60  AND 
  Couch Lamp 1, Floor Lamp, Couch Lamp 2 all off  AND 
  Someone Entered present

Triggers:
  Family Room Presence motion active

Actions:
  Set Family Room Lights State to 'Motion'


# -----------------------------------------------------------------------
# Family Room Lights - Someone Entered - Arrived

Conditions:
  Someone present  AND 
  Someone Entered not present

Triggers:
  Front Door, Garage Access Door, Garage Door, Sliding Door - Left, Garage Door Opener, Sliding Door - Right any contact open
  Family Room Presence motion active

Actions:
  arrived() on Someone Entered


# -----------------------------------------------------------------------
# Family Room Lights - Someone Entered - Departed

Conditions:
  Someone present

Triggers:
  Someone Departed

Actions:
  Set Family Room Lights State to 'None'
  departed() on Someone Entered


# -----------------------------------------------------------------------
# Family Room Lights - State Handler

Variables:
  was_evening    Boolean    false

Triggers:
  Variable reports Family Room Lights State *changed*

Actions:
  IF (Variable Family Room Lights State = 'None') THEN
    Off: Couch Lamp 1, Floor Lamp, Couch Lamp 2
    Exit Rule
  END-IF

  IF (Variable Family Room Lights State ≠ 'Manual Override') THEN
    On: Couch Lamp 1, Floor Lamp, Couch Lamp 2
  END-IF

  IF (Mode is Evening) THEN
    Set was_evening to true
    Wait for event: Mode becomes *changed*
  ELSE
    Set was_evening to false
  END-IF

  IF (Mode is Night) THEN
    IF (Variable Family Room Lights State = 'Door'  AND 
       NOT Variable was_evening = true) THEN
      Wait for Expression: Family Room Presence motion inactive --> duration: 0:15:00
    ELSE
      Wait for Expression: Family Room Presence motion inactive --> duration: 0:05:00
    END-IF
  ELSE-IF (Mode in [Day, Morning]) THEN
    Wait for Expression: Illuminance of Family Room Illuminance is > 60 --> duration: 0:10:00
  END-IF

  Set Family Room Lights State to 'None'
1 Like

Just to close out the the thread, I'd like to request two features.

Request 1:
I think the rules would be cleaner--and certainly less error prone--if Hubitat had an enum type variable. Mode is a pre-existing example of this but afaict there isn't the possibility for defining others.

Request 2:
If simple rules supported setting variables and device properties then most of these rules could be offloaded to the simple rules.

1 Like

Well...that's where custom apps come into play as @bravenel mentioned earlier. The basics of Groovy are not overly difficult to learn. You have, in effect, written an app that probably would have taken less time to write in Groovy than clicking through RM.

1 Like

I agree this could be done with Groovy but for me RM is better because I find it very convenient to be able to edit the rules on my phone via the Hubitat app.

Id also be remiss to not point out that all of the rules except one are as simple as flipping a switch. The key absence which necessitated RM (or Groovy) is merely that the Simple Rules lacks the ability to set variables or device properties.

I believe that adding that ability to Simple Rules would be a welcome feature to programmers and non-programmers alike (speaking as CS PhD fwiw). However I offer this suggestion only in the interest of constructive feedback; I'm sufficiently happy with my solution...now that Ive worked through a half dozen different approaches to achieve it.

Coming from WebCore on SmartThings, this solution would be pretty straight-forward using WebCore. It certainly seems easier to edit pistons than create and edit rules in either RM or Simple Rules. I'll search this out, but does anyone on Hubitat use WebCore?

As for the above discussion, dfacto mentioned expanding Simple Rules to set variables. But does anyone use a combination of Rule Machine in combination with Simple Rules to solve problems like this, or is that just complicating this situation?

1 Like

There's a whole forum category for it:

Download the Hubitat app