I have been trying to use a Pair<A,B> in one of my rules, but I’m getting a Null error when I try to access to the value of the Pair after I inserted it into a Map.
The following rule is a simplification of the real one.
import java.util.Map
var Map<Integer, Pair<Timer, String>> timers = newHashMap
var index = 0
rule "Demo Rule"
when
Item BoilerAlarm received update
then
index = index + 1
val msg = triggeringItem.state
val timer = createTimer(now.plusMinutes(1), [| logInfo("demo4", "timeout")])
val item = timer -> msg //pair operator
// val item = new Pair<Timer, String>(timer, msg)
logInfo("demo4", "Pair key: " +item.getKey())
logInfo("demo4", "Pair value: " +item.getValue())
timers.put(index, item)
timers.forEach[k,pair,i |
logInfo("demo4", "Key: " +pair.getKey()) // OK
logInfo("demo4", "Value: " +pair.getValue()) // Error, null
]
end
val msg = triggeringItem.state
...
timers.forEach[k,pair,i |
logInfo("demo4", "Key: " +pair.getKey()) // OK
logInfo("demo4", "Value: " +pair.getValue().toString())
]
Output
16:13:29.100 [ERROR] [untime.internal.engine.RuleEngineImpl] - Rule ‘Demo Rule’: Could not invoke method: java.lang.String.toString() on instance: IS_FAILURE
You probably have to import Pair as well. I just did some work with Maps and Lists and the like and discovered that while they kind of work when you don’t import them, they don’t completely work. And since Pair comes from the javafx package, I wouldn’t be surprised if it is not available for use in OH. I don’t remember if javafx is generally available in Java 8 or not.
I also don’t understand why you need the Map and the Pair. What specific problem are you trying to solve here? Setting up a poor man’s data structure like this in Rules DSL is a code smell. There is almost always a better way to achieve the same thing.
Anyway, the specific error here is that the Rules DSL can’t figure out how to convert the result from calling pair.getValue to a String (null errors like that are almost always a type problem). You can try explicitly calling toString on the value.
Why a Pair? I need to store each alarm message with its timer, so I can later check the type of message and cancel its timer if it’s needed. That’s why I went for a tuple. Why a Map? I use an incremental integer as Key to be able to iterate over the map in the same order in which the alarm-msgs were received.
I’m trying to define this rule:
Notify me when the water boiler reports three floor heating sensor (FHS) failures and one internal sensor (IS) failure in one hour.
So, I want to be notified only if the four events occur in a time window of an hour.
Maybe you can suggest another way to go, I have been working with OpenHAB less than a week. Suggestions are welcome!
Once again, thanks for your comments and your time
The same occurs with Map, but, for example, if you do not import Map then the Rules DSL will complain there is no getKeys() method. Import it and it works.
It depends on whether you are a programmer or not. If you know how to program then you might be happier in one of the JSR223 languages. Rules DSL is nice and simple and relatively easy to learn for non-coders, but coders, especially those who insist on bending the language to their will rather than adopting to the idiosyncrasies of the language quickly become very frustrated.
OK, so you don’t actually care about the order of the events, just whether they’ve occurred in the last hour. So I would probably do something like the following.
Create a Map<String, Integer> to store a count of each of the events that have occurred in the past hour
When an event occurs, increment the Integer at that event’s key in the Map
Set a Timer for 60 minutes, at which point it decrements the Integer at that key
Check the two keys, if the one event has a 3 or more and the other a 1 or more generate your alert.
val Map<String, Number> events = newHashMap("FHS" -> 0, "IS" -> 0) // initialize the Map with zeros so we don't have to check for null in the Rule
rule "Demo Rule"
when
Item BoilerAlarm received update
then
// Increment the count
val eventStr = BoilerAlarm.state.toString // put this into a constant so we can access it in the Timer later on
val currCount = events.get(eventStr)
events.put(eventStr, currCount+1)
// Create the Timer to decrement in an hour
createTimer(now.plusHours(1), [ | // We don't ever need to cancel the Timer so we don't need to keep a handle on it
val count = events.get(eventStr)
events.put(eventStr, count-1)
])
// Figure out if we need to alert
if(events.get("FHS") >= 3 && events.get("IS") >= 1) {
// publish alert, see https://community.openhab.org/t/design-pattern-separation-of-behaviors/15886
// do any rate limiting if you need to avoid repeated alerts, see https://community.openhab.org/t/design-pattern-event-limit/17831
}
end
I just typed in the above. There are likely errors.