Is it possible to make metadata editable in the app?

Out of curiosity, what happens if you fiddle the metadata while an expire period is in progress? I assume nothing, i.e. timing continues with “old” value.

Looks like a good candidate for the Market Place.

But I wonder, since you have to use a rule anyway whether it makes more sense to just implement the expire within the rule(s) using a timer. That gives you more control over the side cases like rossko57 brings up.

Note, in JS Scripting the code would be something like

var logger = log('org.openhab.rule.ModifyExpire');

// Since we are only updating the value we don't need to directly get at the MetadataRegistry

var modifyInfo = items.getItem(event.itemName).state.split(',');

var timerItem = items.getItem(modifyInfo[0]);
var newHours = parseInt(modifyInfo[1]);
var newMinutes = parseInt(modifyInfo[2]);
var newSeconds = parseInt(modifyInfo[3]);

// Validate new time values and existing metadata
if(newHours < 0 || 23 < newHours) {
  logger.warn('Hours outside of range: expire metadata not set');
} else if(newMinutes < 0 || 59 < newMinutes) {
  logger.warn('Minutes outside of range: expire metadata not set');
} else if(newSeconds < 0 || 59 < newSeconds) {
  logger.warn('Minutes outside of range: expire metadata not set');
} else {
  let newExpire = [newHours, 'h', newMinutes, 'm', newSeconds, 's, ', metadata.split(',')[1]].join(' ');
  timerItem.upsertMetadataValue('expire', newExpire); // use upsertMetadataValue if you want to create it if it doesn't exist
}

items.getItem(event.itemName).postUpdate('None');

An alternative, you could use the js-joda Duration (the same would work in Nashorn but you’d have to use java.time.Duration instead). It uses the same overall String structure as Expire except the duration starts with ‘P’. So, if the widget can be modified to return something like 1H2M3S or even better P1H2M3S the rule could become (assuming the ‘P’ can’t be done in the widget):

var logger = log('org.openhab.rule.ModifyExpire');

var modifyInfo = items.getItem(event.itemName).state;
var timerItem = items.getItem(modifyInfo.split(',')[0]);
var newExpireStr = modifyInfo.replace(timerItem+',', ''); // would be better if some other separator than ',' could be used between the timerName and the expire string

try {
  var newExpireDur = Duration.parse('P'+newExpireStr); // if it parses it's valid
  // if you wanted you could test to see if the new value is different from the old by 
  // parsing the existing Duration and calling .equals instead of always updating
  timerItem.upsertMetadataValue(event.itemName, newExpireStr);
  items.getItem(event.itemName, 'None'); // put this here if you don't want to reset a bad value, put it after the catch if you always want to reset it
} catch(DateTimeParseException e) {
  logger.warn(newExpireStr + " is not a valid expire duration.");
}

Obviously I just typed in the above. There might be typos. For the widget, I’d probably just use a text field with an expected format. You can configure the widget to parse the entry for validity before submitting it. Though then you couldn’t use number selectors for each field.

I haven’t done any extensive controlled testing, but from the couple of times I’ve inadvertently changed the metadata while an expire was timing it appears to just cancel the currently running expire timer.

Yeah, it’s on the todo list. As I said, there numerous improvements and tweaks that it needs first, but it’s been a busy summer. I don’t even use this version of the widget that shows all the expire timers it was just the first development version. I use a smaller version that only shows the timings for all of my door and window alarms.

The use of upsert in the code is one area I’d already identified, but before it’s ready for for general distribution I was planning to also add the ability to add new expire metadata to an item that doesn’t have it which is a slightly larger undertaking.

The widget parses metadata and give separate fields for the times which, for me, gives better readability and fairly easy editing, but there is definitely more that can be done for input validation.

There’s also a two step edit process to avoid accidental modifications. You first have to click the edit button:


Then you get the input fields and can accept or cancel any changes you’ve made:

Interesting - feels like editing an Item is causing some kind of “reset”, which may or may not be desirable in any given circumstance. I wonder if there are other side effects.

That’s what upsert does. If the metadata isn’t there it adds it. If it is there it updates it. But I’ve only see the use of upsert in openhab-js so it might be something not part of the MetadataRegistry but implemented in the library so you’d have to move to JS Scripting instead of Nashorn.

I think your post is a good candidate for a feature request/

I’ve did some custom work around this topic: openhab_metadata_edit2.gif

Its a custom UI build which is checking if there is a registered config descriptor with metadata: prefix. The Config forms/framework is being used in several places of UI and with a bit of adjustments it was possible to embed it.

Yeah, upsert is not from the core registry, I think it’s just implemented in the js library. That’s not a huge issue, at this point I don’t have a problem with posting rules that depend on the JSScripting add-on.

The difficulty with novel expire settings is on the widget side not the rules side. The widget’s repeater filters for all the items that already have expire metatdata, so there has to be a completely separate mechanism to access items that don’t yet have expire metadata. I’ve got the framework to do this in other widgets, just haven’t integrated it into this one yet.

This is a very interesting discussion, so I tried to make this example working.
I made the widget and the string item Rule_ExpireModify, i see my timer and can change the value’s.

The i made the rule with the following code:

configuration: {}
triggers:
  - id: "1"
    configuration:
      itemName: Rule_ExpireModify
    type: core.ItemStateChangeTrigger
conditions:
  - inputs: {}
    id: "2"
    configuration:
      itemName: Rule_ExpireModify
      state: None
      operator: "!="
    type: core.ItemStateCondition
actions:
  - inputs: {}
    id: "3"
    configuration:
      type: application/javascript
      script: >+
        var logger = log('org.openhab.rule.ModifyExpire');

        var modifyInfo = items.getItem(event.itemName).state;

        var timerItem = items.getItem(modifyInfo.split(',')[0]);

        var newExpireStr = modifyInfo.replace(timerItem+',', ''); // would be better if some other separator than ',' could be used between the timerName and the expire string


        try {
          var newExpireDur = Duration.parse('P'+newExpireStr); // if it parses it's valid
          // if you wanted you could test to see if the new value is different from the old by 
          // parsing the existing Duration and calling .equals instead of always updating
          timerItem.upsertMetadataValue(event.itemName, newExpireStr);
          items.getItem(event.itemName, 'None'); // put this here if you don't want to reset a bad value, put it after the catch if you always want to reset it
        }  finally {
              logger.warn(newExpireStr + " finaly state rule");
           }

    type: script.ScriptAction

If the rule is triggerd i get the following error:
2022-10-25 22:44:49.482 [ERROR] [internal.handler.ScriptActionHandler] - Script execution of rule with UID ‘1519412e0e’ failed: ReferenceError: “log” is not defined in at line number 1

How can I make this code working? My programming skils are not so high :roll_eyes:

You are using the old version of javascript that comes pre-installed with OH. The code that you copied requires the a newer version and the helper libraries that come with it. You’ll need to go to the settings page and under Addons click on Automation there you can search for and install the JSScripting Addon. This will add a new option to your rule scripts that looks like this

image

You will need to delete the script you have already created, and add a new script using the newer javascript version and then paste the code into that.

Thanks, I installed the add-on and copied your original code. It works! :slightly_smiling_face:
If I tried also the code from Rich I see some programming errors.

var logger = log('org.openhab.rule.ModifyExpire');

// Since we are only updating the value we don't need to directly get at the MetadataRegistry

var modifyInfo = items.getItem(event.itemName).state.split(',');

var timerItem = items.getItem(modifyInfo[0]);
var newHours = parseInt(modifyInfo[1]);
var newMinutes = parseInt(modifyInfo[2]);
var newSeconds = parseInt(modifyInfo[3]);

// Validate new time values and existing metadata
if(newHours < 0 || 23 < newHours) {
  logger.warn('Hours outside of range: expire metadata not set');
} else if(newMinutes < 0 || 59 < newMinutes) {
  logger.warn('Minutes outside of range: expire metadata not set');
} else if(newSeconds < 0 || 59 < newSeconds) {
  logger.warn('Minutes outside of range: expire metadata not set');
} else {
  let newExpire = [newHours, 'h', newMinutes, 'm', newSeconds, 's, ', metadata.split(',')[1]].join(' ');
  timerItem.upsertMetadataValue('expire', newExpire); // use upsertMetadataValue if you want to create it if it doesn't exist
}

items.getItem(event.itemName).postUpdate('None');

Error in the log
2022-10-26 15:59:08.293 [ERROR] [internal.handler.ScriptActionHandler] - Script execution of rule with UID ‘1519412e0e’ failed: org.graalvm.polyglot.PolyglotException: TypeError: (intermediate value).split is not a function

Last question, how can I put the variables in de logfile?
Something like logger.warn(timerItem.toSting) ??

Some debugging wilI help me to find the problem.

The .split() method applies to string variables. The error is saying that it can’t apply this method which presumably means than the variable it’s being used on isn’t a string type. The problem is this line:

let newExpire = [newHours, 'h', newMinutes, 'm', newSeconds, 's, ', metadata.split(',')[1]].join(' ');

When Rich quick typed up his example, he skimmed over defining the variable metadata. You’ll have to add that back in. The code at the top my the old version of the rule can be greatly simplified these days though. So you just need to put the following after the declaration of the timerItem variable:

var MetadataRegistry = osgi.getService('org.openhab.core.items.MetadataRegistry');
var MetadataKey = Java.type('org.openhab.core.items.MetadataKey');
var metadata = MetadataRegistry.get(new MetadataKey("expire",timerItem));

Close. In this case, you don’t need to convert timerItem to anything, it is already a string variable (the state of the item that caused the rule to run is a string, and timerItem has just been defined as one piece of that string, so it is also just a string). So you can just:

logger.info(timerItem)

In ECMAScript 2021, the Item class supports setting Item metadata, but it only works with the value, not the config part of metadata. So timerItem.upsertMetadataValue('expire', newExpire); should work without importing the registry.

So that metadata variable isn’t the registry. I’m looking at the code and trying to figure out exactly what it’s supposed to be.

Ah, I think I have it. It’s what state you want to expire to. To get that you’d either want to replace the metadata variable with a hard coded state, or

let metadata = timerItem.getMetadataValue('expire');

That will get the current expire and the let newExpire line will extract the expire state from that.

} else {
  let newExpire = [newHours, 'h', newMinutes, 'm', newSeconds, 's', ', ON'].join(' ');
  timerItem.upsertMetadataValue('expire', newExpire); // use upsertMetadataValue if you want to create it if it doesn't exist
}

or

} else {
  let metadata = timerItem.getMetadataValue('expire');
  let newExpire = [newHours, 'h', newMinutes, 'm', newSeconds, 's', ', '+ metadata.split(',')[1]].join(' ');
  timerItem.upsertMetadataValue('expire', newExpire); // use upsertMetadataValue if you want to create it if it doesn't exist
}

Of course, if the Item doesn’t already have expire metadata the second one will fail.

I can change the expire value of the timer with the following code,

configuration: {}
triggers:
  - id: "1"
    configuration:
      itemName: Rule_ExpireModify
    type: core.ItemStateChangeTrigger
conditions:
  - inputs: {}
    id: "2"
    configuration:
      itemName: Rule_ExpireModify
      state: None
      operator: "!="
    type: core.ItemStateCondition
actions:
  - inputs: {}
    id: "3"
    configuration:
      type: application/javascript;version=ECMAScript-2021
      script: >-
        
        var logger = log('org.openhab.rule.ModifyExpire');

        var modifyInfo = items.getItem(event.itemName).state.split(',');

        var timerItem = items.getItem(modifyInfo[0]);

        var newHours = parseInt(modifyInfo[1]);

        var newMinutes = parseInt(modifyInfo[2]);

        var newSeconds = parseInt(modifyInfo[3]);

        logger.info(newHours +' '+ newMinutes + ' '+ newSeconds)


        if(newHours < 0 || 23 < newHours) {
          logger.warn('Hours outside of range expire metadata not set');
        } else if(newMinutes < 0 || 59 < newMinutes) {
          logger.warn('Minutes outside of range expire metadata not set');
        } else if(newSeconds < 0 || 59 < newSeconds) {
          logger.warn('Minutes outside of range expire metadata not set');
        } else {
          let newExpire = [newHours, 'h', newMinutes, 'm', newSeconds, 's', ', OFF'].join(' ');
          timerItem.upsertMetadataValue('expire', newExpire); 
        }

        items.getItem(event.itemName).postUpdate('None');
    type: script.ScriptAction

