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

jsr223
jython
lucid
Tags: #<Tag:0x00007fd311792f20> #<Tag:0x00007fd3117929d0> #<Tag:0x00007fd311792598>

(B Synnerlig) #1

lucid is an openHAB jsr223 Jython helper library. It’s a derivative work based on Steve Bate’s great project openHab2-jython

lucid can be used for general scripting purposes, including defining rules, triggers and conditions.

In this thread, I will provide some random jython script examples, all using lucid. My goal is to show you how easy it is to get started with jython scripting in openHAB.

Feel free to use this thread for any questions or comments you might have about these examples.


(B Synnerlig) #2

First script, greetings.py
All scripts can be found at lucid script examples.

# This scripts speaks a random greeting every minute on your Sonos speaker system.
# 
# Please see: https://github.com/OH-Jython-Scripters/lucid/blob/master/README.md
#
# It assumes that you've set up an openHAB a contact items to represent the presence of
# persons to be greeted. Each item should belong to the item group "G_Presence_Family"

from lucid.rules import rule, addRule
from lucid.triggers import CronTrigger
from lucid.speak import tts
from lucid.utils import greeting, PRIO
import random

@rule
class SayHello(object):
    def getEventTriggers(self):
        return [
            CronTrigger('0 0/1 * 1/1 * ? *'), # Runs every minute
        ]

    def execute(self, modules, inputs):
        greetings = [greeting(), 'Hello', 'How are you', 'How are you doing', 'Good to see you', 'Long time no see', 'It\’s been a while']
        peopleAtHome = []
        for member in itemRegistry.getItem('G_Presence_Family').getAllMembers():
            if member.state.toString() == 'OPEN': peopleAtHome.append(member.label)
        random.shuffle(peopleAtHome)
        msg = random.choice(greetings)
        for i in range(len(peopleAtHome)):
            person = peopleAtHome[i]
            msg += ' '+person
            if i+2 == len(peopleAtHome):
                msg +=' and'
            elif i+1 == len(peopleAtHome):
                msg +='.'
            elif i+2 < len(peopleAtHome):
                msg +=','
        #tts(msg, PRIO['HIGH'], ttsRoom='Kitchen', ttsVol=42, ttsLang='en-GB', ttsVoice='Brian')
        tts(msg, PRIO['HIGH'], ttsRoom='Kitchen', ttsVol=42, ttsLang='en-IN', ttsVoice='Aditi')
        #tts(msg, PRIO['HIGH'], ttsRoom='Kitchen', ttsVol=42, ttsLang='en-US', ttsVoice='Matthew')
        #tts(msg, None, ttsRoom='All', ttsLang='de-DE', ttsVoice='Vicki')
        #tts(msg) # Also works if you accept the defaults

addRule(SayHello())

The function greeting() will render polite standard greetings depending on the time of day. This scripts add to that list of greetings.


(B Synnerlig) #3

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
from lucid.utils import getEvent
from logging import DEBUG, INFO, WARNING, ERROR

@rule
class ShowSomeEventInfo(object):
    def getEventTriggers(self):
        return [
            ItemStateUpdateTrigger('Button_Box_Sw_4'),
            CronTrigger('0 0/1 * 1/1 * ? *'), # Runs every minute
        ]

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

addRule(ShowSomeEventInfo())


(Rich Koshak) #4

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!


(Scott Rushworth) #5

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!


(B Synnerlig) #6

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”.


(B Synnerlig) #7

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.


(B Synnerlig) #8

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
import lucid.config as config
from logging import DEBUG, INFO, WARNING, ERROR

@rule
class readConfigExample(object):
    def getEventTriggers(self):
        return [CronTrigger('0 0/1 * 1/1 * ? *')] # Runs every minute
    def execute(self, modules, inputs):

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

        # Get a single value:
        sonsorName = 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 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.


(Rich Koshak) #9

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.


(B Synnerlig) #10

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.utils import getEvent
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):
        event = getEvent(inputs)
        tts(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)


(Rich Koshak) #11

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.


(B Synnerlig) #12

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


(B Synnerlig) #13

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


(B Synnerlig) #14

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:


(Juelicher) #15

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.


(B Synnerlig) #16

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