Issues with implicit variables newState and triggeringItem in 3.0.0

I wrote a DSL rule and noticed the following couple issues.

State is not import org.openhab.core.types.State

By default, State seems to resolve to a non-openHab class that doesn’t even have a toString object:

2020-10-26 22:11:34.744 [ERROR] [internal.handler.ScriptActionHandler] - Script execution failed: 'toString' is not a member of 'State'; line 69, column 35, length 17 in double_taps

I can fix that by explicitly importing import org.openhab.core.types.State in my script. I really wish that the error message included the fully qualified class name in the error message (I find script errors to be very unhelpful and have some other issues with those, too).

Implicit variable triggeringItem is null

Using both “received update” and “changed to” as triggers, that variable seems to be null. This statement:

logInfo("KitchenSceneAllBright", "Rule triggered by {}", triggeringItem.name)

results in this log:

2020-10-26 22:16:46.710 [ERROR] [internal.handler.ScriptActionHandler] - Script execution failed: cannot invoke method public abstract java.lang.String org.openhab.core.items.Item.getName() on null in double_taps

It’s not really clear if this is in connection with newState? or someItem.state?
A rule fragment may help.

You’ve already got a thread on that

There is currently no github issue for this, maybe you should raise one.

I can’t figure out how to start with quote of your message like you did with mine, so I will just quote without your name?

It’s not really clear if this is in connection with newState? or someItem.state?
A rule fragment may help.

It’s in connection with the type name State without an explicit import not resolving to what I think we would expect and it seems to happen only when using a function too. Here’s a short excerpt that I checked produces the error.

val Functions.Function4<
    Map<SwitchItem, LinkedList<?>>,    // Global caching variable
    SwitchItem,    // SwitchItem triggering tapping
    State, // New state for the SwitchItem triggering tapping
    Map<String, Procedures.Procedure0>,  // Patterns configuration
    Map<String, Integer>  // return list of int value indicating matching result.
  > updatePatternsRecognizer = [
    Map<SwitchItem, LinkedList<?>> states,
    SwitchItem item,
    State newState,
    Map<String, Procedures.Procedure0> patternsConfig |
  val log = "UpdatePatternsRecognizer";
  logInfo(log, "The new state of Item '{}' is {}", item.name, newState.toString)
  return newHashMap
]

rule "Kitchen scene All Off"
when
  Item KitchenLights_Keypad_Button3 changed to OFF
then
  updatePatternsRecognizer.apply(states, KitchenLights_Keypad_Button3, newState, newHashMap(
      'OFF,OFF' -> [| Countertops_Switch.sendCommand(OFF) ]))
end

If I add the import it will work:

import org.openhab.core.types.State

// Everything else the same as above...

But maybe I expect the types to be available and that’s not expected behavior (in OH 3.0.0), it’s not a package listed in this documentation page. So that just leaves me with the gripe that it was tough to figure out as Xtend didn’t show the fully qualified name in its error message.

RE: Already having a thread on triggeringItem, I found that other thread only after this, but yes, we should discuss on it.

In case triggeringItem was working in OH2 and no more in OH3, a git issue has to be declared.
I will test it myself.

I can already confirm that some of the other implicit variables are working as before.

You usually just highlight original text, and a hovering “quote” button auto pops up.

However in the last couple of days, a few forum features seem to have broken. I noticed I can do the above with some text but not others for example, something about screen layers I think.

Okeydoke, this is not the sort of thing many users do.
DSL does not sit comfortably with functions, most users rapidly run into threading issues. General advice is simply avoid when possible - there is usually a more conventional approach using rules and group functions. That is more difficult for people importing skills from similar programming.

DSL is unsophisticated, does have limitations, there are relatively recent features like newState that may have flaws. Regard it as a legacy rule system for OH3 … you may be wasting your time here.

1 Like

DSL is unsophisticated, does have limitations, there are relatively recent features like newState that may have flaws. Regard it as a legacy rule system for OH3 … you may be wasting your time here.

