[OH3] Using global HashMap in Lambda in DSL Rules

  • Platform information:
    • Hardware: Raspberry Pi 3 Model B Plus Rev 1.3
    • OS: Linux openhabian 5.15.84-v7+ #1613 SMP Thu Jan 5 11:59:48 GMT 2023 armv7l
    • Java Runtime Environment:
      • openjdk version “11.0.16” 2022-07-19
      • OpenJDK Runtime Environment (build 11.0.16+8-post-Raspbian-1deb11u1)
      • OpenJDK Server VM (build 11.0.16+8-post-Raspbian-1deb11u1, mixed mode)
    • openHAB version: openHAB 3.4.1 - Release Build
  • Issue of the topic: I try to create a timer for each opened window sensor and delete it when it gets closed and it is not already expired. To reduce the lines of code I tried to manage the timers and configurations in hashmaps.

sensor.rules

import org.openhab.model.script.actions.*
import org.openhab.core.model.script.ScriptServiceUtil
import java.util.HashMap

// storage for timers
val HashMap<String, Timer> sensorTimers = newHashMap()

// echo devices to create generic items
val HashMap<String,String> Echo_Name = newHashMap(
        "WZ"    -> "Living_Room",
        "BZ"    -> "Bath_Room",
        "KU"    -> "Kitchen",
        "FL"    -> "Floor",
        "AZ"    -> "Office"
    )
val HashMap<String,String> Fenster_Name = newHashMap(
        "WZ"    -> "Wohnzimmer",
        "BZ"    -> "Badezimmer",
        "KU"    -> "Kueche",
        "KZ"    -> "Kinderzimmer",
        "SZ"    -> "Schlafzimmer",
        "AZ"    -> "Arbeitszimmer"
    )
// durations in minutes
val HashMap<String,Number> Fenster_Dauer = newHashMap(
        "WZ"    -> 10,
        "BZ"    -> 5,
        "KU"    -> 10,
        "KZ"    -> 5,
        "SZ"    -> 10,
        "AZ"    -> 5
    )
val handleTimer = [ String State, String TimerName, String EchoName, long TimerDuration |
    logInfo("Timer", "Triggered! " + State)
    logInfo("Timer", "Triggered! " + sensorTimers.get(TimerName))
    if(State == "ON") {
        if(sensorTimers.get(TimerName) === null) {
            logInfo("Timer", TimerName + " Timer created.")
            sensorTimers.put(TimerName, createTimer(now.plusSeconds(TimerDuration), [|
                speak_with_volume.apply("Living_Room", 20, TimerName + " Fenster geöffnet.")
                sensorTimers.put(TimerName, null)
                logInfo("Timer", TimerName + " Timer expired.")
            ]))
        }
    }
    if(State == "OFF") {
        if(sensorTimers.get(TimerName) !== null) {
            sensorTimers.put(TimerName, null)
            logInfo("Timer", TimerName + " Timer abgebrochen.")
        }
    }
]

// will create a rule for every sensor
rule "Dev_Manual_Sensor WZ"
when
    Item Dev_Manual_Sensor changed
then
    var Room = "WZ"
    var EchoName = "WZ"
    logInfo("Dev_Manual_Sensor", "new state --> " + newState.toString)
    handleTimer.apply(newState.toString, Fenster_Name.get(Room), Echo_Name.get(EchoName), Long::parseLong(Fenster_Dauer.get(Room).toString))
end

When I manually change the item to trigger the rule I get the following error message:

[internal.handler.ScriptActionHandler] - Script execution of rule with UID 'sensor-1' failed: cannot invoke method public java.lang.Object java.util.HashMap.get(java.lang.Object) on
null in sensor

I think it’s because I try to access the hashmap inside the lambda, but is there an approach to use them as I tried?

I usually try to use item groups and use them as triggers, but with my sensors I only receive updates and no commands. This is why I don’t get the “triggeredItem” and I can not create a specifically named timer on my echo device.

This was my first approach, but here I won't know what item triggered the rule.
import org.openhab.model.script.actions.*
import org.openhab.core.model.script.ScriptServiceUtil
import java.util.HashMap

val HashMap<String, Timer> sensorTimers = newHashMap()

val speak_with_volume = [ String Echo_Location, Number Echo_Vol, String Message |
    val Echo_TTS = ScriptServiceUtil.getItemRegistry.getItem("Echo_" + Echo_Location + "_TTS") as GenericItem
    val Echo_TTS_Vol = ScriptServiceUtil.getItemRegistry.getItem("Echo_" + Echo_Location + "_TTS_Vol") as GenericItem
    var previousVol = if (Echo_TTS_Vol.state === null) 30 else Echo_TTS_Vol.state as Number
    Echo_TTS_Vol.sendCommand(Echo_Vol)
    Echo_TTS.sendCommand("<speak>" + Message.toString + "</speak>")
    Echo_TTS_Vol.sendCommand(previousVol)
]

rule "Sensor group rule"
when
    Member of Fenster_Sensoren received update
then
    Fenster_Sensoren.members.filter[ sensor | sensor.state !== sensor.previousState() && sensor.state !== null ].forEach[ sensor |
        // Only create a Timer if there isn't one already
        if(sensorTimers.get(sensor.name) === null) {
            logInfo("timer", "timer created for " + sensor.name)
            sensorTimers.put(sensor.name, createTimer(now.plusSeconds(10), [|
                speak_with_volume.apply("Living_Room", 20, "Fenster geöffnet.")
                sensorTimers.put(sensor.name, null) // remove the timer from the Hashmap
                logInfo("timer", "removed timer for " + sensor.name)
            ]))
        }
    ]
end

Did you try passing sensorTimers to the lambda as a argument?

1 Like

To elaborate on @mhilbush’s answer, when a lambda is created it inherits the context at that time. That’s how they are able to see global variables when you create one inside a rule. But there is no context at the global level. Global variables cannot see each other. So you must pass them as arguments to the lambdas.

1 Like

Thank you @rlkoshak for providing the explanation, since I failed to do it because I was in too much of a hurry. :+1:

Thank you @mhilbush and thanks for the explanation, @rlkoshak :+1:

Do you have any idea how to proceed with my first approach and get the triggered item of my sensor group?

See Rules | openHAB and Rules | openHAB

1 Like

Thanks, I have missread this. :v: