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.