JSR223 Jython Replacement for Expire Binding

Tags: #<Tag:0x00007fc3f1067e50> #<Tag:0x00007fc3f1067d60> #<Tag:0x00007fc3f1067c98>

As a challenge to myself I wanted to see if I could replace the Expire binding using Rules. The answer appears to be yes.

See https://github.com/openhab-scripters/openhab-helper-libraries/pull/259 for a drop in replacement to the Expire binding. You don’t even need to change your Item configs. The Rule will pick up your existing Expire binding configs and use them.

The only limitation is you must cause OH to reload the script when you make a change to any Expire binding config to pick up those changes.

I don’t expect this to be a permanent replacement for the binding. Instead it can be a bridge for those who want to get off of the 1.x binding before there is a 2.x replacement in place.

I’d love it if a few of you could try it out and tell me if there are any bugs. I tested it pretty heavily on my own system but it’s impossible for one developer to think of everything.

3 Likes

A couple of limitations of expire binding that have cropped up, from memory.

You cannot configure expire= 1.x on a Group type Item, validation just fails at load time.
It would be legitimate to send a command to Group upon expiry (though not a state update).
Example use case, issuing a REFRESH if a bunch of Items all fail to update.

You might remember this one


Couple of things arising from that -

  1. Need a means to set an empty String type Item state.

  2. More generally, setting a ‘true’ UNDEF state was tricky. If you allowed expire 1.x to do its default thing, that’s fine. But if specify UNDEF as a parameter, it ends up as string “UNDEF” in String Item case.
    That’s probably more about the 1.x postUpdate workings than expire in particular, but I expect that is more visible/tweakable in a rules type solution.
    To be clear I do think that is correct behaviour for expire 1.x, because the parameter format doesn’t use quotes to designate strings.

Just highlighting these, in case you wanted to test or take account for your tool :smiley:

I see no reason why this implantation wouldn’t work on a Group Item as long as the Group Item had a typa and therefore receives updates and change events. There might be a race condition though with updates happening so fast on a Group so it definitely needs testing.

As for 1 and 2, that’s tricky to handle. How do you define an empty String in the config? That would take some thought.

The UNDEF part I think I can handle, but it means you can never set a String Item to the String “UNDEF”. In this case I don’t think we can have it both ways for exactly the reason you sight,

I can maybe see about adding single quotes to the state part of the config but I’m not certain that’s allowed and if it is it will greatly complicate the passing and checking. That’s probably why the original never supported it.

These are good suggestions. Thanks!

You could use strings with weird framing for special meaning like expire="1m,::NULL::".

@rlkoshak

Say with the metadata expire="1m,''" you could do this (not tested):

from ast import literal_eval
from core.utils import sendCommand, postUpdate

# special values mapping
special_values = {
    "::NULL::": UnDefType.NULL,
    "::UNDEF": UnDefType.UNDEF,
    # and etcetera
}

# separate time string and action string
time, state = str(get_value(item, "expire")).split(",", 1)

# separate action and command/state if action specified
command = None
if len(state.split("=", 1)) > 1:
    command, state = state.split("=", 1)

# pick function
if command == "command":
    action = sendCommand
else:
    action = postUpdate

# try to parse state into a python type
# if state is `''` you will get back an empty string
try:
    state = literal_eval(state)
except:
    pass

# see if state is a special
if state in special_values then:
    state = special_values[state]

# do action
action(item, state)

That is along the lines I was thinking. Add an optional single quotes to the state. I’ll try out these ideas later this weekend.

If we allow single quotes, I don’t think we need the special values. We can just look for the single quotes and assume a String and without we can check to see if it’s one if the known special words.

But this raises a question, how do I send UNDEF to a String Item in Python? events.sendCommand takes two Strings, right?

Look at the whole example I posted. I think core.utils.sendCommand and core.utils.postUpdate will send the real value not a string.

