How to poll state without overwriting actions in rules?

Hi,
I’m new to code my own rules, so please forgive me if I ask something obvious. I recently installed a Easee Home charger, it has a nice cloud based REST API which I’m trying to use for controlling the charger. Basically I’d like to start/stop charging from HomeKit, and also display information such as charging output, total kWh etc.
What I have done is:
(1) A rule that keeps polling the latest state from the charger, it polls every 30 seconds.
(2) A rule the reacts to a dimmer (switch) when I turn on, it sends the command to start charging, when I turn off it sends the command to stop charging.
The problem I’m experiencing now is sometimes, rule (1) kicks in before rule (2) completes the action. So I have the below scenario:

  • I turn on the charger from OpenHAB GUI
  • The action triggers rule (2) to send a command to start charging
  • The command above is async (to make it responsive in HomeKit).
  • Before or just after the charger takes the command and handshake with the car, the polling kicks in and overwrites the switch to off
  • Now it triggers a command to stop charging.

The issue is caused by rule (1) and (2) are disconnected, when rule (1) polls the value it’s not aware maybe an action is being performed by rule (2). And given the REST API and handshake process it takes 10-20 seconds for the action to complete. A possible solution would be to ignore any change to the switch for X seconds after an action is performed, this requires a global variable to record the lastes timestamp when the action was performed. I’m not sure how this can be implemented as OH rules, can somebody give me a hand? Any suggestion is appreciated!

Can we go back a step, why is your polling routine destructive?

Can you insert your “command” into the poll activity, and cause a “hurry up” poll reschedule?

I poll the state of the switch because I can also manually start/stop charging and I’d like to have state in sync. Sorry I’m not sure how to do the “hurry up” poll reschedule. The easiest thing come to my mind is to record the last timestamp of the action, and ignore update for X seconds, but even that I have no idea how to implement…

How are you writing your rules? Which language? Text files or only UI?

The most universal solution is to use a DateTime Item. Update the Item with the current time in rule 1. In rule 2 skip the poll if the Item is too recent.

Hi I use the text file and looks like it’s based on some scripting language similar to Java but now sure I fully understand how it works though. I’m trying to do this:

  1. Define a var as global variable: var lastActionTimestamp
  2. Compare now - 30 second is before lastActionTimestamp:
    if (!new DateTimeType(now().minusSeconds(30)).isAfter(lastActionTimestamp.getZonedDateTime)){
    //wait for the grace period, do nothing
    }
  3. Assign lastActionTimestamp = new DateTimeType()

But the logic doesn’t seem to work…

Have you looked in the logs at all? What do you see there?

Where did you find that sort of syntax. Break it down token by token…:

Token Meaning
! negate the following boolean
new DateTimeType( ) Create a new DateTimeType Object. Here’s problem 1, a DateTimeType isn’t a boolean. !new DateTimeType() is nonsense.
now() Problem 2, if using .rules files now is a variable, not a function I think. Though I could be wrong. Rules DSL lets you call a function that doesn’t take arguments without the (). But typically you’d just use now.
.minusSeconds(30) Subtract 30 seconds from now and return a new ZonedDateTme.
isAfter() Returns true if the now.minusSeconds(30) is at a later time than the passed in ZonedDateTime. Problem 3 here is that this boolean is being passed to new DateTimeType(). new DateTimeType(true) is nonsense.
lastActionTimestamp.getZonedDateTime Problem 4, you didn’t declare lastActionTimestamp with a type nor with an initial value. Therefore it’s going to be treated as an Object and Object doesn’t have a getZonedDateTime.

Most of the problems above should result in errors in the logs.

You simply can not successfully write rules without watching the logs. And if you are looking at the logs, we simply can not successfully help you unless you tell us what’s in the logs. Posting your actual Rule is usually pretty important too because often the problem isn’t where you think it is.

Please use code fences.

```
code or logs go here
```

After fixing all the problems identified the code should look something like

