Item tags iteration in rules script (OH4)

Hi guys!
I’m writing a rule to check for binding status and restart it if needed.
I’m in trouble due to strange foreach behaviour:

I defined some items with two tags each and I need to iterate through tags to perform different actions like, in “pseudocode”:

If Item1 has tag1 = somevalue
do something
else do another thing

These are my items:


Group gMonitor                          "Monitoring not yet working"    <error>      
    Switch        mMQTT_Temp1       "MQTT Temp1 [%s]"              <switch>       (gMonitor)       ["mqtt:topic:MY-MQTT:RTL_433:temp" , "mqtt.things"]
    Switch        mGPIO_LED         "GPIO LED verde [%s]"          <switch>       (gMonitor)       ["gpio:pigpio-remote:raspiOH4:sample-output-10" , "GPIO.things"]


This is my rule file:

rule "Monitoring Things Status"
when Time cron "0 0/1 * * * ?"  
then

	gMonitor.members.forEach[ i | 
       	{
        var String vThingUID = i.tags.get(0).toString()        // Reads the first tag
        var String tt = i.tags.get(1).toString()               // Reads the second tag
        
		 logWarn("Monitoring Things Status", "Tag0: " + vThingUID)
         logWarn("Monitoring Things Status", "Tag1: " + tt )
         ]

end

The strange behaviour is this:
In the first loop, I correctly get first tag (tag0) with “i.tags.get(0)” and second tag (tag1) with “i.tags.get(1)”
In the second loop, I get Tag0 with “i.tags.get(1)” and Tag1 with with “i.tags.get(0)” !!

These is my Openhab log:

2024-10-09 11:54:02.349 [WARN ] [odel.script.Monitoring Things Status] - Tag0: mqtt:topic:MY-MQTT:RTL_433:temp
2024-10-09 11:54:02.350 [WARN ] [odel.script.Monitoring Things Status] - Tag1: mqtt.things

2024-10-09 11:54:02.360 [WARN ] [odel.script.Monitoring Things Status] - Tag0: GPIO.things
2024-10-09 11:54:02.362 [WARN ] [odel.script.Monitoring Things Status] - Tag1: gpio:pigpio-remote:raspiOH4:sample-output-10

Anyone could help me, please?

Tags are not guaranteed to be stored nor returned in order. You cannot count on the first tag always being at index 0 and the second one always at index 1.

This problem is really outside the bounds of what tags are intended for and will not really work that well for you. A better approach would be to use Item metadata but metadata isn’t available in Rules DSL.

It’s one of several reasons why I do not recommend Rules DSL for new development. Any of the other languages are just as easy to work in, do not have weird problems with type, and have full access to everything in OH. Even Blockly is more capable than Rules DSL.

But thankfully, you don’t even need an Item for this. You can install Thing Status Reporting [4.0.0.0;5.9.9.9] from the add-on store. This will be configured to call a rule when ever a Thing changes status. The called rule will get the thingID, old status and new status of the Thing. You can set up a condition to only look for OFFLINE states, only certain Things, only Things from a certain binding.

For example, a rule that only looks for MQTT Things going offline would look something like this (this is a JS Scripting and a UI rule, again, Rules DSL lacks the ability to be called from another rule with arguments):

Rule Condition:

thingID.startsWith("mqtt") && newStatus != "ONLINE";

This will only run the rule action if the thing ID starts with “mqtt” and the new status is anything but “ONLINE” (e.g. OFFLINE, error, etc.).

Rule Action:

console.warn("Monitoring Things Status: ThingID - " + thingID + " Status - " + newStatus + " Details - " + newDetails);
// add code to reset the binding or take what ever remedial behavior required

No triggers, the rule template will call this rule when any Thing changes status.

Hi Rich!
Thank you very much for your support!
I’m testing your solution to reach my goal and I’ll move to JS scripting for my new rules.

However, it seems to me that GraalJS script engine is a bit slow on my RPi 3b with 1Gb RAM.

When i force a binding reload from Karaf, this JS rule:

configuration: {}
triggers: []
conditions: []
actions:
  - inputs: {}
    id: "2"
    configuration:
      type: application/javascript
      script: >
        //console.warn("Monitoring Things Status: ThingID - " + thingID + "
        Status - " + newStatus + " Details - " + newDetail);

        console.warn("XXX: ThingID - " + thingID + " - Status - " + newStatus + " (was: " + oldStatus + ")");

        console.warn("XXX: Detail - " + newDetail + " (was: " + oldDetail + ")");

        // add code to reset the binding or take what ever remedial behavior required

    type: script.ScriptAction

is not able to check thing fast enough ..
From log you can see that thing goes online again before script engine checks it is UNKNOWN.

2024-10-15 13:49:01.460 [INFO ] [el.core.internal.ModelRepositoryImpl] - Loading model 'telegram.things'

2024-10-15 13:49:01.517 [DEBUG] [.internal.OpenhabGraalJSScriptEngine] - Initializing GraalJS script engine...
2024-10-15 13:49:01.535 [DEBUG] [.internal.OpenhabGraalJSScriptEngine] - Injecting ThreadsafeTimers into the JS runtime...
2024-10-15 13:49:01.537 [DEBUG] [.internal.OpenhabGraalJSScriptEngine] - Evaluating cached global script...
2024-10-15 13:49:01.539 [DEBUG] [.internal.OpenhabGraalJSScriptEngine] - Evaluating cached openhab-js injection...
==> /var/log/openhab/events.log <==
2024-10-15 13:49:01.502 [INFO ] [ab.event.ThingStatusInfoChangedEvent] - Thing 'telegram:telegramBot:MyBot' changed from ONLINE to UNKNOWN

==> /var/log/openhab/events.log <==
2024-10-15 13:49:11.502 [INFO ] [ab.event.ThingStatusInfoChangedEvent] - Thing 'telegram:telegramBot:MyBot' changed from UNKNOWN to ONLINE
==> /var/log/openhab/openhab.log <==
2024-10-15 13:49:19.034 [DEBUG] [.internal.OpenhabGraalJSScriptEngine] - Successfully initialized GraalJS script engine.

2024-10-15 13:49:19.038 [WARN ] [nhab.automation.script.ui.25f0a55c33] - XXX: ThingID - telegram:telegramBot:MyBot - Status - UNKNOWN (was: ONLINE)
2024-10-15 13:49:19.040 [WARN ] [nhab.automation.script.ui.25f0a55c33] - XXX: Detail - NONE (was: NONE)

2024-10-15 13:49:19.059 [WARN ] [nhab.automation.script.ui.25f0a55c33] - XXX: ThingID - telegram:telegramBot:MyBot - Status - ONLINE (was: UNKNOWN)
2024-10-15 13:49:19.061 [WARN ] [nhab.automation.script.ui.25f0a55c33] - XXX: Detail - NONE (was: NONE)

Is it only a Rpi performance problem ?

Thanks again,
Alex

On underpowered machines like an RPi 3b, it can take some seconds for JS Scripting rules to be loaded and parsed. The fix is to use 64-bit Java (which of course requires 64-bit OS) which brings that loading time down by 10x. However, on an RPi 3 there isn’t that much RAM and moving to 64-bit is going to require more RAM.

So your best bet is to either use jRuby or Groovy or move to an RPi with more RAM with 64-bit OS and 64-bit Java.

Note that this slowness is only on the first load of the rule for recent versions of OH 4. Prior to that the slowness is on the first run of the rule. After that the rule runs as fast as expected.

1 Like

Hi Rich!
I’m trying to check for binding status with JS, as you suggested me.
My JS script can’t read things.
It returns undefined on

var bbb = things.getThing(thing_element);        

log:

Failed to execute rule Binding Checker: TypeError: Cannot read property "getStatus" from null: TypeError: Cannot read property "getStatus" from null
your code goes here

This is (part of) my .js file

rules.JSRule({
  name: "00 - Filereader v2",
  description: "Filereader",
  triggers: [""], //[triggers.GenericCronTrigger("*/15 * * * * *")],
  //------ start event
  execute: (event) => {
var thing_element =things.getThing("amazonechocontrol:account:0123456789");
        
//things.getThing(event.thingUID.toString()).label

        //var bbb = things.getThing(thing_element.toString());
        var bbb = things.getThing(thing_element);        
        console.warn("--000a "  + bbb.status);
/*        var bbb2 = things.getThing("amazonechocontrol:account:1ed60f9c:AlexaNero");
        console.warn("--000b "  + bbb2.label);
*/
        var good = 0;
        var bad = 0;

          console.warn("--00 "  + bbb2.status);
          console.warn("## Line: " + line_no +  " --01 "  + bbb2.getThingStatusInfo);  // sempre undefined !!!
/*
        if (bbb.getThingStatusInfo == "ONLINE"){
//          console.warn("-- " + bbb.status + " - "  + bbb.uid);
          console.warn("-- "  + bbb.uid);
          good++;
        }
        else{
          //console.warn("-- " + bbb.status + " - "  + bbb.uid);
          console.warn("-- "  + bbb.uid + " - need to be touched" );
          logger.error("111 goodstatus: -" + goodstatus + "-");
          
          bad++;
        }

        console.error("-- END -- Good: " + good + " / bad: " + bad);
        //ans = fileWrite("/etc/openhab/things/00prova.txt","--",true);
        //actions.Exec.executeCommandLine("sudo -u openhab echo \"aA\" \>\> /etc/openhab/things/"+"00prova.txt");
     
      */        
     }
    
    }
    //---------end while
    logger.error("999 TOT goodstatus: -" + goodstatus_tot + " of " + line_no + " lines..");

  },
  //------ end event
  tags: ["Bindings", "Scarpy"],
  id: "Binding Checker",
//ok
});

