Jsr223: script collection

Tags: #<Tag:0x00007f6188d31f80> #<Tag:0x00007f6188d31e90>

As I have some rather unusual scripts, I wanted to publish them in the hope they become handy for someone. Even if you don’t have the devices or setup itself, it might become handy the way they were scripted.

Common Library

I build some scripts that are reusable in multiple script sets. For this reason I added a directory with common scripts which get sourced into the python script. For some reason it does not work with default import mechanism of python, but execfile is working fine.

The library can be found here: https://github.com/smerschjohann/openhab-jsr223-scripts

Exec/CommandMap Rule

This Script consist of a general ExecRule class which does the general mapping. You simply define which items should be listened on and it will translate commands -> values.
In this example it maps commands to IR values, but it could map to a more complex object as well. It will call self.commandTriggered with this value.

In this example it is overloaded by the method in IrTransmitter which will call the appropiate command.

execfile("/opt/openhab/configurations/scripts/common/execMap.py")

def getRules():
    irTransmit = IrTransmitter("DENON", {
        "ReceiverPowerCmd": { "ON": "ON", "OFF": "OFF" },
        "ReceiverVolumeCmd": { "ON": "VOL+", "OFF": "VOL-" },
        "ReceiverChannel": { "PC": "4", "PI": "5" }
    })

    return RuleSet([irTransmit])

Alive Checker

This checks devices/PCs by simple ICMP messages if they are online.

execfile("/opt/openhab/configurations/scripts/common/aliveChecker.py")

def getRules():
    return RuleSet([
        AliveChecker([("simon", "SimonPC")]),
	])

CountdownTimer

This is a configurable timer. It will use three items to show its states:

  • timerItem: activates/deactives the timer
  • timerCurrent: the current remaining time (in minutes), before the commandItem will receive “OFF” command
  • timerDuration: the initial duration until OFF is sent
  • commandItem: the command receiving item

item config:

Switch ReceiverPower "Receiver Power" <socket>
Switch Receiver_TimerActive <clock> (livingroom)
Number Receiver_TimerDuration <clock> (livingroom)
Number Receiver_TimerCurrent <clock> (livingroom)

sitemap config:

Frame label="Multimedia Timer" {
    Switch item=Receiver_TimerActive label="timer activ" icon="clock"
    Setpoint item=Receiver_TimerDuration label="duration [%d]" icon="clock" minValue=1 maxValue=59 step=1
    Setpoint item=Receiver_TimerCurrent label="remaining [%d]" icon="clock" minValue=1 maxValue=59 step=1 
}

script:

execfile("/opt/openhab/configurations/scripts/common/countdownTimer.py")

def getRules():
    return RuleSet([
        CountdownTimer("Receiver_TimerActive", "Receiver_TimerCurrent", "Receiver_TimerDuration", "ReceiverPower")
	])

AlarmClock

This is a configurable clock alarm. You can configure hours and minutes of the next alarm and activate it by items.

  • itemActive: activate/deactivate alarm
  • itemHours: hours of next alarm
  • itemMinutes: minutes of next alarm
  • commandItem: item which will receive “ON”

items:

Switch Receiver_WeckerActive <clock> (bedroom)
Number Receiver_WeckerHours <clock> (bedroom)
Number Receiver_WeckerMinutes <clock> (bedroom)

sitemap:

Frame label="Multimedia Wecker" {
    Switch item=Receiver_WeckerActive label="alarm active" icon="clock"
    Setpoint item=Receiver_WeckerHours label="hours [%d]" icon="clock" minValue=0 maxValue=23 step=1
    Setpoint item=Receiver_WeckerMinutes label="minutes [%d]" icon="clock" minValue=0 maxValue=59 step=1 
}

script:

execfile("/opt/openhab/configurations/scripts/common/alarmClock.py")

def getRules():
    return RuleSet([
        AlarmClock("Receiver_WeckerActive", "Receiver_WeckerHours", "Receiver_WeckerMinutes", "Spotify"),
	])

Miscellaneous

Buderus KM200 “Binding”

This is more like a binding than a script, as it will connect to the KM200 device, receives its values and updates items in openhab based on that.

