Taking A Rule To The Next Level

I have a set of three rules which help provide an indication of something being forgotten.

These three rules will light up an RGB strip light indicating that either the gate, left garage door, or right garage door have been left open or currently open.

Currently the rules look like this:

rule "GateOpen"
when
    Item GateStatus changed from CLOSED to OPEN
then
{
YeeLightStrip1.sendCommand(ON)
YeeLightStrip1_Color.sendCommand(new HSBType("0,100,100"))    
}
end
   rule "left garage door open indicator"
when
    Item LtGarageDrSensor changed to OPEN
then
{
YeeLightStrip1.sendCommand(ON)
YeeLightStrip1_Color.sendCommand(new HSBType("180,100,100"))   
}
end
rule "right garage door open indicator"
when
    Item RtGarageDrSensor changed to OPEN
then
{
YeeLightStrip1.sendCommand(ON)
YeeLightStrip1_Color.sendCommand(new HSBType("120,100,100"))   
}
end

There is corresponding rules that turn the RGB strip OFF once the doors/gate is closed.

All three of these rules will turn my RGB light strip on to a designated color to indicate which of the specified door or gate is open. This works fine if only one of these items gets opened at a time (a second door/gate opening will force the RGB strip to change to the new color but I will not see the color of the original indicator), where I need assistance is developing some logic to have the RGB light strip rotate through the colors if there are multiple items open and then be able to remove or add if the other items change their state.

So as I visualize it, the rule would create some type of color fade that would begin if two or more items are open and if more items are open it would then add to the color fade. Once items are closed the colors would be removed from the fade.

Figuring out the simple of turning on the light based upon the senor state I could do, this seems way above my current skill level and need some assistance making this happen.

Your thoughts and examples are very much appreciated.

Squid

One quick shot, before moving to work:

One first approach could be to use a Group for those items.
This way, you can handle changes for the whole group and all its sensors.
So the rule will get fired every time one Item of the group changes its state.

Within that rule, you can then iterate through all related sensors in that group and check which of them have the OPEN state.
Depending on the count of OPEN sensors and the related colors, you could then do the RGB “magic”.
(I have no idea for that rgb stuff on the quick response now. Sorry.)

I will provide some example code later, but maybe this approach helps you already get in the right direction.

Since I’m also on the move to work…
My idea on the “magic” would be alternating between the different possible colours.

1 Like

So… before i create duplicated content, which is maybe not completely right i will point you to the Topic i was thinking about earlier today, when i wrote my last post.

Often one finds a number of rules that are very similar and that all work on similar Items.

This is exactly your usecase.
You want to do nearly the same things (only with different colors) for multiple items/sensors.

This is still not a valuable answer for the color magic, but will definitely increase the maintainability.

I have a similar plan with my LED strips, so I’m interested in what you come up with! Which LED controller are you using, and are you using a binding? I have some RGB strips, but my controllers (AL-LC01) only shipped Monday, so I will be able to contribute more next week! These controllers are supposed to work with the WiFi LED binding using the stock firmware, but my plan is to use this firmware and MQTT. I think I can then send one command to the controller to continuously fade between a set of colors or to run an internal program in the controler, but I won’t know until they get here. Otherwise, I’ll modify the firmware or make use of some rules with timers.

Your post incited me to start writing some rules in preparation for the arrival of my controllers. They are tuned for your use case, but the framework will work for me too. I do something slightly similar for adjusting dimmer values based on outside lux changes. You will need to add a group gAlertTrigger, and add your trigger items to it. I started to add a reentrant lock, but the rule should never trigger more than once. Without a fade, this may be too flashy, but maybe your controller can be programmed for one? I usually stay away from Thread::sleep, but it was the quickest/easiest for a start. I will probably modify later with a timer. This is completely untested, so may fail miserably!

// Imports
import java.util.HashMap

// Global Variables
val HashMap<String,HSBType> LEDColorMap = newHashMap( //stores the color associated with an item
    "GateStatus" -> new HSBType("0,100,100"),
    "LtGarageDrSensor" -> new HSBType("180,100,100"),
    "RtGarageDrSensor" -> new HSBType("120,100,100")
)