Thanks!

First you do this:

var thing_element =things.getThing("amazonechocontrol:account:0123456789");     

whhic means thing_element is a Thing.

But then you try to use thing_element to get bbb?

var bbb = things.getThing(thing_element); 

That’s never going to work. You can’t get a Thing using another Thing. You need to use a String containing the ThingID in a call to getThing. getThing is returning null and null of course doesn’t have a status.

One of these calls to getThing is redundant. If what you care about is the status of your amazoncontrol Thing, well you already have that in thing_element. You need not call things.getThing ever agaibn in this rule. You’ve already got the Thing you care about.

If you need the status of some other Things, you need to call things.getThing for each of them, passing a String that contains the ThingUID you care about.

I semplified the script:

rules.JSRule({
  name: "00 - Filereader v2b",
  description: "Filereader",
  triggers: [""], //[triggers.GenericCronTrigger("*/15 * * * * *")],
  //------ start event
  execute: (event) => {
        //var thing_element = things.getThing('amazonechocontrol:account:1234567890');
        var thing_element = things.getThing('astro:sun:local');
        console.warn("--000a "  + thing_element.status());
  },
  //------ end event
  tags: ["Bindings", "Checks"],
  id: "Binding Checker-b",
//ok
});

but when I run it manually, i got:

2024-10-24 19:27:36.931 [ERROR] [tomation.script.file.filereader2b.js] - Failed to execute rule Binding Checker-b: TypeError: Cannot read property "getStatus" from null: TypeError: Cannot read property "getStatus" from null
	at get status (@openhab-globals.js:2)
	at execute (filereader2b.js:9)
	at execute (@openhab-globals.js:2)
