Smart Blinds Control

Hello, I’m currently trying to make my blinds “smart” and I have already developed (with the excellent help of a community member) a quite good working rule for that. How does the rule work in short words: it compares the current position of the blind with the desired position, calculates the needed running time to go there, transfers the running time to a timer and the moving blind stops when the timer has expired. At the end the current position is handed over to a virtual item that will be persisted then. So far so good, everything works well and as it should. There is only one problem I wasn’t able to solve so far and I hope someone could give me some help to solve it…
The problem is as follows: if the blind gets the command to fully open (i.e. 0%) or to fully close (i.e. 100%) then it shall run without timer in order to do a “Reset” / a new “Synchronisation” with Openhab (in case the blind was occasionally moved by manual switch). Unfortunately that specific function doesn’t work as it should. The blind still moves timer-based even when it gets the 0% or 100% command. Obviously something is stil wrong with the code of the rule. Insofar any help would be very appreciated here :slight_smile:
Here’s the rule:

val Number WZ_Rollo_01_100 = 30 // time in seconds for a complete run of the blind, i.e. open --> close
var Number RolloTime_01 // running time of blind to go to a certain position
var Number RolloDifference = NULL // Delta between actual and desired position
var String RolloDirection = NULL // UP / DOWN direction

rule "WZ_Rollo_01_Alexa"
when
Item WZ_Rollo_01_Alexa received command
then
var Timer timer = null

if (receivedCommand > WZ_Rollo_01_Pos.state) {
// --> blind must go DOWN
RolloDifference = ((receivedCommand as DecimalType).intValue-(WZ_Rollo_01_Pos.state as DecimalType).intValue)
RolloDirection = "DOWN"

}
if (receivedCommand < WZ_Rollo_01_Pos.state) {
// --> blind must go UP
RolloDifference = ((WZ_Rollo_01_Pos.state as DecimalType).intValue-(receivedCommand as DecimalType).intValue)
RolloDirection = “UP”
}

if (receivedCommand == 100) {
// --> if blind closes completely it shall run without Timer
RolloDirection = "DOWN"
RolloTime_01 = NULL

}

if (receivedCommand == 0) {
// --> if blind opens completely it shall run without Timer
RolloDirection = "UP"
RolloTime_01 = NULL

}

if (RolloDirection !== NULL) {
RolloTime_01 = (RolloDifference/100) * WZ_Rollo_01_100   // calculation of moving time in seconds     
WZ_Rollo_01.sendCommand(RolloDirection)
if (RolloTime_01 !== NULL) {
	timer = createTimer(now.plusSeconds(RolloTime_01.intValue), [|
        WZ_Rollo_01.sendCommand(STOP)
    ])
}
WZ_Rollo_01_Pos.sendCommand(receivedCommand) // wir geben die aktuelle Position ans Item.

}
end

Thank you very much for your help.

Jens

You set RolloDirection to UP or DOWN, so it’s ever !== NULL.

move the

var timer = null

Change the NULL to null everywhere

to the top of your rule file and reset the timer in the timer function

if (RolloTime_01 !== null) {
    if (timer === null) {
        timer = createTimer(now.plusSeconds(RolloTime_01.intValue), [|
            WZ_Rollo_01.sendCommand(STOP)
            timer = null
        ])            
    }
}

Hm, did it as proposed but the blind still moves based on calculated time when it receives the 0% or 100% command. Why the hell the rule doesn’t skip the calculation step in these two cases? Obviously there is still something wrong… Here’s the new rule again. Can someone find the “bug” in it?

val Number WZ_Rollo_01_100 = 30
var Number RolloTime_01
var Number RolloDifference = null
var String RolloDirection = null
var Timer timer = null

rule “WZ_Rollo_01_Alexa”
when
Item WZ_Rollo_01_Alexa received command
then

if (receivedCommand > WZ_Rollo_01_Pos.state) {
RolloDifference = ((receivedCommand as DecimalType).intValue-(WZ_Rollo_01_Pos.state as DecimalType).intValue)
RolloDirection = "DOWN"

}

if (receivedCommand < WZ_Rollo_01_Pos.state) {
RolloDifference = ((WZ_Rollo_01_Pos.state as DecimalType).intValue-(receivedCommand as DecimalType).intValue)
RolloDirection = "UP"

}

if ((receivedCommand as DecimalType).intValue == 100) {
RolloDirection = "DOWN"
RolloTime_01 = null

}

if ((receivedCommand as DecimalType).intValue == 0) {
RolloDirection = "UP"
RolloTime_01 = null

}

if (RolloDirection !== null) {
RolloTime_01 = (RolloDifference/100) * WZ_Rollo_01_100
WZ_Rollo_01.sendCommand(RolloDirection)
if (RolloTime_01 !== null) {
    if (timer === null) {
  timer = createTimer(now.plusSeconds(RolloTime_01.intValue), [|
        WZ_Rollo_01.sendCommand(STOP)
  timer = null
    ])
 }
}
WZ_Rollo_01_Pos.sendCommand(receivedCommand)

}
end

Just doing a quick scan and I see the following potential gotchas.

  • You should be using else if when checking receivedCommand. In the best case you have code that executes unnecessarily (e.g. if received command is 0 two of the if statements will execute but only the last one will actually do something. In the worst case, there can be unexpected side effects that take place when both execute.
    I would rewrite the top part of the rule as:
    // initialize some variables
    // The usual convention is Items start with a cap, variables start with lowercase, constants are all caps.
    // Variables that are not referenced by other Rules and that do not need to save state between runs of the Rule should not be declared as globals
    val Number WZ_ROLLO_01_100 = 30
    var int rolloDifference = Math::abs((receivedCommand as Number - WZ_Rollo_01_Pos.state as Number).intValue)
    var String rolloDirection = if(receivedCommand > WZ_Rollo_01_Pos.state) "DOWN" else "UP"
    var rolloTime_01 = -1
    
    // exit the rule if the new value is the same as the old value
    if(rolloDifference == 0) {
        logWarn("Rollo", "receivedCommand is the same as WZ_Rollo_01_Pos")
        return;
    }

Note that we always go ahead and calculate the rolloDifference and rolloDirection as does your code above, only we do so in just two lines. We initialize rolloTime_01 to -1 instead of null. And if rolloDifference == 0 we know that we should ignore this command.

  • the line that checks whether RolloDirection is null is redundant. RolloDirections can never be null given the lines that come before it.

  • the line that checks whether RolloTime_01 !== null will always evaluate to true because you always calculate it on the previous lines.

  • there is nothing in this code that would cause it to do anything different when it receives 0 or 100. It always calculates RolloTime_01 and it always sets the Timer based on RolloTime_01. So if there is a bug, it is a bug of omission. You haven’t implemented any code to make it behave any differently for 0 or 100.