Jython scripting examples using lucid, an openHAB 2.x jsr223 Jython helper library

Here is another script, doing nothing else but how to retrieve various information about the event that caused the script to trigger.

showeventdetails.py

# Sometimes you need various information about the event that caused the script to trigger.
# This scripts demonstrates how to retrieve various event information using lucid.
#
# Please see: https://github.com/OH-Jython-Scripters/lucid/blob/master/README.md
#

from lucid.rules import rule, addRule
from lucid.triggers import ItemStateUpdateTrigger, CronTrigger

@rule
class ShowSomeEventInfo(object):
    def getEventTriggers(self):
        return [
            ItemStateUpdateTrigger('Button_Box_Sw_4'),
            CronTrigger(EVERY_MINUTE), # Runs every minute
        ]

    def execute(self, modules, inputs):
        self.log.setLevel(DEBUG)
        if self.event.isItem:
            self.log.debug('Triggering item name: \'' + unicode(self.event.itemName) + '\', state: ' + str(self.event.state))
            item = self.event.item # Get the item object for item that caused the event
            self.log.debug('item: ' + unicode(item.name) + ' isActive(): ' + str(self.event.isActive))
            self.log.debug('item: ' + unicode(item.name) + ' has a new state: ' + str(item.state))
        self.log.debug('event type: ' + self.event.type)
        self.log.debug('This was a cron event: ' + str(self.event.isCron))
        self.log.debug('This was a command event: ' + str(self.event.isCommand))
        self.log.debug('This was an update event: ' + str(self.event.isUpdate))

addRule(ShowSomeEventInfo())

Fantastic posting. thanks for doing this. We need more JSR223 examples and these are really helpful.

What would be really helpful would be:

  • Showing side-by-side so that users can translate their existing rules or adding comments to point out equivalent concepts between Rules DSL and Lucid. For example, I assume getEventTriggers is equivalent of the when clause in Rules DSL, execute is equivalent to the then clause in Rules DSL.

  • Try to include examples with all the different triggers (Item changed, changed from ON to OFF, et. al.) This was a major challenge for me when I was looking into JSR223 while back.

  • Really useful examples would be showing how to create/use Timers (or the Jython equivalent), Locks, and calls to Actions, in particular third party Actions (e.g. Mail). I assume the tts is a wrapper around the built in say Action, or is it Jython’s own implementation?

  • For at least one or two of the examples, a “theory of operation” where you describe using prose what the various parts of the code does. I’ve found this really helps the non-programmers interpret the code, though for JSR223 perhaps non-programmers shouldn’t be using it.

  • A few examples showing a couple of killer applications that can only be done in JSR223, or at least can be done much easier in JSR223 than Xtend.

Keep up the postings. These are great!

I was going to submit a PR for triggers.py into the openHAB-Jython repo. Is this one no longer going to be maintained? If so, I’ll submit to the lucid repo. But someone should probably make note of it in the README.md to point eople to the new repo.

I’m also curious… why didn’t you use the function decorators in triggers.py for your examples? I’m just getting into JSR223 Jython (about half way through migrating ~5000 lines of DSL rules). More documentation and examples will be very helpful… thank you!

Thanks for your kind words and for the suggetsions @rlkoshak

That would be a bit hard for me personally because I have very little experience in DSL Rule scripting. The lucid script examples are on GitHUB so there is a chance that someone with that knowledge will contribute doing just that. The repo is also “group owned” by openHAB Jython Scripters. We are now three members within the group and I’m sure others will join given some more time.

Good idea. I’ll sure do that!

I’ll have a look into that too.

Yes, the tts function is a wrapper for the DSL built in say action (eclicpse Voice). It can be used as simple as tts("Hello") for speaking “Hello” on the default speaker. There’s also the possibility to specify priority, room, language and voice. For example: tts("Hello", PRIO['HIGH'], ttsRoom='Kitchen', ttsVol=42, ttsLang='en-GB', ttsVoice='Brian') There’s also a way to turn off tts and playing of audio files whenever it’s inappropriate (when sleeping etc) by turning off a openHAB switch item named ‘Sonos_Allow_TTS_And_Sounds’. Then each script using the tts function won’t have to bother whether it’s a good time to speak or not. Specifying a higher priority when using the tts function will override the ‘Sonos_Allow_TTS_And_Sounds’ switch. I will for sure improve the documentation here.

It’s sure a good idea.

I don’t know if it can be referred to as a “killer application” but I’ve already published ideAlarm which is a Multi Zone Home Alarm Script depending on lucid in it’s latest version. It has performed really great here at my home. It has a wiki too. I can’t tell if something similar can be made in Xtend, at least I haven’t seen any approach.

There is also a small application for uploading data to Weather Underground which uses lucid. It’s named weatherStationUploader and I believe it’s a good starting project for getting acquainted to Jython scripting and getting started with using lucid. “Learning-by-doing”.

Thanks. I sure will because it’s fun and rewarding. It will also force me to make my own personal scripts more “generic” and “shareable”.

