Design Pattern(?): Conditional Sequence Trigger (CST)

Design Pattern: Conditional Sequence Trigger (CST)

Problem:
You want to execute a sequence of actions only when a complex combination of conditions is met, e.g., multiple sensor states, time of day, and presence detection.

Solution:

  • Define a trigger item or a group that represents all relevant conditions.

  • Use a rule to check whether all conditions are satisfied simultaneously.

  • If conditions are met, execute the defined sequence of actions.

  • If any condition changes and the combination is no longer satisfied, reset the sequence.


1. Rule DSL Example

Items

Group gCST "Conditional Sequence Trigger"

Switch motionSensor "Motion Sensor" (gCST)
Switch presenceSensor "Presence Sensor" (gCST)
DateTime currentTime "Current Time" (gCST)
Switch light "Light"

Rule

rule "Conditional Sequence Trigger"
when
    Member of gCST changed
then
    // Check if all conditions are satisfied
    if(motionSensor.state == ON && presenceSensor.state == ON && now.getHourOfDay >= 18 && now.getHourOfDay <= 22) {
        
        // Execute the action sequence
        logInfo("CST", "All conditions met – starting sequence")
        light.sendCommand(ON)
        
        // Additional actions can follow here
        // ...
        
    } else {
        logInfo("CST", "Conditions not met – resetting sequence")
        light.sendCommand(OFF)
        // Optionally cancel intermediate steps
    }
end

Explanation:

  • Member of gCST changed triggers the rule whenever any item in the group changes.

  • Conditions can be extended to include more sensors, states, or time windows.

  • The else branch resets actions if the combination is no longer met.


2. JSR223 Python (Jython) Example

Items

from core.rules import rule
from core.triggers import when
from core.log import logging

log = logging.getLogger("org.openhab.rules.CST")

motionSensor = ir.getItem("motionSensor")
presenceSensor = ir.getItem("presenceSensor")
light = ir.getItem("light")

Rule

@rule("Conditional Sequence Trigger")
@when("Member of gCST changed")
def conditional_sequence_trigger(event):
    hour = now.getHourOfDay()
    
    if motionSensor.state == ON and presenceSensor.state == ON and 18 <= hour <= 22:
        log.info("All conditions met – starting sequence")
        light.sendCommand(ON)
        # Additional actions here, e.g., start timers, control more devices
    else:
        log.info("Conditions not met – resetting sequence")
        light.sendCommand(OFF)
        # Optionally cancel intermediate steps

Explanation (Python/Jython):

  • now.getHourOfDay() retrieves the current hour.

  • Additional sensors or complex conditions can easily be added using and operators.

  • The action sequence can include lights, blinds, notifications, timers, etc.


Advantages of CST

  1. Clean handling of complex, interdependent conditions.

  2. Easy to extend by adding more sensors or conditions.

  3. Flexible reset mechanism if conditions are no longer met.

  4. Works in both Rule DSL and Python/Jython environments.

When using managed rules, there is a whole Conditions section where these sorts of tests can be defined.

That’s under the “but only if…” section.

See the following in the docs for details

jRuby and JS Scripting’s rule builder also have built in support for conditions similar to managed rules for text based rules as well.

As an example of using Conditions and scenes here is a rule that adds a metadata value to a lights Item if it’s changed during the day time which causes that light to no longer run using the automated criteria for the rest of that day.

configuration: {}
triggers:
  - id: "1"
    configuration:
      groupName: TOD_Lights_ON_WEATHER
    type: core.GroupStateChangeTrigger
conditions:
  - inputs: {}
    id: "3"
    configuration:
      itemName: TimeOfDay
      operator: =
      state: DAY
    type: core.ItemStateCondition
  - inputs: {}
    id: "4"
    configuration:
      type: application/javascript
      script: >-
        var close = time.Duration.ofMillis(2500);

        var todChange = items.TimeOfDay.lastStateChangeTimestamp;

        var brightnessChange =
        items.Outdoors_Weighted_Brightness.lastStateChangeTimestamp;

        var now = time.toZDT();



        console.debug('Time of day changed at ' + todChange);

        console.debug('Brightness changed at  ' + brightnessChange);

        console.debug('Now                    ' + now);

        console.debug("time of day change is close " + todChange.isClose( now,
        close ) + " brightness change is close " + brightnessChange.isClose(
        now, close));


        (!todChange.isClose( now, close ) && !brightnessChange.isClose( now,
        close));
    type: script.ScriptCondition
actions:
  - inputs: {}
    id: "2"
    configuration:
      type: application/javascript
      script: >
        if(event.itemName !== undefined) {
          console.info('Manual light trigger detected, overriding the light for ', event.itemName);
          items[event.itemName].replaceMetadata('LightsOverride', 'true', []);
        }

        else {
          console.warn('Manual triggers do not work for this rule!');
        }
    type: script.ScriptAction

In the UI it appears as:

The rule triggers when a member of the TOD_Lights_ON_WEATHER changes state.

There are two conditions. The Time of Day (see Time Based State Machine [4.0.0.0;5.9.9.9] - #131 by rlkoshak) is “DAY”. The second condition is a bit of code that checks to see if enough time has passed since the Items that cause the lights to change based on automation have changed. The idea is if enough time has passed, the change to the light was made manually so the automation is overridden.

The action sets metadata on the Item to indicate it should no longer be controlled by the automation.

In addition, there is the concept of scenes for managed rules. Scenes are basically just a list of Items and what you want them to be commanded to when the scene is activated. You can define your sequence of events as a scene and then create a rule that implements the conditions and triggers which calls the scene.

You can find Scenes in MainUI → Settings.

A scene is just a rule like any other so any rule’s language that supports calling other rules can be used. Sorry Rules DSL does not support this.

An example of a scene is as follows:

items:
  FamilyRoomOutlet: ON
  PeanutPlug4_Switch: ON
  FrontRoomOutlet: ON
  PatioLights_Switch: ON
  FrontPorchLight: ON
  FamilyRoomLampShelves: ON
triggers: []
conditions: []

In the UI it appears as:

The rule that calls this scene is:

configuration: {}
triggers:
  - id: "1"
    configuration:
      itemName: TimeOfDay
    type: core.ItemStateChangeTrigger
conditions: []
actions:
  - inputs: {}
    id: "2"
    configuration:
      type: application/javascript
      script: |-
        var mapping = { "MORNING":   "lights-morning",
                        "DAY":       "lights-day",
                        "AFTERNOON": "lights_afternoon",
                        "EVENING":   "lights-evening",
                        "NIGHT":     "lights_night",
                        "BED":       "lights_bed" };
        var sceneID = mapping[items.TimeOfDay.state];

        console.info('Calling time of day light scene ' + sceneID);
        rules.runRule(sceneID, { }, true);
    type: script.ScriptAction

I was not consistent in how I named my scenes, so I have to add a mapping between the time of day state and the rule uid for the scene. IF you are more careful than me, you could avoid that and the action becomes a one liner:

rules.runRule('lights_'+event.newState.toString().toLowercase(), { }, true);

Note that the true at the end tells OH to use the rule conditions defined on the called rule.

This topic was automatically closed 41 days after the last reply. New replies are no longer allowed.