0

If I have not explained this properly please help me to correct my question. My question may be related to Java timer, or it may be related to general problem solving.

Dear reader, you don't need to understand what OpenHAB is, what an OpenHAB rule is, or indeed what MQTT does. But I'll use these terms anyway to set the scene of my question.

In OpenHAB I have a rule that responds to messages published to an MQTT topic. When I turn a light dimmer down, an burst of "down" MQTT messages are sent to a broker. Each message fires an OpenHAB rule which reads the current value of the light and subtracts 1 then writes it back.

To ensure the rule does not fire concurrently in different threads (thus preventing the light from dimming at the correct rate), a colleague of mine recommended I add lock.lock like this:

rule "ArduinoBedroomVector"
    when
        Item Bedroomvector received update
    then
        lock.lock()
        try {
          // rules here
          var Number lightcircuit1level = BedroomCeilingLight.state as DecimalType
          switch(Bedroomvector.state) {
            case "light_1_up" : {
              lightcircuit1level = lightcircuit1level + 3
              if(lightcircuit1level>100) lightcircuit1level = 100
              sendCommand(BedroomCeilingLight, lightcircuit1level);
            }
            case "light_1_down" : {
              lightcircuit1level = lightcircuit1level -3
              if(lightcircuit1level<0) lightcircuit1level = 0
              sendCommand(BedroomCeilingLight, lightcircuit1level);
            }
          }
        }
        finally {
          lock.unlock()
        }
    end

and this worked a treat.

Now my actuator doesn't miss "down" messages.

However because the actuator takes a little while to respond to each individual message (it's running over 433MHz RF transmission, each RF message message takes .5 seconds to send), the dimming commands to the actuator are queuing up.

So I need to introduce a way of checking to see whether the rule ran in the last e.g. 0.6 seconds. If it did, then increment the value, but don't send the command. If it didn't, increment the value and finally send the command.

For example this means I could dim the light up and down continuously and as long as I don't stop, the light level wouldn't change. Then as soon as I decide to stop dimming up or down, the light level will be finalised and set.

Better still a timing "rule" that would allow me to continuously change the level but only set the light level every 0.5 seconds according to what the latest level is.

I'm sure this is just a case of creating a timer which is checked at the time the rule is run, but I can't get my head around when the timer should be created and checked. This is probably blindingly obvious to many people.

hazymat
  • 404
  • 1
  • 6
  • 20

1 Answers1

0

Don't be too hard on yourself, it isn't blindingly obvious.

My approach is to keep a timestamp of the most recent execution of the rule and add an if statement before calling sendCommand to check if at least .6 seconds has passed.

However, there is one edge case where if the last receipt of the a command is before the .6 seconds have passed the new value will never be sent so we need to set a timer to get that last value published. But then we need to clean up the timer so it doesn't fire if we received a new command after the timer was set but not yet fired. Edge cases are messy.

var lastExec = now.millis
var Timer timer = null

rule "ArduinoBedroomVector"
when
    Item Bedroomvector received update
then
    lock.lock()
    try {
      // rules here
      var Number lightcircuit1level = BedroomCeilingLight.state as DecimalType
      switch(Bedroomvector.state) {
        case "light_1_up" : {
          lightcircuit1level = lightcircuit1level + 3
          if(lightcircuit1level>100) lightcircuit1level = 100
          // Wait to send the new value
        }
        case "light_1_down" : {
          lightcircuit1level = lightcircuit1level -3
          if(lightcircuit1level<0) lightcircuit1level = 0
          // wait to send the new value
        }
      }

      // if more than .6 seconds have passed since the last time the value was sent
      if((now.millis - lastExec) > 600){
        // cancel the timer if one is already set
        if(timer != null) {
          timer.cancel
          timer = null
        }
        sendCommand(BedroomCeilingLight, lightcircuit1level)
        lastExec = now.millis
      }
      // its too soon to send the update, set a timer
      else {
        // cancel the timer if one is already set
        if(timer != null) {
          timer.cancel
          timer = null
        }
        // set a timer to go off in what is left of the .6 secs since the last update
        var t = now.plusMillis(600) - lastExec
        timer = createTimer(now.plusMillis(t), [|
          sendCommand(BedroomCeilingLight, lightcircuit1level)
          lastExec = now.millis 
          // beware, there could be a race condition here as the timer
          // will execute outside of the lock. If the rule executes 
          // at the same time as the timer the most recent value of
          // lastExec may be overwritten with an older value. It should 
          // happen very rarely though and may not be a problem.
        ]
      }
    }
    finally {
      lock.unlock()
    }
end