Elegant may not be much of an option here but it should be doable with a cascading timer I think. Design Pattern: Cascading Timers. I never really consider timers to be elegant but they sure are handy.
I do have an elegant solution in JavaScript and Python with my Gatekeeper library. But that’s not going to work in Rules DSL.
Indeed, sleep() won’t work if you need to be cancel it on a new command because in OH 3 only one instance of the rule can run at a time so the new command won’t start processing until the previous one completes processing.
From the description these commands are being sent to the same Item though and that will be the challenge. How do we tell the difference between a command that came from somewhere, or the repeated command generated by this rule? Do you need to support the case where the Item receives and ON and starts looping and sending ON again, and then something else sends ON and the loop needs to stop and start over again?
This is really tricky to do with only one Item. But with a proxy it seems more doable. The initial command that triggers the rule comes from the proxy. Then as we send and repeat the command to the “real” Item the rule doesn’t get retriggered and we don’t have to figure out how to tell the difference between a rule generated command and an external to the rule generated command.
So maybe something like this would work (just typing this in, there will be typos):
var Timer repeatTimer = null;
rule "Repeat command"
when
Item ProxyItem received command
then
// Cancel the looping timer if it's already running
repeatTimer?.cancel()
repeatTimer = createTimer(now.plus(<initial delay>, [ |
if(<not done>){
RealItem.sendCommand(receivedCommand)
repeatTimer.reschedule(now.plus(<next delay>)
}
else {
repeatTimer = null
}
]
end
OK, that’s one problem solved. The next problem is to handle that list of different delays. Is it safe to assume that “t” is the same each time through the “n” loops? Put another way, we don’t need a different “t” for each iteration of the loop?
If so it could be as simple as:
var Timer repeatTimer = null;
rule "Repeat command"
when
Item ProxyItem received command
then
// Cancel the looping timer if it's already running
repeatTimer?.cancel()
var loopCount = LoopCount.state as Number
val delaySecs = LoopDelay.state as Number
repeatTimer = createTimer(now.plus(<initial delay>, [ |
if(loopCount > 0){
RealItem.sendCommand(receivedCommand)
loopCount = loopCount - 1
repeatTimer.reschedule(now.plus(delaySecs)
}
else {
repeatTimer = null
}
]
end
To make this generic you’ll probably want to use Associated Items DP. If you need a variable delay between each iteration you’ll need to devise some sort of math algorithm to calculate it or a look-up table.
That should get you pretty close and it should be pretty stable and reliable, assuming that I understand the requirements correctly. If not I can explore other options.