Error with openhab(?) py script 100_DirectoryTrigger.py after Update to 4.0.1

I looked into it, because I didn’t remember myself exactly :face_with_peeking_eye:
It is a simple light that I toggle with a single button of a remote control. So I have this one rule that reacts on the key press, checks the current light state and commands the opposite. And I have a second rule that reacts on a motion sensor and switches on the light, but only if the current luminance is beyond a threshold (i.e.: it is dark enough to turn the light on).
Maybe I overengineered it, but I wanted to eliminate any race conditions in case both rules happen to run at the same time. So I used a lock object for both rules to isolate them - that’s it.

Absolutely, I wouldn’t recommend that either. But I really like Python, and consider it the perfect language for small and readable automation tasks. It is the closest to executable pseudo code that I know of.
And at the same time, I could never get used to JavaScript in the last 25 years. So I celebrate each day that my syntax of choice is still supported (or at least: works). When the day comes, I might switch to HabApp like you suggested.

But let’s not focus on the things that might not work in future. My main point is that I’m very happy with the OpenHab ecosystem, and I’m overwhelmed from all the new add-ons that are available compared to OH1/2.

I found this add-on, ‘Virtual Solar Light Sensor’, that combines the current sun elevation and a ‘cloudiness’ value from a weather API to approximate the luminance outside. And this is exactly what I did in the last years with some rules to trigger my shutters in the evening. Nice to see that other people invent the same crazy workarounds, and spend hours just to save 20€ for a light sensor :sweat_smile:.

1 Like

I probably would have put them both into the same rule. Then you have more control over how to handle the race condition in the first place.

jRuby is pretty nice in that regard. It’s also more terse and reads more like English than Python in many ways.

I’m a generalist. I don’t advocate for any one language over the others, as long as those languages are roughly at parity in capabilities when it comes to OH. I use JS Scripting day to day mostly because it helps me support the Blockly users better.

Someday, when I have more time, I hope to get Clojure or some other functional language (I saw a thread where someone got Scheme working I think) working as a rules language for us old timers. :smiley:

I use that rule template myself. It’s written in JS Scripting and works quite well. The nice thing about rule templates is you don’t have to write the code yourself so it doesn’t really matter what language it’s written in.

If you like “small, readable, and closest to pseudo code”, check out
Python to Ruby Rule Conversion

Actually this looks very nice. Maybe worth to learn a new syntax.
Although the author’s obvious intent was to make look Python as ugly as possible. Python doesn’t even know the ‘&&’ operator :wink:

I think the intent was to show how to translate Jython with the third party helper library to jRuby in a way where most of the concepts have an obvious mapping.

  • How do I define a rule?
  • How do I define a trigger?
    etc.

If there are errors in the Python version on that page I’m sure that they would welcome a correction.

LOL :slight_smile:

I thought it was taken from the author’s actual python rule when he converted it to ruby. But yeah, if python doesn’t have a && operator, that does makes me wonder too. I am a co-maintainer / apprentice for the jruby library. I’ll fix that example and make the Python look as good as it can be! :slight_smile:

1 Like

I had the same idea when I thought about it a second time, and just came back here to answer to my own post - obviously you were much faster :smiley: No need to increase complexity with locks when you can just subscribe to multiple items in the first place. Didn’t think of that when I wrote them back in - must have been 2017/2018.

If I remember correctly you once documented some common patterns that cover most of the common use cases - yes, I found them:

I’m slowly getting really motivated to do a fresh start on some free weekend.

I can’t think of how to simplify or make the Python code any more succinct or nicer to read.

Yeah maybe I would have written it like this (no running code as well, but you get the idea). And it’s obvious I prefer camel over snake case - a real Python developer would sure hate me for that :smiley:

officeTemperatureItem = ir.getItem("Office_Temperature")
hallTemperatureItem = ir.getItem("Thermostats_Upstairs_Temp")
heatSetItem = ir.getItem("Thermostats_Upstairs_Heat_Set")
occupiedItem = ir.getItem("Office_Occupied")
doorItem = ir.getItem("OfficeDoor")

THRESHOLD = 2.0

@rule("Use Supplemental Heat In Office")
@when("Item Office_Temperature changed")
@when("Item Thermostats_Upstairs_Temp changed")
@when("Item Office_Occupied changed")
@when("Item OfficeDoor changed")
def office_heater(event):
  officeTemp = float(str(officeTemperatureItem.state))
  hallTemp = float(str(hallTemperatureItem.state))
  heatSet = int(str(heatSetItem.state))
  officeOccupied = str(occupiedItem.state) == "ON" or False
  doorOpen = str(doorItem.state) == "OPEN" or False
  
  difference = hallTemp - officeTemp
  
  if officeOccupied and not doorOpen and heatSet > officeTemp and difference > THRESHOLD:
    events.sendCommand("Lights_Office_Outlet","ON")
  else:
    events.sendCommand("Lights_Office_Outlet","OFF")