// Rules
rule "LED alert trigger"
when
    Item GateStatus changed to OPEN
    or
    Item LtGarageDrSensor changed to OPEN
    or
    Item RtGarageDrSensor changed to OPEN
then
    YeeLightStrip1.sendCommand(ON)
end

rule "YeeLightStrip1 changed to ON"
when
    Item YeeLightStrip1 changed to ON
then
    while (YeeLightStrip1.state == ON && gAlertTrigger.members.filter(GenericItem trigger|trigger.state == OPEN).size > 0) {
        gAlertTrigger.members.filter(GenericItem trigger|trigger.state == OPEN).forEach[GenericItem item| //filters a group's items based on state OPEN, then iterates through them
            YeeLightStrip1_Color.sendCommand(LEDColorMap.get(item.getName))
            Thread::sleep(5000) //time in milliseconds to hold the color before cycling to next
        ]
    }
    YeeLightStrip1.sendCommand(OFF)
end
1 Like

Everything you want to do is pretty straight-forward and possible by applying a couple of design patterns except for this part.

Implementing a fade like this in the Rules DSL is, unfortunately, one of those things that is challenging to implement in OH’s Rules DSL and the performance of it leaves much to be desired. Just search the forum for “fade” and you will see lots of postings.

If you want a real smooth fade in and fade out between the colors you will probably want to look into the JSR223 rules as they do not have the same limitations like tying up a runtime thread and they usually perform an order of magnitude faster.

However, given that we are working with a single light we might be able to make something work.

In addition to the Groups DP Jerome linked to, I would include Design Pattern: Associated Items and perhaps Design Pattern: Encoding and Accessing Values in Rules.

Group:Number:SUM WarningDoors
Group:Color AlertColors

Contact GateStatus ... (WarningDoors) ...
Color GateStatus_Color (AlertColors) ...

Contact LtGarageDrSensor ... (WarningDoors) ...
Color LtGarageDrSensor_Color (AlertColors)

Contact RtGarageDrSensor .. (WarningDoors) ...
Color RtGarageDrSensor_Color (AlertColors)

Switch AlertLightActive
Switch LoopTimer { expire="500ms,command=OFF" }
var Number brightness = 0
val Number brightStep = 10
var Number currAlertIndex = -1
val Number holdTimes = 10
var Number holdStep = 0

// If using persistence, this rule can be disabled after the first run or
// you can also put these on your sitemap and set them manually and skip the rule entirely
rule "Populate alert color Items"
when
    System started
then
    GateStatus_Color.postUpdate(new HSBType("0,100,100"))
    LtGarageDrSensor_Color.postUpdate(new HSBType("180,100,100"))
    RtGarageDrSensor_Color.postUpdate(new HSBType("120,100,100"))
end

// Triggers when the number of open doors changes
rule "Warning Doors"
when
    Item WarningDoors changed
then
    // If any doors are open, trigger the alert light, otherwise turn off the alert light
    val alertState = if(WarningDoors.state as Number > 0) ON else OFF

    // Only send a command to start/stop looping if it is different
    if(AlertLightActive.state != alertState) AlertLightActive.sendCommand(ON)
end

rule "Alert Light activated/deactivated, start the loop"
when
    Item AlertLightActive received command
then
    if(receivedCommand == ON) {
        YeeLightStrip1.sendCommand(ON)
        LoopTimer.sendCommand(OFF) // the off command will trigger the looping rule immediately
    }
    else  {
        YeeLightStrip1.sendCommand(OFF)
        LoopTimer.postUpdate(OFF) // postUpdate will cancel the expire timer
        brightness = 0
        currAlertIndex = -1
        brightStep = 10
    }
end

rule "Light Looping Rule"
when
    Item LoopTimer received command OFF