It’s also quite cumbersome (for example in the code I pasted below, my function can’t call the invokeProcedureForItem without an obscure null error I haven’t figured out, so I inlined it in the main function), so I am okay not using it :slight_smile:.

So what can I look at? I was looking at JSR 223 and Jython as a way to update my Python a bit, but that’s an old Python (2.7). There’s HAPapp but that runs externally and has some limitations I know (that I don’t understand, being new, so I don’t know if I’d run into them as an issue). JSR 223 with JavaScript? something else like the blocky UI (if it’s there in 3.0)?

That’s the kind of code I am using (inspired from this post), for example. I consider that trivial code (and I would make it a bit better than what I’ve done, data-structures wise, if I wanted to invest in it) and the kind of stuff I expect to write at times. What would be the best other mean to code this, if you were to do so?

import java.time.Duration
import java.time.Instant
import java.util.HashMap
import java.util.LinkedHashMap
import java.util.LinkedList
import java.util.Map
import java.util.regex.Pattern
import org.openhab.core.types.State

/*
 * ======================================= Update Pattern Detection ======================================
 * Ver. 1.01
 * Author: Victor Bai
 * Date: Mar 17, 2017
 * Updated by Frenchman on Oct 26, 2020
 
 * Description: Function to match switch's tapping pattern: double-tap, triple pattern, customized pattern, etc. 
 */

/*
 * Data structure to hold Tap pattern states (don't edit this, or care about it):
 * {
 *   SWITCH -> [
 *     [{'ON' => '2017-03-12 12:30:10:125'}, {'ON' => '2017-03-12 12:30:11:125'}], // States queue: LinkedList<Pair<String, Instant>>
 *     { String -> Pattern, String -> Pattern, String -> Pattern }   // Update pattern strings and compiled regular expressions
 *     [2, 0, 1],  // Match results. HashMap<String, Integer>. Same keys as the input pattern. 0: no match; 1: partial match; 2: complete match 
 *     Timer                                    // Timer
 *   ]
 * }
 */
val Map<SwitchItem, LinkedList<?>> states = newHashMap

/*
 * Function updatePatternsRecognizer.apply(states, SwitchItem, State, patternConfig)
 * 
 * Input Arguments:
 *   states: Rule scope predefined variable
 *   item: SwitchItem switch triggering the tap pattern(s)
 *   newState: State new state of the switch
 *   patternConfig:  Map<String,Procedures.Procedure0> pattern definition. e.g. newHashMap('ON,ON' -> lambda1, 'ON,ON,-,ON' -> lambda2)
 *             "String" representing pattern composed of ',' delimited states. Possible state values are:
 *               "ON":  state of ON
 *               "OFF":  state of OFF
 *               "-":  Represent a "skipping" of a tapping. you have to time it right. if it's longer than LONG_SPAN, previous tapping will be discarded
 *               "#":  Represent any DimmerSwitch state in Positive percentage
 *               "0":  Represent any DimmerSwitch's OFF state 
 *             "SwitchItem" here is the virtual/proxy item you want to switch ON whenever associated pattern is detected
 * Return:
 *   ArrayList<Integer>: list of int value indicating matching result: 0: no match; 1: partial match; 2: complete match
 * 
 */