var lastActionTimestamp = now.minusDays(365) // initialize it to some time far in the past so the first time the rule runs it is not skiped

rule "Some rule"
when
    // some triggers
then
    if(now.minusSeconds(30).isAfter(lastActionTimestamp)) {
        return;
    }
    // rest of rule
end
1 Like

Hi, thanks a lot! I basically googled and glue together all the code without understanding it. With you help I think I manage to get it to work.
Just one additional question if you don’t mind:
Can I use rules to validate input so if the input is not allowed I can reset to the old value?
For example, I can only switch on charging when the car is connected, so I’d like the rule to reset to switch off when the car is disconnected. The rule I have looks like:

rule "EaseeBox Charging Power Pct Changed"
when
    Item EaseeBox_chargingPowerPct changed
then
    //If I turn on the dimmer, but the state != 2, I want to reset to the old value
    if(prevPct == 0 && newPct > 0 && state != 2 ) 
    {
      EaseeBox_chargingPowerPct.postUpdate(previousState)
    }
    else 
    {
      ......
    }
end

I got this error in log which doesn’t tell me much:

2021-09-08 17:58:42.478 [ERROR] [internal.handler.ScriptActionHandler] - Script execution of rule with UID 'EaseeBox-4' failed: For input string: "{"status":429,"traceId":"|554cc532-4d2c2b22e8db6edb."}" in EaseeBox

Rules are Turning complete. You can do anything that can be done by computers in them. It doesn’t always make sense but in this case it that should be possible. However you have to be careful about creating infinite loops. Consider…

  1. EaseeBox_chargingPowerPct changes to let’s say “FOO”
  2. (prevPct == 0 && newPct > 0 && state != 2) is true
  3. EaseeBox_chargingPowerPct is update to it’s previous state which was let’s say “BAR”
  4. EaseeBox_chargingPowerPct changes to “BAR”
  5. The rule triggers again because the Item changed
  6. the condition still evaluates to true
  7. The Item is updated to it’s previous state which was “FOO”

Now we are stuck in a loop with the Item updating between FOO and BAR as fast as your system can manage until somehow line 2 evaluates to false.

You’ve not shown where prevPct, newPct, and state are defined so I can’t say if this is likely or not.

Well, either you cut off the line so we don’t know the full name of the Item involved here, or you have an Item named EaseeBox which you attempted to update? with the quoted JSON String but that wasn’t allowed. Maybe EaseeBox isn’t a String Item?

Ok, I see the problem here, I think the code I have will result in infinite loops.
Let me try to explain what I’m trying to do:

  1. I defined a dimmer item for controlling the charger - right now I only want on/off actions, but in the future I’d like to be able to adjust charging power (kW), hence I choose the dimmer type instead of a normal switch
Dimmer EaseeBox_chargingPowerPct "EaseeBox: Charging Power Pct [%d %%]" (gEaseeBox)

In my rules (1) I poll the state from the charger every 30 seconds, and I update the dimmer with the latest charging power. I device the charging power by 11 because the charge can max support 11kW.

      var powerPct = chargingPower/11*100
      EaseeBox_chargingPowerPct.postUpdate(powerPct)

In my rule (2) I listen to any change to the dimmer:

  • If the dimmer value is changed between two positive numbers, do nothing
  • If the dimmer is changed from 0, and the charger is in state 2 (ready to charge), I send a command to start charging.
  • If the dimmer is changed to 0, and the charger is in state 3 (charging), I send a command to stop charging.
  • Otherwise the change is not allowed it’s not possible to start/stop charging when the charger is in wrong state.
rule "EaseeBox Charging Power Pct Changed"
when
    Item EaseeBox_chargingPowerPct changed