then
    var door = null

    // If brightness is 0 it's time to move to a new open door
    if(brightness == 0) {
        currAlertIndex = currAlertIndex + 1
        val numDoors = WarningDoors.members.size
        var Number count = 0

        // get the first open door starting from the currAlertIndex
        while(door == null && count < numDoors){
            val candidateDoor = WarningDoors.members.get(currAlertIndex)
            if(candidateDoor.state == OPEN) {
                door = candidateDoor
            }
            else {
                count = count + 1
                candidateIndex = (candidateIndex + 1) % numDoors
            }
        }
    }
    // if brightness != 0 we will continue with the current door
    else {
        door = WarningDoors.members.get(currAlertIndex)
    }

    // Calculate the new brightness

    // Hold at 100 for holdTimes loop iterations before dimming down
    if(brightness == 100) {
        holdStep = holdStep + 1

        // The door closed or we are done holding the brightness at 100! Start dimming
        if(door.state == CLOSED || holdStep >= holdTimes) {
            brightStep = brightStep * -1 // start counting down
            brightness = brightness + brightStep 
            holdStep = 0            
        }
    }
    // Dimming up or down
    else {
        brightness = brightness + brightStep
    }

    if(brightness > 100) {
        brightness = 100
    }
    if(brightness < 0) {
        brightness = 0
        brightStep = brightStep * -1 // next step count up
    }

    // Get the light color
    val color = AlertColors.members.findFirst[c | c.name == door.name + "_Color"].state as HSBType

    // Send the command to the light
    YeeLightStrip1.sendCommand(new HSBType(color.hue, color.saturation, brightness))

    // Schedule the next loop iteration
    LoopTimer.sendCommand(ON)
end

Theory of operation: We put the Door Contacts into a Group and use SUM to add up the number of OPEN doors in the Group. When the number of open doors changes we trigger a rule. If the number is 0 we turn off the light, cancel the timer, and reset the global variables.

We also create a Color Item with an Associated Item name for each Door Contact and put them into a Group. finally, we create a couple of Switches, one to trigger and represent whether the alerting light is running and another one with the expire binding to drive the dimming loop.

We have four Rules:

“Populate alert color Items”: triggered by System started, the only job of this rule is to initialize the Color Items. This rule can be skipped if using persistence and you put the Color Items on your sitemap to set the color manually.

“Warning Doors”: triggered when the number of open doors changes. If the number goes to 0 the light is turned off and turn off the alerting mode. If the number is more than 0 then, if we are not already running, turn on the alerting mode.

“Alert Light activated/deactivated, start the loop”: Triggered when the AlertLightActive Item receives a command. If it receives ON it starts the dimming loop. If OFF it cancels the expire binding that drives the dimming loop and resets all the global vals.

“Light Looping Rule”: This is where the real work takes place. The way the rule works at a high level is it dims the light up for an open door, holds it for five seconds at 100, then dims it down to zero. When the rule triggers and brightness is 0, the rule moves to the next open door and dims up that door’s color, holds it for five seconds, and so on.

If you close a door while it is showing that door’s light the color will immediately start dimming. However, closing a door while the light is actively dimming has no affect on the light, it will continue to dim up and then immeidately dim down or it will continue to dim down.

It works as follows:

  • First we figure out what door to show the color for. If brightness is 0, we know it is time to move to the next door so we loop through all the doors to find the next one past the current door that is OPEN. If the brightness is not 0 we will continue with the light for the current door.

  • If the brightness is 100 and the door remains OPEN, we hold at 100 for holdTimes iterations of the rule. This will keep the light on at 100% brightness, in this case, for five seconds or until the door opens. Once we have held the light at 100% for long enough, we start to dimm the light by multiplying the dimming step by -1.

  • Then we get the Color Item associated with the current door and send it the new brightness.

I just typed this in and it is untested so beware there may be bugs. It is complicated code so I would be surprised if there are not bugs. But hopefully it will be complete enough to get you started.

2 Likes

Thank you for the code work and the detailed response on how and why. I’ve just been called in to produce a show so my schedule won’t allow for uninterrupted periods of time for me to test and focus on implementing what you have provided.

Please don’t take my silence as a lack of respect for your time and the work you have provided.

I will be unencumbered this weekend and will definitely have results and more questions.

Many thanks for all of your assistance with this and the knowledge you share within these forums.

Squid

1 Like