Timer do not cancel properly

Hi Folks,

I’ve the following situation:
An ESP32 detects BLE key trackers and publishes their MAC-adresses and RSSI via MQTT in JSON-String every 10 seconds like

{
"ac:23:3f:26:46:1c":-72,
"ff:ff:30:03:a9:93":-84,
"ff:ff:af:50:14:8f":-83
}

To figure out if a certain key tracker is present, a rule simple checks if the json-string contains its MAC-address and switches a personal presence proxy Item ON. Else OFF.
Occasionally, ESP32 misses to detect one or other tracker.
To avoid switching the proxy OFF an ON again, a timer should forces the rule waiting for 2min before switch the proxy OFF. But if the MAC-address appears again the timer should be canceled.

Two identical rules for two different trackers with this logic looks like this:

var Timer presenceTimerPerson1 = null
var Timer presenceTimerPerson2 = null

rule "Person1PresenceProxy from presenceString"
when
    Item presenceString received update
then
    // MAC address in JSON-string from ESP32, turn presence ON
    if(presenceString.state.toString.contains("ff:ff:af:50:14:8f")){
        if(Person1PresenceProxy.state == OFF){
            Person1PresenceProxy.sendCommand(ON)
        }
        presenceTimerPerson1?.cancel
    }
    // MAC address not in JSON-string from ESP32, wait for 2min, check again, switch OFF
    else{
        presenceTimerPerson1 = createTimer(now.plusMinutes(2), [ |
            if(!presenceString.state.toString.contains("ff:ff:af:50:14:8f")){
                if(Person1PresenceProxy.state==ON){
                    Person1PresenceProxy.sendCommand(OFF)
                }
            }
            presenceTimerPerson1 = null
        ])
    }
end

rule "Person2PresenceProxy from presenceString"
when
    //Time cron "0 0/1 * ? * * *" or
    Item presenceString received update
then
    // MAC address in JSON-string from ESP32, turn presence ON
    if(presenceString.state.toString.contains("ff:ff:18:00:91:ff")){
        if(Person2PresenceProxy.state == OFF){
            Person2PresenceProxy.sendCommand(ON)
        }
        presenceTimerPerson2?.cancel
    }
    // MAC address not in JSON-string from ESP32, wait for 2min, check again, switch OFF
    else{
        presenceTimerPerson2 = createTimer(now.plusMinutes(2), [ |
            if(!presenceString.state.toString.contains("ff:ff:18:00:91:ff")){
                if(Person2PresenceProxy.state==ON){
                    Person2PresenceProxy.sendCommand(OFF)
                }
            }
            presenceTimerPerson2 = null
        ])
    }
end

The problem I have is, that it works as expected for Person2PresenceProxy but not for Person1PresenceProxy. It happens time to time, that it switches OFF an ON within less than 10 seconds. No matter if I let the timer wait for 2min or 5min.

It seems to me that the presenceTimerPerson1 does not cancel properly. But I’ve no idea why. And why not the other has the same problem, too.

There is no hint in the logs like warnings or errors as I can see. Can anyone help me out?

Make sure your nearly identical rules actually have different names
rule “xxx”

Actually I do. Person1 and Person2 are replacements for the names of my wife and me.

First of all, there is absolutely no reason why this needs to be duplicated in two different Rules. So let’s just take the second one that works and make it generic so it can work for all your Items.

The first problem is to create a mapping between the MAC IDs and an Item name. Since you are using Rules DSL our options are a little limited. For this I think the easiest will be us a Map.

import java.util.Map

Map<String, String> devices = createHashMap("ff:ff:18:00:91:ff" -> "Person2PresenceProxy",
                                            "ff:ff:af:50:14:8f" -> "Person1PresenceProxy")

Just add more entries to devices when you add new persons to track.

Next, since we are making this generic, we don’t know ahead of time how many people we are tracking so we can’t statically define the timers. So we will use a Map for that too.

import java.util.Map

Map<String, String> devices = createHashMap("ff:ff:18:00:91:ff" -> "Person2PresenceProxy",
                                            "ff:ff:af:50:14:8f" -> "Person1PresenceProxy")

Map<String, Timer> timers = createHashMap

Now for the Rule. We will loop through the keys of devices to see if it’s present in the string. If so we can send the command to the corresponding Item. If not we will create a timer and wait two minutes before completely setting it to OFF.

How important is it that you don’t send duplicate ON commands? If you don’t care the code is really simple. If not, we need to use Design Pattern: Associated Items.

import java.util.Map
import org.eclipse.smarthome.model.script.ScriptServiceUtil

Map<String, String> devices = createHashMap("ff:ff:18:00:91:ff" -> "Person2PresenceProxy",
                                            "ff:ff:af:50:14:8f" -> "Person1PresenceProxy")

Map<String, Timer> timers = createHashMap

rule "PresenceProxy from presenceString"
when
    Item presenceString received update
then
    // For each device
    devices.keys.forEach[ mac |

        personProxy = ScriptServiceUtil.getItemRegistry.getItem(devices.get(mac))

        // mac is in presenceString
        if(presenceString.state.toString.contains(mac)) {
            if(personProxy.state == OFF) personProxy.sendCommand(ON)
            timers.get(mac)?.cancel()
            timers.put(mac, null)
        }

        // mac isn't in presenceString
        else{
            if(timers.get(mac) != null) timers.get(mac).cancel
            timers.put(mac, createTimer(now.plusMinutes(2), [ |
                if(!presenceString.state.toString.contains(mac)){
                    if(personProxy.state != OFF) personProxy.sendCommand(OFF)
                }
                timers.put(mac, null)
            ]
        }
    ]

I just typed in the above so there may be typos but it should be close enough for you to get it to work generically. I based this off of the second Rule so I’ve no idea if there is an error in the first Rule.

Thanks for the redesign. I’ll test it this night. It needs a couple of hours, because the current misbehaviour happens round about every one or two hours.

First question:
I guess I need to script
“val Map<…”?

Second question:
I get following error message

'keys' is not a member of 'java.util.Map<java.lang.String, java.lang.String>'

did I miss something?

Yes, that’s an error. I don’t use Rules DSL much anymore and miss some of these details.

No, I did. It’s “keys”.

After some experiments I’ve figured out, that one of the key trackers is the problem. So the code, however not that beautiful, is working right.