val Functions.Function4<
    Map<SwitchItem, LinkedList<?>>,    // Global caching variable
    SwitchItem,    // SwitchItem triggering tapping
    State, // New state for the SwitchItem triggering tapping
    Map<String, Procedures.Procedure0>,  // Patterns configuration
    Map<String, Integer>  // return list of int value indicating matching result. 
  > updatePatternsRecognizer = [
    Map<SwitchItem, LinkedList<?>> states,
    SwitchItem item,
    State newState,
    Map<String, Procedures.Procedure0> patternsConfig |
  val log = "UpdatePatternsRecognizer";

  // Pre-configured time span between tapping
  val SHORT_SPAN = 400 // Sequence of key pressing must happened within this time frame
  val LONG_SPAN = 600  // If time between tapping extend beyond this time, previous tapping will be ignored
 
  // Initialize.
  if (states.get(item) === null) {
    //logInfo(log, "Creating state for updates of Item '{}'", item.name)
    states.put(item, newLinkedList)
  }
  val itemState = states.get(item) as LinkedList<Object>
  
  if (itemState.size == 0) {
    itemState.add(newLinkedList) // States queue.
    itemState.add(newLinkedHashMap)  // Update patterns and their compiled regular expressions.
    itemState.add(newHashMap) // Match results.
    for (key : patternsConfig.keySet) {
      (itemState.get(2) as HashMap<String, Integer>).put(key, -1)
    }
    itemState.add(null) // Timer.
    
    // Generate patterns from the strings.
    var i = 0
    for (key : patternsConfig.keySet) {
      var pattern = Pattern.compile('^' + key.replaceAll('\\s', '').replaceAll('#', '[1-9]\\d*'))
      (itemState.get(1) as LinkedHashMap<String, Pattern>).put(key, pattern)
      //logInfo(log, "Pattern{} for updates of Item '{}' set to '{}' from '{}'", i + 1, item.name, pattern, key)
      i = i + 1
    }
  }
  
  val stateQueue = itemState.get(0) as LinkedList<Pair<String, Instant>>
  val updatePatterns = itemState.get(1) as LinkedHashMap<String, Pattern>
  val matchResults = itemState.get(2) as HashMap<String, Integer>
  var tapTimer = itemState.get(3) as Timer

  // Push out the old state from the queue
  var eventTime = now.toInstant()
  if (stateQueue.size > 0) {
    if (eventTime.isAfter(stateQueue.last.value.plusMillis(LONG_SPAN))) {
      //logInfo(log, "Resetting updates due to inactivity")
      stateQueue.clear
    }
  }
  if (stateQueue.isEmpty()) { 
      //logInfo(log, "Beginning recognition of updates for Item '{}'", item.name)
  }
  stateQueue.add(newState.toString -> eventTime)
  
  // Compose comparison string from existing queue.
  var queueString = ""
  for (var i = 0; i < stateQueue.length; i++) {
    var state = stateQueue.get(i)
    var stateTime = state.value
    if (i > 0) {
      queueString += ","
      if (stateTime.isAfter(stateQueue.get(i - 1).value.plusMillis(SHORT_SPAN))) {
        queueString + "-,"
      }
    }
    queueString += state.key
  }
  var last = if (stateQueue.length == 0) null else stateQueue.last.value
  var first = if (stateQueue.length == 0) null else stateQueue.get(0).value
  var duration = if (first === null) 0 else Duration.between(first, last).toMillis()
  if (duration != 0) {
    //logInfo(log, "Item '{}' updated to {} over {}ms", item.name, queueString, duration)
  } else { 
    //logInfo(log, "Item '{}' updated to {}", item.name, queueString)
  }
  
  // Match against regular expressions.
  var partialMatch = null
  var completeMatch = null
  for (entry : updatePatterns.entrySet) {
    var matcher = entry.value.matcher(queueString)
    if (matcher.matches) {
      matchResults.put(entry.key, 2)
      completeMatch = entry.key
    } else if (matcher.hitEnd) {
      // Partial match found
      matchResults.put(entry.key, 1)
      partialMatch = entry.key
    } else {
      matchResults.put(entry.key, 0)
    }
    //logInfo(log, "Updates {} match {}/{} of {} updates", queueString, matchResults.get(entry.key), entry.key.split(",", -1).length, entry.key)
  }
  
  if (completeMatch !== null) {
    val comp = completeMatch
    tapTimer?.cancel
    tapTimer = null
    if (partialMatch !== null) {
      if (patternsConfig.get(comp) !== null) {
        // Schedule timer for matched pattern
        //logInfo(log, "Waiting to see if updates {} will match {}", patternsConfig.get(comp).key, patternsConfig.get(partialMatch).key)
        tapTimer?.cancel
        tapTimer = createTimer(now.toInstant().plusMillis(LONG_SPAN)) [|
          stateQueue.clear
          logInfo(log, "Calling action as Item '{}' updated to {}", item.name,  comp)
          patternsConfig.get(comp).apply()
          //invokeProcedureForItem.apply(item, comp, patternsConfig.get(comp))
        ]
        itemState.set(3, tapTimer)
      }
      else {
        // No action to invoke on complete match, so no need to wait to resolve
        // the ambiguity. Let the caller decide what to do basedon matchResults.
      }
    } else {
      stateQueue.clear
      if (patternsConfig.get(comp) !== null) {
        logInfo(log, "Calling action as Item '{}' updated to {}", item.name, comp)
        patternsConfig.get(comp).apply()
        //invokeProcedureForItem.apply(item, comp, patternsConfig.get(comp))
      }
    }
  } else if (partialMatch !== null) {
    // Partial match is in progress, we should stop previous delayed complete match.
    tapTimer?.cancel 
  }

  return matchResults
]