lucid won’t replace the openHAB-Jython repo. I created lucid because I wanted to merge my own handy utilities with the openHAB-Jython repo. My aim is to make life easier for people to get started with Jython scripting on the openHAB platform. I felt that in lucid I have more freedom to integrate whatever functionality I want using my own perspective of what’s useful or not. openHAB-Jython is a “masterpiece” as it is but honestly somewhat hard to grasp for a newbie. Given some time, lucid aims to lower the thresholds and allow coding at a higher level using some handy utilities.

Please submit your PR to both repos.

Initially when learning how to use openHAB-Jython’s trigger function decorators, I found out that I needed to access the function argument objects “self”, “modules”, “inputs” , but they weren’t there for me to use when using decorated functions. You can see my question here. I build some crucial logic using those objects so that’s why. The trigger function decorators way to do things look pretty but given the reduced functionality it gave me, I’ve chosen not to decorate my functions.

Today I’ll give you another example. This time we read configuration data from the lucid config file. No need for me to be verbose since all information is within the leading comment.

# This example scripts demonstrates how you can use lucid for reading
# various configuration entries that you have stored in the
# lucid configuration file.
#
# Q: Why would I want to do something like that?
# A: Jython scripts using lucid sometimes need a place to store various settings
# like a username, an application-ID, an update frequency etc. Those settings
# could be entered as constants directly in the main script but that makes it
# difficult to share them with other fellow Jython scripters and it's easy to
# accidentally overwrite the configuration data when downloading an updated script.
# Therefore lucid provides a module for storing this configuration data as
# demonstrated below.
#
# This configuration file is not restricted to "official" lucid scripts. That is,
# you might probably wan't to use it for your own personal scripts too!

# Unless you already have a lucid config file, please have a look at
# https://github.com/OH-Jython-Scripters/lucid#configuration-file for
# how to make one.
#
# This example script is reading a dictionary named "wunderground" as seen in
# the example config file: https://github.com/OH-Jython-Scripters/lucid/blob/master/automation/lib/python/lucid/example_config.py
# Add entries to the config file using your favorite editor.
# The syntax for the configuration file is pure python 2.7. 

from lucid.rules import rule, addRule
from lucid.triggers import CronTrigger

@rule
class readConfigExample(object):
    def getEventTriggers(self):
        return [CronTrigger(EVERY_MINUTE)] # Runs every minute
    def execute(self, modules, inputs):

        self.log.setLevel(DEBUG) # Set the logging level to DEBUG

        # Get a single value:
        sonsorName = self.config.wunderground['sensors']['tempc']
        self.log.debug('Read sensorname from the lucid config file: ' + sonsorName)

        # wunderground['sensors'] is a dictionary. Let's iterate through it
        self.log.debug('Iterate through a dictionary in the lucid configuration file')
        for the_key, the_value in self.config.wunderground['sensors'].iteritems():
            self.log.debug('Key: ' + the_key + ', Value: ' + the_value)

addRule(readConfigExample())

Script found in the repo here.
Configuration Example File here.

As before, just ask if you have any comments or questions.

Rules DSL is Turring complete. Anything that can be computed can be done in Xtend. Whether it can be done easily is the question.

I’d have to study the code, which I don’t have time right now especially since I don’t really know the OH and Lucid classes for OH, but it doesn’t look like something that would be hard in Rules DSL. Note that I’m talking about implementing the same functionality, not implementing in the same way.

I just wrote something along these lines in another posting in Rules DSL. There are several approaches one can use. I think my preference is to use Properties files.

import java.util.Properties
import java.io.FileInputStream

val prop = new Properties

rule "Test"
when
    Item Virtual_Switch_1 received command or
    System started // presumably one would want to load this at startup
then
    val String propFileName = "/opt/openhab2/conf/test/test.properties" // the name of the file really doesn't matter
    prop.load(new FileInputStream(propFileName))
    logDebug("Rules","Test: [{}]",prop.toString)
end


// To access a property from a Rule
    val myProp = prop .get("key")

It uses standard map files.

Of course, this does have the limitation that props will only be available from within the same .rules file.

1 Like

The following script example is cool but maybe not for everyone. (I’m not sure)

Using this script, you can make openHAB speak any message from an external host. For example when your cell phone rings, you can have openHAB announce the caller’s name. (By using the tasker app and by issue a http POST request to the REST API). Well, I leave it up to your imagination what you want to do with it.

First create an openHAB Item like this:

String Speak_This "Speak this [%s]" <text>

URL: curl -X PUT --header "Content-Type: text/plain" --header "Accept: application/json" -d "Hello world" "http://OPENHABHOST:8080/rest/items/Speak_This/state"

Script can be downloaded here.

from lucid.rules import rule, addRule
from lucid.triggers import ItemStateUpdateTrigger
from lucid.speak import tts

@rule
class SpeakThisPhrase(object):
    """
    Make openHAB speak a message that you've posted to the openHAB REST API from an external host
    This script watches an openHAB string item, e.g: String Speak_This "Speak this [%s]" <text> 
    Use curl from an external host to set the item to the text string that shall be spoken
    e.g. curl -X PUT --header "Content-Type: text/plain" --header "Accept: application/json" -d "Hello world" "http://OPENHABHOST:8080/rest/items/Speak_This/state"
    """
    def getEventTriggers(self):
        return [ItemStateUpdateTrigger('Speak_This')]
    def execute(self, modules, inputs):
        tts(self.event.state)