then
    var Number prevPct = previousState as Number
    var Number newPct = EaseeBox_chargingPowerPct.state as Number
    ......
          if(prevPct != 0 && newPct != 0)
          {
             // Do nothing 
          } 
          else if(prevPct == 0 && newPct > 0 && state == 2 )
          {
            // Start charging, set last action timestamp
          } 
          else if (prevPct > 0 && newPct == 0 && state == 3)
          {
            //Stop charging, set last action timestamp
          }
          else
          {
            logInfo("Info", "EaseeBox: invalid action, no change")
            //EaseeBox_chargingPowerPct.postUpdate(previousState)
            //Need to find a way to revert the state to the previous state without triggering this rule again
          }
        }
      }
end```

Anybody?

Well, as I said, either the error you posted is truncated and we need the full error, or you have an Item named EaseeBox that is being updated or commanded and you’ve not shown that code.

The error has nothing to do with potential infinite loops.

We can offer no more help without either the full error message or the code that the error message is referring to.

Hi, Ok I don’t know what do you mean by error is truncated? I copied the whole line here, and there is no exception stack trace, so I’m not sure what more can I do with the log.
Anyways, I appreciate your help and I think I understand it better now. Just want to ask if there is a way to make what I want without introducing infinity loop.

I’m obviously not seeing what you are seeing but

2021-09-08 17:58:42.478 [ERROR] [internal.handler.ScriptActionHandler] - Script execution of rule with UID 'EaseeBox-4' failed: For input string: "{"status":429,"traceId":"|554cc532-4d2c2b22e8db6edb."}" in EaseeBox

isn’t the full line. Do you have a scroll bar at the bottom? Maybe it wrapped to the next line?

As for the infinite loop, you can usually tell if you walk through it line by line. The code you posted above won’t result in an infinite loop because you are not updating the Item which causes the rule to trigger again.

Hi, sorry I didn’t make it clear. In the code I posted I commented out the following line which caused the infinity loop:

EaseeBox_chargingPowerPct.postUpdate(previousState)

I basically would like to change the state back to the previousState without causing the infinity loop.
The log is the full line, I think you can scroll left/right to view it.

Have you tried it? Work though it line by line on paper. Or run it yourself and see what happens. One of the reasons they called it computer “science” is because when we write code we are constantly running experiments to see how it works.

I promise your computer won’t explode if you do end up in an infinite loop.

Frankly, we don’t have enough information here to easily tell you how it will behave. Not without making a bunch of guesses and assumptions about how the values are changing.

You have the full code. You have the full logs. You know how it’s really supposed to work. You are in the best position to determine if it works like you want it to.

All I can really say is it might result in an infinite loop. But I also might not depending on how the values of the Item behave. At a minimum the rule will trigger again when you update it, but that might make it hit one of the other conditions and not result in the loop.

I scrolled all the way. The log statement as posted ends with

in EaseeBox

There is nothing you’ve posted so far named just “EaseeBox” so either the error message is truncated or wrapped and you didn’t post the full error, or you do have something named “EaseeBox” somewhere in code you’ve not posted.

I tried it and I did get into a loop (not infinite because the polling kicks in), and I don’t think the error I posted means anything important in my knowledge.
I’d like to focus on the question: is there anyway to update the state without hitting the rule again?
Maybe the answer is no in which case I will have to live with it.

Not the way you’ve written it. You have the following choices:

  • received command: will trigger the rule only when the Item is commanded, but the Item will not have changed state in response to that command yet and there won’t be a previousState and newState variables

  • received update: will trigger when the Item is updated, even updated to the same state

  • changed: will trigger when the Item changed state.

You can’t use received command because you can’t see the new state and previous state.

You can’t use received update because when you postUpdate that will trigger the rule again.

You can’t use changed because your postUpdate will result in a change and the rule will trigger again.

Ok, thanks a lot for the explanation, appreciated your help!

Maybe it would help if we understood why your “read poll” affects (“writes to”) the status of any charger feature?

Read poll keep the state in sync with the charger.
Write performs user action to start/stop charging. The charging can stop by itself due to completion or error, and therefore I need to poll the state.