val Procedures.Procedure3<
    SwitchItem,    // SwitchItem triggering tapping
    String,    // Pattern
    Procedures.Procedure0  // Action
> invokeProcedureForItem = [
    SwitchItem item,
    String pattern,
    Procedures.Procedure0 action |
  logInfo(log, "Calling action after Item '{}' updated to {}", item.name,  pattern)
  action.apply()
]

val Procedures.Procedure2<
    SwitchItem,    // SwitchItem triggering tapping
    Map<SwitchItem, LinkedList<?>>    // Global caching variable
  > clearUpdates = [
    SwitchItem item,
    Map<SwitchItem, LinkedList<?>> states |
  var state = states.get(item)
  if (state !== null) {
    logInfo("ClearUpdates", "Clearing updates of Item '{}'", item.name)
    state.get(0).clear
  }
]

rule "Kitchen scene All Bright"
when
  Item KitchenLights_Keypad_Button1 changed to OFF
then
  // Id is "Blm0lJ8AEwajtA0" for scene "Bright (Countertops)"
  // Id is "49esTNYAU92x2rA" for scene "Concentrate (Countertops)"
  updatePatternsRecognizer.apply(states, KitchenLights_Keypad_Button1, newState, newHashMap(
      'OFF,OFF' -> [| Countertops_Scene.sendCommand("Blm0lJ8AEwajtA0") ]))
end

rule "Kitchen scene All Off"
when
  Item KitchenLights_Keypad_Button3 changed to OFF
then
  updatePatternsRecognizer.apply(states, KitchenLights_Keypad_Button3, newState, newHashMap(
      'OFF,OFF' -> [| Countertops_Switch.sendCommand(OFF) ]))
end

rule "Kitchen scene All Raise"
when
  Item KitchenLights_Keypad_RaiseButton changed to OFF
then
  updatePatternsRecognizer.apply(states, KitchenLights_Keypad_RaiseButton, newState, newHashMap(
      'OFF,OFF' -> [| Countertops_Brightness.sendCommand(Lightbench_LightLevel.state) ]))
end

rule "Kitchen scene All Lower"
when
  Item KitchenLights_Keypad_LowerButton changed to OFF
then
  updatePatternsRecognizer.apply(states, KitchenLights_Keypad_LowerButton, newState, newHashMap(
      'OFF,OFF' -> [| Countertops_Brightness.sendCommand(Lightbench_LightLevel.state) ]))
end

Yes, everything in DSL in the “global” context (actually only global within this rules file) cannot “see” other global objects… So when you call function X from a rule, and it needs to use function Y, you must pass a reference to that in the call.

I’ve no recommendation. OH3 continues to support DSL rules, which in turn continues to have the same limitations, that in their turn are usually completely avoidable.
If you want to use exotic (for home automation) programming techniques, that might not suit.

Jython rules are flavour of the month, likely to be the standard for OH3, but you don’t like that and it doesn’t yet appear to be set in concrete anyway.