I have one problem left… after updating the timer the widget is reading his new value’s and al the fields H: M: S: are filled with the error code:
TypeError: Cannot read properties of null (reading ‘1’)
TypeError: Cannot read properties of null (reading ‘2’)
TypeError: Cannot read properties of null (reading ‘3’)

In edit mode I can remove this message and fill in my new timer value and save.
The value is correct written in timer problem in the widget is the same.

When I go to my items in de UI, and change the expire timer the widget is showing the new values I entered without errors.

In the logfile I don’t see any error’s or warnings.

I suspect that this problem is with this line in the rule:

There is a space between the single quotes in the join method, but there shouldn’t be. It should be:

let newExpire = [newHours, 'h', newMinutes, 'm', newSeconds, 's', ', OFF'].join('');

It seems that the OH core can properly parse the non-standard expire time code with the extra spaces, but the widget code (which I haven’t had the chance to clean up) isn’t as flexible.

Thanks, now its working without error’s.

Rich, I’ve only just moved over to Openhab v3 (took a week or two migrating DSL rules and learning new bits etc). I’ve bumped in a few new features, one of which is new lighting rules with expire timers, however, I’m sticking with the BasicUI for now, so cant use this widget above. As such, I was thinking I could create say 5x sliders or setpoints to change the minutes value only of my expire timers (so 1 to 60 values).

This widget works great, but I cant use it in the BasicUI. And try as I might (for a good many hours) to understand the JS code with reading forums or trying to do something in Blocky, I’m getting nowhere, bar confused with errors in my logs.

Obviously my items with expires set are On/Off switches, so for each item with an expire, Id need a matching item to use as the slider (as far as I’m aware or would do in DSL rules). To put that in simple terms:

Items

Switch PresenceDetect1
Switch PresenceDetect2
Switch Pre........etc

Number PresenceDetectExpireChanger1
Number PresenceDetectExpireChanger2
Number Prese......etc

Sitemap

Slider item=PresenceDetectExpireChanger1 minValue=1 maxValue=60
Slider item=PresenceDetectExpireChanger2 minValue=1 maxValue=60
Slider item=PresenceDetectExpir......etc

I read your Design Pattern here and many other forum posts, but I cannot seem to figure the way to make the right code that does what I’m asking for. Somewhere I’m getting lost between JS’s use of pulling in/using Item names, and I’ve tried with Blocky to re-create something, but struggling without a metadata option in there, and using the inline code block to affect the metadata of an item.

Do you know of, or would you be kind enough to show me an example of Javascript code, where it takes a value from ITEM-A and updates/changes the metadata value of ITEM-B?

As an addition perhaps I could even store in the Item that the Number Item is going to affect, in a custom metadata Namespace, so that the code can just look up there all the bits it needs… but again, so far this is beyond my understanding.

Thanks in advance if you manage to find time to help me with this.

You cannot access nor change Item metadata in Blockly. There are no blocks to do it.

Similarly you cannot do it in Rules DSL because there is no easy access to the MetadataRegistry.

I’ve not updated that post in a long time and it doesn’t have JS Scripting examples which is what is being used above.

In JS Scripting it’s simple.

var mins = items.getItem('PresenceDetectExpireChanger2').state;
items.getItem('PresenceDetect2').upsertMetadataValue('expire', mins+'m,command=OFF');

It always pays to look at the reference docs first when you want to know how to do something: JavaScript Scripting - Automation | openHAB

1 Like

Thanks!! Ill give that a go later. Very appreciated!

@JustinG , thanks so much for pointing us in this direction.

Would it be possible to replace the core f7-row component by a slider, instead of the somewhat difficult to edit HMS-text boxes. I have tried that. My struggles are:

  • how to set the initial value (as read from the metadata). when using oh-slider-item that’s not possible, via the value parameter. when using f7-range, that can be done via value, but not being a css expert, it’s difficult for me to determine the wrap-components around f7-range, which ressemble the oh-slider-item.

  • how would a value change on the slider trigger an action command (as is done through oh-link), to change the helper item and run the rule.

Probably not without several significant and bulky workarounds.

You might just be able to change the type property of the oh-input to range. That’s the most likely method to work. You will of course need to do a lot of reconfiguration of the styling to get it all to fit…