Going on a tangent… during our development of jruby library, we discovered that the object you got from ir.getItem could become stale, i.e. pointing to the old object that is no longer active, if the item got redefined (e.g. .items file edited / changed, or perhaps even managed UI item changed).

This is why in JRuby library, items are proxy-cached to avoid this issue, so it’s safe to do something like this in jruby (although not necessary, but here to demonstrate the idea)

door_item = OfficeDoor

rule do
  changed XXxxxxxx
  run do
    if door_item.open?
      # do something
    end
  end
end

Both the item constant OfficeDoor and the reference to it door_item will continue to point to the actual active item even if the item was updated throughout the lifetime of the script. Even items obtained through office_door = items["OfficeDoor"] is managed the same way so office_door doesn’t get stale in this case either.

So the safer way for the Python code is to keep the ir.getItem calls inside the rule, to be called every time it’s needed.

I know this is probably considered an edge case, but one that nonetheless could rear its ugly head.

From my experience, the ugly type conversions are necessary to do it the ‘Python’ way, but this due to Java/Jython object translation. Never cared much about it, perhaps there is a better way.

And since the items are already assigned by name, I would try to avoid passing the same name again in the annotations, and instead create the trigger as an object. After that, I would feel comfortable with that.

Plus: I intentionally ignored the Celsius to Fahrenheit conversion because - come on, there’s only one proper way to do it, isn’t it :smiley: Just kidding, of course this is just an example for a transformation - let me add it in a second.

But still, I see the advantages of the Ruby version.

I’m sure you’re right, so this might be a version that can be compared to Ruby:

THRESHOLD = 2.0

@rule("Use Supplemental Heat In Office")
@when("Item Office_Temperature changed")
@when("Item Thermostats_Upstairs_Temp changed")
@when("Item Office_Occupied changed")
@when("Item OfficeDoor changed")
def office_heater(event):
  officeTemp = ir.getItem("Office_Temperature").getStateAs(QuantityType).toUnit(ImperialUnits.FAHRENHEIT).floatValue()
  hallTemp = items["Thermostats_Upstairs_Temp"].floatValue()
  heatSet = items["Thermostats_Upstairs_Heat_Set"].intValue()
  officeOccupied = items["Office_Occupied"] == ON
  doorClosed = items["OfficeDoor"] == CLOSED
  
  difference = hallTemp - officeTemp
  
  if officeOccupied and doorClosed and heatSet > officeTemp and difference > THRESHOLD:
    events.sendCommand("Lights_Office_Outlet","ON")
  else:
    events.sendCommand("Lights_Office_Outlet","OFF")

I mean, the comparison is not ultimately fair, because in the Ruby example the items are already there and even proxied. Of course this could be achieved in Python as well, then the examples would be more similar. But in the context of ‘what is possible right now with Jython and JRuby integration’, I think this is a better example than the current one.

Office_Temperature is probably a dimensioned item in the author’s system. I’m rusty with my Python (I actually learned python because of openHAB to use Jython in 2019)

>>> float(str("1.5 °F"))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: could not convert string to float: '1.5 °F'

so the whole .getStateAs(QuantityType).toUnit(ImperialUnits.FAHRENHEIT).floatValue() seems necessary to me, and he probably wanted to make sure he’s working in Fahrenheit even if the item is in celsius.

OK, I see and changed it back. But this is not Python, this is a call to the OpenHab Java object, like the former ‘floatValue()’ calls.

I suspect it was more a case of “This is the rule that I had written in Jython, and this is how I converted it to JRuby”. I mean, the sets of conditions were pretty uniquely peculiar to start with.

I changed it back to the way the item values were accessed before, to avoid str/float conversion that can break your neck when it comes to locale dependent decimal points and stuff, so now the only significant change is that I replaced the wrong operator with the Python ones :rofl:
Good enough for a PR though.

1 Like

I see it was already committed an hour ago, just leave it as it is right now, I’m totally fine with it. I’m happy to help improve the example, and it was a pleasure to play around with the code :slight_smile:

This is true in Rules DSL too. I ran into that a long time ago when I tried to use an Item as a key in a Map instead of the name of the Item.

I’m not sure about JS Scripting. I suspect it does something. similar to jRuby, but I never looked.

Both jRuby and JS Scripting try to manage those conversions in the add-on/helper library so you end up working with native Objects that work in typical ways for the language. The developer of the Jython Helper library chose the opposite route and tried to keep openHAB stuff Java, but as a result you have to be careful to know when you are working with Java and when you are working with Python types.

Both approaches have their advantages and disadvantages.

JS Scripting‘s items.getItem is actually calling ItemRegistry.getItem and creating a JS Item from it without any caching.
So as long as you don’t load an Item as constant in file-based rules outside of the rules callback, I don’t suspect that issue to occur.
For UI-based rules that would need to be tested, since the whole lifecycle thing is different there.

1 Like

Since there is no “outside the rule” in this case, I don’t think there would be a problem. Unless you put it into the cache you’d be pulling the Item from the registry every time the rule runs so it shouldn’t get stale. If you do put the Item into the cache, well that would probably be a problem though, but that’s true in the UI or file based rules.

1 Like