Yes, everything in DSL in the “global” context (actually only global within this rules file) cannot “see” other global objects… So when you call function X from a rule, and it needs to use function Y, you must pass a reference to that in the call.

Oh thanks for that explanation. That also means I can move that as a local val in the main function. Maybe not worth it for these two lines of code, but good to know.

If you want to use exotic (for home automation) programming techniques, that might not suit.

Yeah, I understand. I wish one could write things like this as simple extensions, say in JavaScript or whatever that could then be used by the whole framework. Or even if it had to be done in Java, that’d be fine (though slower to prototype, at least from where I stand right now).

While the code may be exotic in a way, the notion itself (multiple taps) is trivial and definitely something that is common on switches. It’s natural to want to be able to use that on controls where it’s not native.

Jython rules are flavour of the month, likely to be the standard for OH3, but you don’t like that and it doesn’t yet appear to be set in concrete anyway.

Well I just wish my brain didn’t have to think about Python 2.7 vs Python 3 when writing any Python :smiley:. But that wouldn’t be the end of the world. It’s not like I breathe Python anyways.

Yes, others are ahead of you. This one of the reasons that OH3 has a new rule engine and the jython implementation features libraries.

Okay, let’s work on that.
What are you trying to achieve? Ignore how you think of going about it.
What’s the starting point here? You are receiving trigger events from a binding, or state updates to an Item, or?

Yes, others are ahead of you.

I am sure! openHAB is pretty impressive.

Okay, let’s work on [the notion of multiple taps].
What are you trying to achieve? Ignore how you think of going about it.
What’s the starting point here? You are receiving trigger events from a binding, or state updates to an Item, or?

In this case I am receiving changes from an item:

rule "Kitchen scene All Off"
when
  Item KitchenLights_Keypad_Button3 changed to OFF
then
  updatePatternsRecognizer.apply(states, KitchenLights_Keypad_Button3, newState, newHashMap(
      'OFF,OFF' -> [| Countertops_Switch.sendCommand(OFF) ]))
end

and I am interesting in triggering an action (what would be the then part of a rule if I could write something like Item KitchenLights_Keypad_Button3 changed to OFF and OFF (or some better syntax for something like this). The sequence of changes has to happen within a predetermined time (e.g. every state change has to be, say, within 600 ms) so that I could have a rule for a double tap and a different one for a triple tap etc. for the same item.

Alright, I have to guess that this technology updates Item to ON when pressed and updates to OFF when released. They do not all work like that.


// global timer
var Timer buttonTimer = null

rule "detect press"
when
   Item myButton changed to ON
then
   logInfo("test", "button pressed, don't know if it might get pressed again")
   if (buttonTimer === null) {
         // start a timer
      buttonTimer = createTimer(now.plusMillis(600)) [ |
            // if timer expires
         logInfo"test", "it was a SINGLE button press")
         if (myButton.state == ON) {
               logInfo"test", "and they're still holding it down")
         }
         buttonTimer = null
      ]
   } else {
         // within timer window
      logInfo"test", "button DOUBLE pressed")
      buttonTimer.cancel
      buttonTimer = null
   }
end

It’s scalable to many Items using Groups, member of triggers, implicit variables (which it seems need fixing in OH3 new engine).
EDIT - ooh you’d need a Map (array) for button timers, see Rich’s Design Patterns

Alright this may work, but who wants to copy/paste and edit this every time they want to handle a double tap? In my current rules file, I have lots of such cases, and they all are a two-lines readable rule calling one function, not ten lines of code, that, if I came up with a better mousetrap, or a different log format, or whatever, I’d want to update many times.

And if I wanted three taps, this code would become pretty unwieldy and/or brittle. The lambda can’t access a var outside of it, I believe, so would you have have to prop up a similar code path for that next tap?

I hear you, it’s probably possible, and timers with lambdas are one way to have code “in the air” waiting to be executed. I was going for a building block that can be used simply in multiple places.