it uses:

  • Java Crypto API (you have to change the JCE policy
  • Time trigger
2 Likes

Looks very useful - however I’m not sure whether this would be better being put on the Wiki to keep all documentation and examples together?

@Kai what are your thoughts here?

yes, I wanted to link this thread and the repo on the wiki page.

I posted it here, because:

  • of possible feedback and discussion
  • additional scripts without github knowlege :wink:

The github-repo could be moved to the openhab user if that fits better :wink:

Most likely this is due to the lack of an __init__.py file in the common directory. Create this file (it can be empty) and you should be able to import with import common.<scriptname>.

Nice work!

Thanks, these look useful.

I’ve been able to import Jython modules into my JSR223 scripts. Are you setting the Jython python.path system property on the openHAB command line? For example…

-Dpython.path=/opt/openhab/configurations/scripts/common

Jython uses that instead of the normal CPython environment variable.

Is the purpose of this to avoid some of the quirks of the Java InetAddress.isReachable function?

I see you’re using the operating system ping command and redirecting to /dev/null. This doesn’t seem like it would work on Windows. You might want to check the operating system the script is running on and raise an exception if the operating system isn’t supported.

I’m doing something similar with ping but I’m also extracting the latency from the ping output and updating an item with the value. I monitor the latency to specific internet hosts to know if my WAN connection is slow even if it’s active.

ok I was not specific enough here. So what I tried was:

  • define all methods in __init__.py
  • import the script by:
import sys
sys.path.append("/opt/openhab/configurations/scripts")

import common

The main problem was, that my injected methods into the JSR223 runtime are not available in the common module. I tried to import them by import org.openhab... but that did not work out either. So your imported modules access the Openhab-Runtime or are they plain python scripts?

Yes, I use the ping command here because I don’t need to run it as root (on unix) and second it works even if the route to the host is more than one hop away…

It would be nice to see your script, that would be a nice addition.

Yes, my modules access the OH runtime. I have an openhab package with a module called “globals” that exports similar types and values as provided through the JSR223 engine state. For example, my other modules then use that globals module by using an import like the following:

from openhab.globals import oh, ItemRegistry

This is equivalent to

from org.openhab.core.jsr223.internal.shared import Openhab as oh
from org.openhab.core.jsr223.internal.engine.scriptmanager import ScriptManager
ItemRegistry = ScriptManager.getInstance().getItemRegistry()

My thoughts are that such scripts would make a nice rule modules for Eclipse SmartHome - I’d rather wish to see such efforts spent there, since with the move to openHAB 2, I worry that most of this documentation here won’t be applicable anymore…

@smerschjo Did you already have a chance to look at the new rule engine? As it is merged into the main repo by now, it would be great if you could try to get Phyton working in the script modules, which so far only support Javascript.

Well, those modules were used in my setup already - so no big deal.:wink:

I tried to setup the eclipse dev environment one or two days after the merge, but the rule engine project was not buildable (as it was missing in the IDE, I tried importing it manually but that did not resolve the issues). I will recheck in the next days. I will try to port my setup to smarthome/oh2, check what is missing for jython and use that as a daily driver.

Is there any (unfinished) documentation on the rule engine already?

@Kai, I took a quick look at the EH JSR223 support. It appeared that the support was limited to scripted conditions and actions in the JSON-represented rules. Is that correct?

Will you describe how one of Simon’s simpler rule classes (e.g. AliveChecker, a parameterized rule template) would be represented in EH?

I indeed did not yet include it in the openHAB 2 IDE. If you use the openHAB 2 IDE with ESH bundles, it should be easy though to manually import the projects as they are already checked out in git/smarthome as well. Let me know if you need any help!

Yes, right here: https://github.com/eclipse/smarthome/blob/master/docs/documentation/features/rules.md

That’s the whole idea: The modules can be implemented through scripts - you could actually also use a Javascript-Trigger with a Python-Action.

Will you describe how one of Simon’s simpler rule classes (e.g. AliveChecker, a parameterized rule template) would be represented in EH?

It will be described as a “parameterized rule template” :smile:
See https://github.com/eclipse/smarthome/blob/master/docs/documentation/features/rules.md#rule-templates

But please don’t ask too many questions yet - the rule templates are still in an early phase and I haven’t dived into examples myself yet. But the whole idea is that you have a template “AliveChecker” that comes as a JSON structure that contains the scripts and which the user can simply “instantiate” by providing a few configuration settings.

But if you are interested, you are invited over to ESH for further discussion; for openHAB 2, I have taken the new rule engine out of the scope for the first beta.

I’ve read the “New Module Rule Concept” thread a while ago. I’ll read through it again to see the latest thoughts.

My biggest concern is that ESH is focussing on a rule representation that is easy to manipulate in a UI. That’s fine since many users are not programmers and a rule development UI will be a great feature for them.

However, as a programmer, I’d want to see something similar to the OH1 JSR223 binding that allows advanced extension of the runtime functionality using languages like Jython without the need to combine the scripting language with predefined JSON structure.

I’m hoping if the general JSR223 support is not initially available in ESH or OH2 that it will be possible to add it.

I will have a look at exactly this areas as I use some of these extended features of the current implementation as well. As I see it, the only thing missing (if I haven’t overlooked it) is the possibility to programmatically combine trigger, condition and execution logic.

If there is an API for that we would have both: modular scripts and IDE support if needed/wanted.

Yes, this is exactly what we had discussed at the time. The new rule engine simply defines rules as a combination of triggers, conditions and handlers and you can have programmatic ways of providing this, so this would have to be implemented in order to have a “full” backward compatibility.

Well, what you are doing is to squeeze it into some Jython format and I would challenge you if this is really the best way to define triggers. Especially once the set of triggers grows at ESH, you might want to be able to directly use them without having to come up with some further extension to this Jython format.

I am not saying that I find JSON better suited in any way - I definitely do not want to write this by hand.
So I am rather thinking of a simple text format, where we could have a general parser that is independent from the used language and could construct the rules.

Just typing while not thinking something like

Rule: <rule name>
Description: <bla bla bla>

ON
itemChangedTrigger { itemName="MyItem" }
cronTrigger { cronExpression="0/5 * * * *" }

THEN
script(python) [
 <your script goes here>
]-endscript

script(javascript) [
 <your script goes here>
]-endscript

The challenge is to make the textual representation anywhere as close as powerful and flexible as a programming language like Python/Jython. There’s rule parameterization but the Jython rules also allow me to implement techniques like dynamically generating the set of triggers based on item definitions, system context, rule parameters, and so on. I can also create abstract rule inheritance hierarchies, define mixins or use Python metaprogramming techniques to create and reuse complex rule logic relatively easily.

Even some relatively simple features like dynamic item name generation for the triggers could be challenging with a simple text format. I use item name generation quite a lot with the Jython rules. I’ll have a base item name and then a naming convention for several other items that are also used in triggers or actions.

The current JSR223 binding is very powerful and I think we are just starting to see the possible applications of it. For example, I have built a prototype of a standard OH1 binding in Jython. The prototype allows binding of items to JMX managed bean properties and operations. Although it is written in Jython, it can be used like any other Java binding, using the normal item binding configurations.

I’ve also implemented an ItemProvider in Jython, but I’m still experimenting with it. I’m thinking I might try defining some of my items using an RDF representation so I can provide arbitrary metadata for my configuration and the query it or do automated inferencing (for capabilities, autogenerated rules, etc.).

I’d actually like to be able to define sitemaps too in Jython. :slight_smile:

Ok, I looked at the code. Looks quiet useful already. So at the current time, we have two ways of adding rules:

  • prepackaged bundles with ESH-INF/automation/* inside it
  • adding them by the REST-API

I would go in following direction:

General Concept: Implement a JSR-RuleProvider which is able to do the following:

  • Look into a directory (call it scripts)
  • execute any script it finds in that directory based on file extension

The scripts can do what ever they want. They are ment to construct rules, but you could actually do something completly different like @steve1 did :wink:

The scripts would use a specially crafted API to allow nearly the functionality of RuleGSONParser ± whatever comes to mind:

  • add trigger types, condition types and actions
  • generate template objects
  • generate rule objects

The API I have in mind:

class RuleBuilder(RuleBuilderJava):
    def addTrigger(self, id, type, config): 
        # check if config is string, else json.dumps()
        RuleBuilderJava.addTrigger(id, type, jsonconfig)
        return self
    def addTrigger(self, id, TypeTrigger):
        self.addTrigger(id, TypeTrigger.getType(), TypeTrigger.getConfig())
    def addTrigger(self, id, method): pass # native python method
    def addTrigger(self, id, classExecutor): pass # python method in class form (class.... def call(): pass )
    def addCondition(self, id, type, config): return self # same here
# and so on
    def build(): return Rule

def method1(callparamers?):
     return #still need to look at that

builder = RuleBuilder()
builder.addTrigger(GenericEventTrigger("MyTrigger", "smarthome/items/MyTrigger/state", "ItemStateEvent")
builder.addTrigger("MyTrigger2","GenericEventTrigger", {
                        "eventSource":"MyTrigger",
                        "eventTopic":"smarthome/items/MyTrigger/state",
                        "eventTypes":"ItemStateEvent"
                    })
builder.addTrigger("MyTrigger3", method1)
RuleRegistry.addRule(builder.build())

# nearly the same thing for templates

I hope you get a feeling of what I have in mind.

Everything can be programmatically combined. For convenience a programming language dependent wrapper could be added, which allows nicer use of common Trigger and Condition types. But that would be optional as the Java part would be enough to support the general use case as the configuration is past as JSON-string.

Yes, roughly, and I think it makes sense.
You are right, currently rules are provided either through bundles (in ESH-INF/automation) or through the REST API. But the architecture allows implementing additional RuleProviders, which source the rules from somewhere else - this is your plan, if I get it right.

You might also want to spend some thoughts on a possibility to implement a TemplateProvider. The idea of the templates is to make it much easier to share rules with others by replacing concrete itemnames and configuration values by placeholders, so that other users can fill in their personal settings and easily reuse the rule. I think this can be very powerful, if we one day establish a shared repository for such tempates - it would be much more convenient than copying stuff from the wiki and manually editing it…

ok, I played a little around with the smarthome code :wink:

I have added the ability to define scripts in:

  • a scripts directory
  • in ESH-INF/automation/scripts

This is a small example of how it currently works, but I’m still playing around with the API:

for rule in RuleRegistry.all:
    print RuleRegistry.remove(rule.UID)

class MyAction(ActionHandler):
    def __init__(self, uid):
        self.actionType = ActionType(uid, [ConfigDescriptionParameter("test", ConfigDescriptionParameter.Type.INTEGER)], None)

    def execute(self, module, inputs):
        print module
        print inputs

        return None

HandlerRegistry.addType(MyAction("PythonAction"))

builder = RuleBuilder()
builder.UID = "test"
builder.name = "PythonRule"

builder.addTrigger(Trigger("trigger2", "GenericEventTrigger", {
                        "eventSource":"Sun_Elevation",
                        "eventTopic":"smarthome/items/MyTrigger/state",
                        "eventTypes":"ItemStateEvent"
                    }))
builder.addAction(ActionBuilder().setId("sampleAction").setTypeUID("PythonAction").addConfiguration("test", 123))

#builder.addAction(MyAction(None)) # <-- This is another way I want it to work ;) I will look into it, or maybe simply done by method injection on the python side...

RuleRegistry.add(builder.build())

So what you will be able to do in the future:

  • define module types (trigger, condition, action) by scripts (and bundle them)
  • define rules and use rule templates
  • define rule rule templates (not present right now)

here is the repo of the current state: https://github.com/smerschjohann/smarthome

There are still some bugs around, especially concerning bundle initialization, but I wanted to show a first concept before doing any more work. (If I go into the wrong direction)

@steve1: Could you give me some examples or remarks on the API. Any special needs or desires? :wink:

@Kai: I’ve changed and extended small aspects of the RuleEngine and the way scripts are loaded. I’m not sure if the change concerning ModuleHandlerFactory is the right way to do it. Any thoughts on this?

Should I open another community thread for this and/or an Issue/Pull Request on Github?