Well, the 1.x expire actually can manage this both ways.
It does work on a String type Item with “proper” UNDEF if (and only if) you allow the parameter to default.
If you give the parameter as UNDEF, that is (quite reasonably) assumed to be the string you want i.e. “UNDEF”.
Maybe you can make a similar arrangement.

p.s. yes wot @CrazyIvan359 said abut UNDEF - it is NOT allowed as command.

The code as originally posted handles Group Items as one would expect. You can put the config on the Group Item. It’s worth noting that only a command is reasonable to put on the Group through. Updating a Group isn’t very meaningful and I’m not entirely certain what would happen. I don’t think there will be an error.

Group TestGroup { expire="5s,command=OFF" }

Switch ExpireTestItem1 (TestGroup)
Switch ExpireTestItem2 (TestGroup)

Notice I didn’t even supply a type to the Group.

2019-09-25 12:25:50.373 [ome.event.ItemCommandEvent] - Item 'ExpireTestItem1' received command OFF
2019-09-25 12:25:50.377 [vent.ItemStateChangedEvent] - ExpireTestItem2 changed from ON to OFF
2019-09-25 12:25:50.378 [vent.ItemStateChangedEvent] - ExpireTestItem1 changed from ON to OFF
2019-09-25 12:25:55.372 [ome.event.ItemCommandEvent] - Item 'TestGroup' received command OFF
2019-09-25 12:25:55.373 [ome.event.ItemCommandEvent] - Item 'ExpireTestItem2' received command OFF
2019-09-25 12:25:55.374 [ome.event.ItemCommandEvent] - Item 'ExpireTestItem1' received command OFF

OK, I looked into it some more. It looks like if you pass the actual Item to events.sendCommand then you can pass the actual State/Command enums. If you just past the Item name, you can only pass to it the state/command as a String. At least that is how I interpret the docs:

https://openhab-scripters.github.io/openhab-helper-libraries/Guides/But%20How%20Do%20I.html#send-a-command-to-an-item

I’ve taken your ideas and come up with a surprising result. Apparently it’s impossible to set the state of a String Item to the String “UNDEF” or “NULL”. Try as I might, events.sendCommand/postUpdate always converts the String to UNDEF whether it’s a String or not.

The good news is it seems to handle that case a little better than the Expire Binding itself. Instead of ending up with “UNDEF” if you provide the state you always get UNDEF. I’m pretty much at a loss though right now as to how to fix this.

Anyway, I’m about to check in the following changes:

  • You can supply '' as the state to expire a String Item to the empty string.
  • No matter whether you use ‘UNDEF’ or UNDEF, the Item will get set to the state UNDEF. I can’t figure out any way to set the Item to the string ‘UNDEF’. No matter what I try, events.postUpdate converts it to the UnDefType.UNDEF. I’ve used the utils.sendCommand, I’ve used events directly passing the Item as both an Object or the item name and no matter what I try, the String “UNDEF” gets converted to the UnDefType.UNDEF.

Off the top of my head, try a raw or Unicode string.

r'UNDEF'
u'UNDEF'

Or explicitly pass it a StringType('UNDEF') maybe?

Just to clarify, you can currently use expire 1.x to set either UNDEF by default, or “UNDEF” at your choice.

You can’t set NULL with expire 1.x, only “NULL”, but we might say that is a Good Thing as that should really only get set by system at startup - this is what UNDEF is for.

Sounds like the issue with “new” UNDEF is more generalized than just this script - it’s hard to see why you might want a string “UNDEF” but there again, why not.
Be useful for all to find out the trick.

Good ideas.

I can only pass it a StringType if the Item is of type StringItem. And isinstance(item, StringItem) returns false, even for Items I know are a StringItem. I’ve imported from org.openhab.core.library.items import StringItem. It would seem that ir.getItem("ItemName") returns some other type. Know of any way to test for the type of an Item? Never mind, I figured it out. if item.type == "String":