EDIT - ooh you’d need a Map (array) for button timers, see Rich’s Design Patterns

From this post? Also an array is akin to a list, not a map (which has keys mapping to values), so not sure I get what you are talking about.

Maybe you meant a map as in:

var Map<Item, Timer> timerMap = newHashMap

and then use triggeringItem (once that works) as the key for the timer for that source?

Why, does it cost you very much?
It’s a home automation system.
Why isn’t the button doing double tap detection for you? Many do. How many of these limited feature buttons did you buy?

The point is you can do this kind of work with DSL, however clumsy you find it. It has been around years, but is now being put to graze as a “legacy” system. There really is no point grizzling about it.

When you have specific problems, people will be happy to help.

Sure. That applies to functions in DSL too, as you have found out.

If you like. That is why it said (array) in brackets.

When you have specific problems, people will be happy to help.

I see that and appreciate it. Thanks.

This may be of interest…

1 Like

That’s the first of a series of a few dozen posts. Search on the forum is currently broken unfortunately so the link to them isn’t working from that post. So see Topics tagged designpattern for a list of some of the DPs that have been tagged as such. I’ve written a lot of them but others have contributed too. Here is a site search from duck-duck-go that shows more of them.

Several DPs could apply (in no particular order:

There are a couple of state machine DPs as well which might be useful.

Almost all of the time a Map is used in Rules with the name of the Item being the key and the Timer associated with that Item as the value. That lets you have one Rule that handles the behavior for lots of Items. You can get the name of the Item that triggered the rule with triggeringItem.name.

But notice I said the name, not the Item itself as the key. I never looked into it way back when but I definitely ran into troubles when I used the Item as the key. The hash seems to change for the Item Object over time so from one run of the rule to the next the Item may no longer be able to pull the value out of the Map.

If you are on OH 3, than I believe you can get at the triggering Item using ir.getItem(event.name) but this is mainly going on some comments on on the GitHub Issue for triggeringItem not working. I’ve not tried this from Rules DSL in OH 3 and can’t guarantee it will work. But that’s how you would do it in Jython or JavaScript.

You can get the name of the Item that triggered the rule with triggeringItem.name. But notice I said the name, not the Item itself as the key.

Got it. Names are fine, they’re unique too. I haven’t run into that issue in my function (the key to my map is a SwitchItem) but I believe you :slight_smile:. Maybe the hash changes if some item properties change…

As you mentioned (and as my post started), triggeringItem is null in rules in OH 3 right now. I’ll try ir.getItem(event.name) instead. What does ir stand for? Is that am official new OH 3 implicit variable, or just a bit of implementation you’re exposing to help here?

ir stands for “Item Registry”. It’s used pretty extensively in Jython, JavaScript, and Groovy rules to gain access to actual Items, for example to get at the members of a Group. But there is also and items dict that one can use to get an Item’s state by the Item’s name. For example items[event.itemName] will give you the state of the Item that triggered the Rule (don’t do that for command triggered rules, the Item may not have changed state yet).

Now that Rules DSL is running on the new rules engine (formerly the experimental next-gen rules engine), these variables get injected into Rules DSL rules just like they have in Jython, JavaScript, and Groovy rules for quite some time.

Does that give me the state, or the item? Not knowing anything I’d expect an Item given the items variable name (and items[event.itemName].state the state).

BTW, neither ir nor items seem to be working for me (OH 3 build #1989):

Script execution of rule with UID 'update_patterns-3' failed: The name 'ir' cannot be resolved to an item or type; line 235, column 49, length 2 in update_patterns
Script execution of rule with UID 'update_patterns-3' failed: The name 'items' cannot be resolved to an item or type; line 236, column 49, length 21 in update_patterns

The state, just like I said.

if(items["MySwitch"] == ON) {

Well, it was a shot in the dark. Because Rules DSL imports event I figured it might import ir and items too. I guess not. If you ever find a need to get access to the ir from Rules DSL see Design Pattern: Associated Items (scroll down to the Item Registry section).