2024-10-24 19:27:36.935 [WARN ] [e.automation.internal.RuleEngineImpl] - Failed to execute action: 1(Error: Failed to execute rule Binding Checker-b: TypeError: Cannot read property "getStatus" from null: TypeError: Cannot read property "getStatus" from null

Be sure that you are looking at the docs: JavaScript Scripting - Automation | openHAB

Thing : object

  • .bridgeUID ⇒ String
  • .label ⇒ String
  • .location ⇒ String
  • .status ⇒ String
  • .statusInfo ⇒ String
  • .thingTypeUID ⇒ String
  • .uid ⇒ String
  • .isEnabled ⇒ Boolean
  • .setLabel(label)
  • .setLocation(location)
  • .setProperty(name, value)
  • .setEnabled(enabled)

It’s a property, not a method. No parens.

thing_element.status

Thanks Rich!

Be sure that you are looking at the docs: JavaScript Scripting - Automation | openHAB

I’ll do again with more attention!
Sorry, I’m still a newbye with JS so it’s not easy for me to understand how JS works with Openhab.

I corrected status() in status but nothing to do.

The log always says:

Failed to execute rule Binding Checker-b: TypeError: Cannot read property "getStatus" from null: TypeError: Cannot read property "getStatus" from null

Problem is here:

var thing_element = things.getThing('amazonechocontrol:account:123456789');

things.getThing returns null ! The same happens with other UID:

var thing = things.getThing('astro:moon:home');

Could it be a general/addon configuration problem?

One of the wonderful things you can do with Blockly is plop some blocks into a script then click the code tab and see what JS it generates. It’s a great way to learn.

Are you sure it’s that line? The error is complaining about getStatus(), not getThing(). That line doesn’t have an attempt to call getStatus().

Are you sure you don’t have “getStatus” anywhere in this .js file?

Have you ever installed openhab-js manually?

Which version of OH 4 are you running?

This specific error isn’t because things.getThing() is returning null. That would complain that status doesn’t exist. This is caused by something else.

What happens if you create a Blockly rule to get the status of the Thing. Does that work? Is there any difference between the code it generated and what you have in your file?

The depth of this development environment and the possibilities it gives me are some of the reasons that make me addicted to it!

I found the problem..
Trying Blockly’s “get thing status” block, I discovered that the thing list is almost empty…

None of my things (defined in .thing file) are listed in the blocly pick list ..

01 prima|400

..neither in the main UI things page !

01 Blockly thing list

After refreshing the .thing file
(with echo “” >> /etc/openhab/things/astro.things command)
objects appear in the list:
Blockly thing list

and in the main UI things page !
02 dopo_

Now JS statement:

var thing_element = things.getThing('astro:sun:local');

works…

It seems to me that my JS script can’t check unknown things..
I don’t know how to proceed ..

Perhaps a try/catch error management?

Well, if the Thing doesn’t exist you can test for that by testing for null. things.getThing() will return null when the Thing doesn’t exist.

Are you sure the missing Things were working in the first place? They seem to have been in some sort of inbetween state where they sort of existed but not fully existed. Both the call to thing.getThing() and the REST API call that populates the Things page get that information from the ThingsRegistry. If both are showing that the Thing doesn’t exist that means that as far as OH core is concerned those Things do not exist.

You will get the same behavior from any of the rules languages that support access to the ThingsRegistry. It’s not specific to JS.

What ever happened should be a rare occurance. In fact it should never happen again. If it does happen again an issue needs to be files to figure out what happened and fix it.

And while this is supposed to work and must be made to work properly, one of the reasons (not the bioggest reason, just one among several) I don’t use file based configs is weirdness like this which can crop up.