That looks like StringType did the trick. The following all now work as expected on String Items:

  • expire="5s": StringItem (any Item really) gets set to UNDEF
  • expire="5s,state=''": String Item expires to empty String
  • expire="5s,state=UNDEF": String Item expires to UNDEF
  • expire="5d,state='UNDEF'": String Item expires to the String “UNDEF”

I hate having to deal with StringItems differently but it’s only a couple lines of code.

I suppose I should note though that this now means it’s an “almost” drop in for the Expire1 binding. The behavior is just a little different from the Expire1 binding in the case where you supply state=UNDEF. Expire1 will use the String “UNDEF” and this script will use UNDEF.

Thanks for the help!

Changes are checked into the PR.

2 Likes

Wow, impressive amount of effort, all in the pursuit of recovering from an impending loss of basic product functionality for no actually sensible reason! It seems tragic that you have developers pitted against users, where the developers take away your functionality for ideological reasons, and users have to cobble together workarounds. In the commercial world, such an abuse of users would have killed off the software company in preference to its competitors. All the while, in an alternate reality, you could instead have developers and users working together to solve real-world challenges instead of recovering from self-inflicted wounds. Seems tragic, but also further validates my old decisions.

Best of luck to you users, and may your efforts cease “expiring!”

No, all in pursuit of learning and experimenting. And even if Expire were going away for sure, which is in no way a done deal, I would have done the above. I personally do have problems with the 1.x bindings. They are keeping me from moving my Items to the JSONDB where, based on my experience, everything loads much faster and much more reliably without the startup timing issues that have existed since OH 1.6. so this was one user solving a personal problem and sharing the solution with the community. Is that not how FOSS supposed to work?

Still looking for someone to volunteer to maintain the compatibility later though. In the commercial world you can pay someone to maintain it. In the foss world you don’t have that leverage. Would you chain every developer to any code they have ever released for life? I certainly would release a longer if code if that were the case.

Users either need to be flexible and adjust with the changing sets of developers, priorities, and inconsistent quality and advancement of the product or they need to go buy a commercial product where those qualities are easier to control. In a pure volunteer project like openHAB, there is no other option.

I think it’s great you are still finding interesting projects. I just think it’s unfortunate that you have to operate with a worry that you will lose functionality that people have relied on for years, and set out to reinvent it with tools that, hopefully, you won’t also lose someday.

The threat of breaking the assumed contract of not stripping old functionality by removing the 1.x features, is the “Brexit” of openHAB. Should we/shouldn’t we sever off one of our own limbs, strand users and suppress interest in us by being thought of as contract breakers?

The premise is false that the 1.x bindings are the barrier, while the architect(s) have chosen to create a two-headed situation for users and could easily choose to apply their talents to fixing that instead of jettisoning a massive body of work contributed by others, all the while ignoring the loss that represents of core capabilities not present elsewhere in the product.

It’s also mistaken to believe there is a chain from every developer to the code he/she contributed; it is perfectly normal for others to submit PRs on code they didn’t originally contribute. Writing code is not magic!

A tone not seen in many and probably most FOSS projects, is the threat of breaking a major “contract” with the user, but you have reinforced the idea that “users need to be flexible” to the point that they lose core capabilities and just suck it up and rethink years-old work, with possibly no viable path forward. For every other project out there that doesn’t hang this over its users’ heads, all else being equal it would have much more interest and enthusiasm.

But you’re definitely right – it is what it is, and it’s becoming what it’s becoming.

I completely agree. But no one is doing it. There is no one continuing to the compatibility layer. Unless and until someone steps up to do so, it’s dead code for all intents and purposes.

I’m not saying that people can’t contribute. I’m saying people are not doing so. If just one developer is willing to step up and take on maintaining the compatibility layer there wouldn’t even be a problem. No one has volunteered. No one has stepped up. It’s been nearly a year since this topic first came up and still no one has volunteered.

Some are vocal about wanting to preserve it yet no one is willing to step up and do something about it.