addRule(SpeakThisPhrase())

Setting up Tasker
This is a bit beyond the scope of this thread, but in case you’d like to set up Tasker on your Android phone to announce the name of who is calling you (by a TTS spoken message on openHAB), I attach 3 tasker tasks xml backup files.

To use it:

  • create 2 vars named OHSERVER and OHPORT. Set OHSERVER to Username:Password@myopenhab.org (Where Username and Password are your own login credentials to myopenHAB service). Set OHPORT to 443. In case you prefer to use your own LAN openHAB server instead, it should work too but then your openHAB Tasker scripts will only work while your phone is connected to your LAN Wifi.
  • Restore the three attached task xml files.
  • Create a profile in Tasker. Event: “Phone Ringing”, State: “Wifi Connected” and specify your Wifi’s SSID. (Use search while connected). Associate the profile with with the previously imported task named “Announce Caller”

Now you should be good to go. Ask your mother in law to call you, just to check!

Edit: Note, the tts function is currently implemented with the Sonos Speakers system in mind. It probably works for other systems as well but I should rename the variables to make it vendor independent.

ohSpeak.tsk.xml (486 Bytes)
ohRestAPI.tsk.xml (1018 Bytes)
Announce_Caller.tsk.xml (1.4 KB)

Could you post your Tasker task (even just a high level description). There are lots of examples on this forum but I know that someone will ask.

Does the Tasker task avoid sending the POST when you are not home?

I could see this being useful coupled with AutoVoice to send audio messages.

I will check that tomorrow, export it and post it here :smiley:

1 Like

Tasker backup files and instructions are now added to the original post. Good luck! :stuck_out_tongue_closed_eyes:

1 Like

yes! It checks that you’re connected to your home Wifi. That is, if you forget your phone at home (while being elsewhere) the Tasker task will certainly announce the callers. :joy::joy::joy:

Hi,

maybe a problem with my installation? When I am trying to run the “showeventdetails.py” example script, I get the followig error and I am nut sure why:

2018-07-12 23:04:00.010 [ERROR] [lucid.ShowSomeEventInfo             ] - Traceback (most recent call last):
  File "/srv/lib/jython/python/lucid/log.py", line 14, in wrapper
    return fn(*args, **kwargs)
  File "<script>", line 20, in execute
NameError: global name 'DEBUG' is not defined

I try to look into this tomorrow.

There was a missing import statement in the script. I’ve uploaded an updated script.

Today, I’ve added a new lucid script example at https://github.com/OH-Jython-Scripters/lucid/blob/master/Script%20Examples/astro.py

This script will calculate and save the “Time of day” and the “Solar time” of the day to items.
“Time of day” can be morning, day, evening or night. These are clock dependent.
“Solar time” can be dawn, day, dusk and night. These are dependent of the sun’s position relative the horizon.
So depending on where you live and the season, the solar time might be “day” while time of day is “night”
and vice versa.
This topic by @rlkoshak might also be of interest: Design Pattern: Time Of Day

If you try it out please let me know the results. Thanks.

1 Like

One thing I noticed in your script is that it will fail when there is a change in Daylight Savings becasue you are using now.withTimeAtStartOfDay. There was some discussion in the DP thread about solutions for this. Here is my preference…

Here is a version using Jython (w/ openhab2-jython).

Thanks @5iver for the suggestion. I wasn’t aware of that.

I’ve made these changes to my example script.

:sunglasses: :sunglasses: :sunglasses:

I’ve just started trying out JSR223/Jython but am having some difficulty in the addRule line when using the Lucid libraries/examples. Every rule fails with the no such field exception for ‘uid’:

2018-09-28 14:15:43.460 [INFO ] [rt.internal.loader.ScriptFileWatcher] - Loading script 'helloworld.py'
2018-09-28 14:15:43.492 [ERROR] [ipt.internal.ScriptEngineManagerImpl] - Error during evaluation of script 'file:/openhab/conf/automation/jsr223/helloworld.py': java.lang.NoSuchFieldException: java.lang.NoSuchFieldException: uid in <script> at line number 18

The offending line 18 is a simple:
addRule(HelloWorld())

Standard Jython rules (without going through Lucid libraries) work fine, e.g. automationManager.addRule(MyRule()).

Can anyone point me in the right direction on this?

Many thanks!

Can you please post the rest of the rule, and which version of OH are you using?

The full script (taken from Lucid’s examples folder) is:

from lucid.rules import rule, addRule
from lucid.triggers import CronTrigger



@rule
class HelloWorld(object):
    def getEventTriggers(self):
        return [
            CronTrigger(EVERY_MINUTE), # Runs every minute
        ]


    def execute(self, modules, inputs):
        self.log.info('Hello world from lucid!')


addRule(HelloWorld())

I’m on OH 2.4 snapshot build #1370.