Problem Statement
For my holiday lights, I’m using LED strips with over 30 different effects. Not all of these effects fit the theme I’m looking for, so I would like to be able to select which effects are shown and which will be skipped. Further, I wanted to have the effects “roll” from one to the next after a predetermined amount of time, so I don’t have to manually switch them. While I had rules in place last year that did this for me, I didn’t like that they weren’t easily configurable without rewriting. So, for this year, I decided to create something more dynamically configurable, and the idea of a sequencer popped into my head. Sequencers are powerful automation devices, allow a variable number of steps, and great configurability. I didn’t see anything that fit the bill in the forums, so I decided to make my own.
Solution
My implementation of a sequencer for openHAB is shown below:
The sequencer allows:
-Starting by pressing the “Start” PB. The Current Step selected will be the step from which the sequencer begins
-Stopping/Resetting the sequencer by pressing the “Stop” PB. The Current Step will be reset to 0 (and starts from step 1 on next restart or then-selected Current Step)
-Changing the Current Step (either prior to sequencer start, or while the sequencer is running)
-Changing the Step Length
-Selecting an action/value for each step. Selecting a value of “End” determines the last step before the sequence is restarted from step 1 again.
openHAB Items, Rules and Sitemap entries
Below are the sample items, rules and sitemap files which implement the sequencer. I’ve included comments that should be self-explanatory to allow the user to adapt the sample to their own configuration, step values, etc…
Note: This is pretty much version “1.0”, also known in the software industry as “hey it worked, publish it!” I wanted to get it out in case anyone else can use them for this holiday season, but there are several “known issues” that need to be corrected for stability improvements:
1. Not selecting an “End” value for at least one step will cause the sequencer to “run away” and error out when it gets past step 10.
2. Better error/null checking should be implemented in general, so NULL steps and rule errors can be avoided
3. There’s probably a more efficient way of implementing the rules.
4. Need to handle canceling any running timers when “Stop/Reset” is pressed (so the current step isn’t incremented after the “Reset” rule sets it to 0, and the next sequencer operation doesn’t start with step 2 instead of 1)
That said, this definitely works for me and I have been successfully using it to rotate through a set of effects for my holiday light LED strips…but, I have some other ideas for possible improvement:
- Add “Shuffle” switch, which will make the sequencer pick a random step from the available ones (NULL or END steps would be automatically skipped until a good step is found). This would be cool for a “Halloween” style effect, with random lights defined in each step, and the output rule turning them on and off for the duration of the step (e.g. step duration of 1 second)
- Add selectable “interlocks” to each step, so the sequencer doesn’t increment until the interlock is latched (think, beer brewing, where each step has a process variable, like temperature of the mash, fill level, etc…)
sequencer.items
Group Sequencer_Step "Sequencer Steps"
Switch Sequencer_Start "Sequencer Start"
Number Sequencer_CurrentStep "Current Step [%d]"
Switch Sequencer_Reset "Sequencer Stop/Reset"
Number Sequencer_Step_Time "Sequencer Step Time"
Number Sequencer_Step1_Value "Sequencer Step 1" (Sequencer_Step)
Number Sequencer_Step2_Value "Sequencer Step 2" (Sequencer_Step)
Number Sequencer_Step3_Value "Sequencer Step 3" (Sequencer_Step)
Number Sequencer_Step4_Value "Sequencer Step 4" (Sequencer_Step)
Number Sequencer_Step5_Value "Sequencer Step 5" (Sequencer_Step)
Number Sequencer_Step6_Value "Sequencer Step 6" (Sequencer_Step)
Number Sequencer_Step7_Value "Sequencer Step 7" (Sequencer_Step)
Number Sequencer_Step8_Value "Sequencer Step 8" (Sequencer_Step)
Number Sequencer_Step9_Value "Sequencer Step 9" (Sequencer_Step)
Number Sequencer_Step10_Value "Sequencer Step 10" (Sequencer_Step)
// Add more step items if needed
Number Sequencer_Output "Sequencer Output [%d]" // This is the action item (which receives each step value as command)
sequencer.rules
import org.eclipse.smarthome.model.script.ScriptServiceUtil
var Integer seqCurrentStep = 0
var Integer seqSequencerActive = 0
var Integer seqSequencerMaxStep = 10 // Number of Steps defined in the sitemap
/* Handle Sequencer Start */
rule "Sequencer Start Rule"
when
Item Sequencer_Start received command ON
then
seqCurrentStep = (Sequencer_CurrentStep.state as DecimalType).intValue
seqSequencerActive = 1
logInfo("Sequencer", "Starting Sequencer Timer after Start button with CurrentStep @ " + Sequencer_CurrentStep.state.toString)
createTimer(now.plusMinutes((Sequencer_Step_Time.state as DecimalType).intValue))[|
logInfo("Sequencer", "Sequencer Timer elapsed after " + Sequencer_Step_Time.state.toString + " minutes. Incrementing" )
sendCommand(Sequencer_CurrentStep, (Sequencer_CurrentStep.state as DecimalType).intValue + 1)
]
Sequencer_Start.sendCommand(OFF)
end
rule "Update command when Current Step changes"
when
Item Sequencer_CurrentStep received update
then
logInfo("Sequencer", "Received Current Step Update")
if (seqSequencerActive == 1) //sequencer active, process command
{
logInfo("Sequencer", "Sequencer Active, sending command")
val n = ScriptServiceUtil.getItemRegistry.getItem("Sequencer_Step" + Sequencer_CurrentStep.state.toString + "_Value")
logInfo("Sequencer", n.name + " Item state is " + n.state.toString)
if((n.state == 0) || (n.state == NULL) || (seqCurrentStep > seqSequencerMaxStep)) // If we see an "End Step" value, a NULL or we're at the last step, reset currentStep back to the top
{
Sequencer_CurrentStep.sendCommand(1)
}
else
{
/* Send command value stored in current Step Selection box */
sendCommand( Sequencer_Output, n.state as Number ) // Replace Sequencer_Test_Item with actual action item
/* Start timer for next step */
createTimer(now.plusMinutes((Sequencer_Step_Time.state as DecimalType).intValue))[|
logInfo("Sequencer", "Sequencer Timer elapsed after " + Sequencer_Step_Time.state.toString + " minutes. Incrementing" )
sendCommand(Sequencer_CurrentStep, (Sequencer_CurrentStep.state as DecimalType).intValue + 1)
]
}
}
else // Sequencer is not active, skip out
{
//NOP
}
end
rule "Sequencer Stop/Reset Rule"
when
Item Sequencer_Reset received command ON
then
seqCurrentStep = 0
Sequencer_CurrentStep.sendCommand(0)
logInfo("Sequencer", "Resetting Sequencer timer to step " + seqCurrentStep.toString)
seqSequencerActive = 0
Sequencer_Reset.sendCommand(OFF)
end
rule "Handle Sequencer Output" //This is the ONLY rule the end user should have to change (use the value stored in Sequencer_Output to do proper tasks in your system)
when
Item Sequencer_Output received update
then
//LEDStrip_Group1_FX.sendCommand(Sequencer_Output.state.toString)
//LEDStrip_Kitchen_Cabinet_FX.sendCommand(Sequencer_Output.state.toString)
//... Handle actual devices based on the output value currently stored in the Sequencer_Output
end
sequencer.sitemap
Text label="Sequencer"
{
Frame{
Switch item=Sequencer_Start mappings=[ON="ON"]
Selection item=Sequencer_CurrentStep mappings=[0="Start",1="1",2="2",3="3",4="4",5="5",6="6",7="7",8="8",9="9",10="10"] //Add more values if more steps needed
Switch item=Sequencer_Reset mappings=[ON="ON"]
Selection item=Sequencer_Step_Time mappings=[1="1 min",15="15 mins",30="30 mins",45="45 mins",60="60 mins"] //Modify timer values as needed
}
Frame{
Selection item=Sequencer_Step1_Value mappings=[0="End",1="FX 1",2="FX 2",3="FX 3",4="FX 4"] //Modify Step values as needed (this is what will be sent to the action item)
Selection item=Sequencer_Step2_Value mappings=[0="End",1="FX 1",2="FX 2",3="FX 3",4="FX 4"]
Selection item=Sequencer_Step3_Value mappings=[0="End",1="FX 1",2="FX 2",3="FX 3",4="FX 4"]
Selection item=Sequencer_Step4_Value mappings=[0="End",1="FX 1",2="FX 2",3="FX 3",4="FX 4"]
Selection item=Sequencer_Step5_Value mappings=[0="End",1="FX 1",2="FX 2",3="FX 3",4="FX 4"]
Selection item=Sequencer_Step6_Value mappings=[0="End",1="FX 1",2="FX 2",3="FX 3",4="FX 4"]
Selection item=Sequencer_Step7_Value mappings=[0="End",1="FX 1",2="FX 2",3="FX 3",4="FX 4"]
Selection item=Sequencer_Step8_Value mappings=[0="End",1="FX 1",2="FX 2",3="FX 3",4="FX 4"]
Selection item=Sequencer_Step9_Value mappings=[0="End",1="FX 1",2="FX 2",3="FX 3",4="FX 4"]
Selection item=Sequencer_Step10_Value mappings=[0="End",1="FX 1",2="FX 2",3="FX 3",4="FX 4"]
}
Frame{
Selection item=Sequencer_Output mappings=[0="End",1="FX 1",2="FX 2",3="FX 3",4="FX 4"]
}
}
If you find a use for this, let me know in a post below!