How to Setup Jython

Try removing putting the startup delay script in /automation/jsr223, and remove the components directory. Unless you are specifically using any of those, there is no reason to load them.

@5iver, I moved 000_0_delay_startup.py up one level from automation/jsr232/000_components/ to automation/jsr232/. I also cleared OH2’s tmp/ and cache/ directories, which I had not done for my previous tests. Startup no longer hangs and, as is normal, the DSL rules’ globals are eventually initialized after several NPEs during DSL rule startup. I suspect clearing tmp/ and cache/ was probably needed during my earlier testing.

Thanks all for your help!

1 Like

@H102, I’m running openHAB 2.4.0~20181012154014-1 (Build #1389) on an RPi 3B.

Here is quick comparison between 2 rules one in python, the other DSL. NOTE: I am new to both python and DSL so my approach and coding style leave something to be desired :slight_smile:


"""

Resonds to changes in the occupancy state of an area

Manages Occupancy/Vacancy actions

Propagates state to parent and/or child areas 


"""
  

@rule ("gOccupancy State Changed")

@when ("Member of gOccupancyState changed")

def gOccupancyStateChanged (event):

    if event.oldItemState == UnDefType.NULL: # avoid events from persistance
        return

    if "gOccupancyState" not in ir.getItem(event.itemName).getGroupNames():        # hack for a bug in JSR
        return

    triggeringItem = itemRegistry.getItem(event.itemName)
    #log.info("gOccupancyState Changed ->>> "+triggeringItem.name+ "          state "+ str(triggeringItem.state))

    gArea = itemRegistry.getItem ("gArea")

    #areaItem = list(group for group in ir.getItem("gArea").members if ir.getItem(event.itemName) in group.members)[0] 
    areaItem = list(group for group in ir.getItem("gArea").members if group.name in ir.getItem(event.itemName).getGroupNames())[0]

    # find the occupancy locking item
    occupancylocking = itemRegistry.getItem("OL_"+areaItem.name[1:])
    #log.info("Occupancy Item Event"+"    Occupancy Locking:  "+occupancylocking.name)

    # check if the area is locked before doing any actions
    if occupancylocking.state != OnOffType.ON:
        # Process any actions based ON the tags for that area for change from OnOffType.OFF to OnOffType.ON ie vacant to occupited
        if triggeringItem.state == OnOffType.ON:
            # perform any actions
            for action in areaItem.getTags():
                np = action.split (":")
                if np[0] == "OccupiedAction":

                    if np[1] == "LightsOn": 
                        for item in areaItem.members:
                            for tag in item.getTags():
                                if tag=="Lighting":
                                    events.sendCommand (item,OnOffType.ON)

                    elif np [1] == "LightsOnIfDark":
                        if ir.getItem ("VT_DayLight_Switch").state == OnOffType.OFF:
                            for item in areaItem.members:
                                for tag in item.getTags():
                                    if tag=="Lighting":
                                        events.sendCommand (item,OnOffType.ON)

                    elif np[1] == "SceneOn": 
                        for item in areaItem.members:
                            for tag in item.getTags():
                                if tag=="AreaAll":
                                    events.sendCommand (item,OnOffType.ON)

                    elif np [1] == "SceneOnIfDark":
                        if ir.getItem ("VT_DayLight_Switch").state == OnOffType.OFF:
                            for item in areaItem.members:
                                for tag in item.getTags():
                                    if tag=="AreaAll":
                                        events.sendCommand (item,OnOffType.ON)

        # OFF = vacant, do any vacant actions
        if triggeringItem.state == OnOffType.OFF:
            # Process any actions based OnOffType.ON the tags for that area
            for action in areaItem.getTags():
                np = action.split (":")
                if np[0] == "VacantAction":
                    log.info("Vacant action "+action)

                    if np[1] == "LightsOff": 
                        for item in areaItem.members:
                            for tag in item.getTags():
                                if tag=="Lighting":
                                    events.sendCommand (item,OnOffType.OFF)

                    elif np [1] == "SceneOff": 
                        for item in areaItem.members:
                            for tag in item.getTags():
                                if tag=="AreaAll":
                                    events.sendCommand (item,OnOffType.OFF)

                    elif np [1] == "ExhaustFansOff": 
                        for item in areaItem.members:
                            for tag in item.getTags():
                                if tag=="ExhaustFan":
                                    events.sendCommand (item,OnOffType.OFF)

                    elif np [1] == "AVOff": 
                        for item in areaItem.members:
                            if item in ir.getItem("gAVPower").members:
                                events.sendCommand (item,OnOffType.OFF)
 
        # if area is vacant, force child areas to vacant
        if triggeringItem.state == OnOffType.OFF:
            for groupName in areaItem.getGroupNames():
                if groupName in gArea.getGroupNames():
                    childArea = ir.getItem (groupName)
                    events.postUpdate (childArea.OnOffType.OFF)

Original rule in DSL

/*

Resonds to changes in the occupancy state of an area

Manages Occupancy/Vacancy actions

Propagates state to parent and/or child areas 


*/
  
import org.eclipse.smarthome.model.script.ScriptServiceUtil



rule "React on OccupancyState changed"
when
    Member of gOccupancyState changed    // your condition here
then
    if(previousState == NULL) return; // we don't want to process events from restoreOnStartup

    logInfo("Occupancy State Changed","STARTED Name:  "+triggeringItem.name+"     State:    "+triggeringItem.state)

    // get the area that the triggering item belongs too
    val GroupItem area = ScriptServiceUtil.getItemRegistry.getItem("g"+triggeringItem.name.substring (3))
    logInfo("Occupancy State Changed","Area Group:  "+area)     

    // find the occupancy locking item
    val occupancylocking = ScriptServiceUtil.getItemRegistry.getItem("OL_"+triggeringItem.name.substring (3))
    //val occupancylocking = area.members.findFirst [ i | (i.name.indexOf ("OL_") == 0 ) ]
    logInfo("Occupancy State Changed","Occupancy Locking:  "+occupancylocking)

    if (occupancylocking.state != ON) {
        // Process any actions based on the tags for that area for change from OFF to ON ie vacant to occupited
        if (triggeringItem.state == ON) {
            // perform any actions
            triggeringItem.getTags.forEach [ i |
                val np = i.toString.split (":")
                if (np.get (0) == "OccupiedAction")
                    switch (np.get (1)) {
                        case "LightsOn": {
                            area.members.forEach [ i |
                                i.getTags.forEach [ t | 
                                    if (t=="Lighting") {
                                        i.sendCommand (ON)
                                    }
                                ]
                            ]
                        }

                        case "SceneOn": {
                            area.members.forEach [ i |
                                i.getTags.forEach [ t | 
                                    if (t=="AreaAll") {
                                        i.sendCommand (ON)
                                    }
                                ]
                            ]
                        }

                        case "SceneOnIfDark": {
                            if (VT_DayLight_Switch.state == OFF) {
                                area.members.forEach [ i |
                                    i.getTags.forEach [ t | 
                                        if (t=="AreaAll") {
                                            i.sendCommand (ON)
                                        }
                                    ]
                                ]
                            }
                        }
                    }
            ]
        }

        // OFF = vacant, do any vacant actions
        if (triggeringItem.state == OFF) {
            // Process any actions based on the tags for that area
            triggeringItem.getTags.forEach [ i |
                val np = i.toString.split (":")
                if (np.get (0) == "VacantAction")
                    switch (np.get (1)) {
                        case "LightsOff": {
                            area.members.forEach [ i |
                                i.getTags.forEach [ t | 
                                    if (t=="Lighting") {
                                        i.sendCommand (OFF)
                                    }
                                ]
                            ]
                        }

                        case "SceneOff": {
                            area.members.forEach [ i |
                                i.getTags.forEach [ t | 
                                    if (t=="AreaAll") {
                                        i.sendCommand (OFF)
                                    }
                                ]
                            ]
                        }
                        
                        case "ExhaustFansOff": {
                            area.members.forEach [ i |
                                i.getTags.forEach [ t | 
                                    if (t=="Exhaust Fan") {
                                        i.sendCommand (OFF)
                                    }
                                ]
                            ]
                        }

                        case "AVOff": {
                            area.members.forEach [ i |
                                if (gAVPower.members.findFirst [ a | a.name == i.name] !== null)
                                    i.sendCommand (OFF)
                            ]
                        }

                    }
            ]
        }   
    }
 
    //update parent area group
    val GroupItem parentarea = gArea.members.findFirst [ a | a.name == area.getGroupNames.findFirst [ i | a.name == i ] ]
    logInfo("Occupancy State Changed","Parent area:  "+parentarea)     
    
    if (parentarea !== null) {
        val poccupancystate = ScriptServiceUtil.getItemRegistry.getItem("OS_"+parentarea.name.substring (1))
        //val GenericItem poccupancystate = parentarea.members.findFirst [ i | (i.name.indexOf ("OS_") == 0 ) ]

        // notify parent area that child area is now occupied
        if (triggeringItem.state == ON)
            poccupancystate.postUpdate (ON)
    }     


    // if area is vacant, force child areas to vacant
    if (triggeringItem.state == OFF) {
        logInfo("Occupancy State Changed"," Area Vacant, updating child areas")     

        area.members.forEach [ i | // iterate all members of the area
            //logInfo("Occupancy Status Changed","parent area members  "+i)     
            val childarea = gArea.members.findFirst [ a | a.name == i.name ] as GroupItem // find if the member is a member of gArea

            if (childarea !== null) { // we have a child area
                val childoccupancystate = ScriptServiceUtil.getItemRegistry.getItem("OS_"+childarea.name.substring (1))
                childoccupancystate.postUpdate (OFF)
            }
        ]
    }

    logInfo("Occupancy State Changed","FINISHED Name:  "+triggeringItem.name+"     State:    "+triggeringItem.state)
end  
1 Like

Hi,

when using jython. Are we actually talking Python 3 or Python 2?
Also if i want to use a Library lets say requests. Can i just import it in the rules file and jython will make it available?

Thanks

We’re talkin’ Jython 2.7! You could try newer, but the rule engine may need some tweaking. There’s a short comparison at the bottom of this page… https://wiki.python.org/jython/WhyJython.

https://wiki.python.org/jython/JythonFaq/JythonModules#What_parts_of_the_Python_library_are_supported.3F

This one is a little dated… http://www.jython.org/docs/library/indexprogress.html.

So, yes… you can import Python 2.7 libraries in your scripts and modules. The best part is that you can also import Java libraries, like I did here with org.eclipse.smarthome.core.items.Metadata.

import requests
from openhab.rules import rule
from openhab.triggers import when
from org.slf4j import Logger, LoggerFactory


log = LoggerFactory.getLogger("org.eclipse.smarthome.model.script.Rules")
url = "http://172.16.0.15/api/GBL1Hvu8d4kbK7G1iCb3BtsRQiPXFabP9RpKnjng/"


@rule("Hallway Light ON")
@when("Hallway_Motion changed to ON")

def testFunction(event):
    
    payload = "{\n\t\"on\": true\n}\n"
    requests.request("PUT", url +"groups/10/action", data=payload)
    
    log.info("JSR223: Hallway Light ON")
    

Doesn’t load.

2018-10-18 13:51:03.685 [ERROR] [ipt.internal.ScriptEngineManagerImpl] - Error during evaluation of script 'file:/etc/openhab2/automation/jsr223/light.py': ImportError: No module named requests in <script> at line number 1

So with Python i would now jsut pip install it. However how do it do this on jython. Would i just add this to one of the startup componentes?

Scott, where do you put 2.7 modules so that import can find them?

@Tomibeck and @mjcumming,

If I’m not mistaken, python modules should be placed under automation/lib/python. I base my educated guess on the fact that that is where openhab/rules.py is found (the line from openhab.rules import rule in your example rule, @Tomibeck.)

Confirmed per the documentation found here.

2 Likes

Do i really have to manually copy in all the dependecies? It first complained that there was no urlib3, then chardet, then certifi. and then it complained there is no ordered_dict.py which i cant find on Github

Why not append to the python.path environment variable defined in /etc/default/openhab2 in the definition of EXTRA_JAVA_OPTS? Something like this perhaps:

... -Dpython.path=/etc/openhab2/automation/lib/python:/usr/lib/python2.7

I’m neither a long time python nor jython coder, but the documentation does state that this will work.

This will work, although my python libraries are under /usr/lib/python2.7/site-packages. However, this will not work for @Tomibeck. Attempting to use requests will return this…

TypeError: Error when calling the metaclass bases
    function() argument 1 must be code, not str in <script> at line number 14

A full stack trace shows…

2018-10-18 12:47:44.301 [ERROR] [org.eclipse.smarthome.automation.scriptException] - JSR223: test: Exception: [Error when calling the metaclass bases
    function() argument 1 must be code, not str]: [Traceback (most recent call last):
  File "<script>", line 15, in <module>
  File "/usr/lib/python2.7/site-packages/requests/__init__.py", line 93, in <module>
    from .api import request, get, head, post, patch, put, delete, options
  File "/usr/lib/python2.7/site-packages/requests/api.py", line 13, in <module>
    from . import sessions
  File "/usr/lib/python2.7/site-packages/requests/sessions.py", line 28, in <module>
    from .adapters import HTTPAdapter
  File "/usr/lib/python2.7/site-packages/requests/adapters.py", line 41, in <module>
    from urllib3.contrib.socks import SOCKSProxyManager
  File "/usr/lib/python2.7/site-packages/requests/adapters.py", line 41, in <module>
    from urllib3.contrib.socks import SOCKSProxyManager
  File "/usr/lib/python2.7/site-packages/urllib3/contrib/socks.py", line 27, in <module>
    import socks
  File "/usr/lib/python2.7/site-packages/urllib3/contrib/socks.py", line 27, in <module>
    import socks
  File "/usr/lib/python2.7/site-packages/socks.py", line 267, in <module>
    class _BaseSocket(socket.socket):
TypeError: Error when calling the metaclass bases
    function() argument 1 must be code, not str
]

Jython cannot use CPython extension modules written in C, so if you see a .pyc file, chances are it will not work (there is a socks.pyc). An alternative would be to use a Java library, or you could also use executeCommandLine and run anything in Jython that you can in a shell (even Python3), just as you could in the Rules DSL…

from org.slf4j import Logger, LoggerFactory
from org.eclipse.smarthome.model.script.actions.Exec import executeCommandLine
log = LoggerFactory.getLogger("org.eclipse.smarthome.model.script.Rules")

result = executeCommandLine("/bin/sh@@-c@@/usr/bin/python -c \"import requests; print(requests.get('http://localhost:8080').status_code)\"",5000)
log.info("JSR223: test requests [{}]".format(result))

This returns…

2018-10-18 14:22:20.269 [INFO ] [org.eclipse.smarthome.model.script.Rules] - JSR223: test requests [200]

But don’t forget we are in OH! By far the easiest solution for you is to simply use one of the OH HTTP Actions.

from openhab.rules import rule
from openhab.triggers import when
from org.slf4j import Logger, LoggerFactory
from org.eclipse.smarthome.model.script.actions.HTTP import sendHttpPutRequest

log = LoggerFactory.getLogger("org.eclipse.smarthome.model.script.Rules")
url = "http://172.16.0.15/api/GBL1Hvu8d4kbK7G1iCb3BtsRQiPXFabP9RpKnjng/"

@rule("Hallway Light ON")
@when("Hallway_Motion changed to ON")
def testFunction(event):
    payload = "{\n\t\"on\": true\n}\n"
    sendHttpPutRequest(url, "text/html", payload)
    log.info("JSR223: Hallway Light ON")

Ha Thanks all for the help my first light Rules is running:

from openhab.rules import rule
from openhab.triggers import when
from org.slf4j import Logger, LoggerFactory
from org.eclipse.smarthome.model.script.actions.HTTP import sendHttpPutRequest
import openhab
log = LoggerFactory.getLogger("org.eclipse.smarthome.model.script.Rules")
url = "http://172.16.0.15/api/GBL1Hvu8d4kbK7G1iCb3BtsRQiPXFabP9RpKnjng/groups/10/action"

@rule("Set Scene")
@when("Item TimeofDay changed")

def setscene(event):
    
    global payload
    Now = str(items.TimeofDay)
    log.info("Time of Day is:"+ str(Now))
    
    if "Morning" in Now:
        payload = '{"scene":"Roet3VxODPYz4eG"}'
        log.info("JSR223: Morning light set" + payload)
    
    elif "Day" in Now:
        payload = '{"scene":"6ln5CRxZMhip1eo"}'
        log.info("JSR223: Day light set" + payload)
    
    elif "Evening" in Now:
        payload = '{"scene":"8I4bGQ6kMYV48MH"}'
        log.info("JSR223: Evening light set" + payload)
   
    elif "Night" in Now:
        payload = '{"scene":"Off"}'
        log.info("JSR223: Night light set" + payload)

    log.info("done")


@rule("Hallway Light ON")
@when("Item Hallway_Motion changed to ON")
def hallwaylighton(event):
    
    sendHttpPutRequest(url, "application/json", payload)
    log.info("JSR223: Hallway Light ON")


@rule("Hallway Light OFF")
@when("Item Hallway_Motion changed to OFF")
def hallwaylightoff(event):
    
    payload = '{"on": false}'
    sendHttpPutRequest(url, "application/json", payload)
    log.info("JSR223: Hallway Light OFF")

As im still leraning Python any suggestions for improving this rule?

For waht ever reason i couldnt use the == operator in the if statments. It never jumped into the if rule even when i set the item manually to one of the Statements. Is it also normal that i need to cast the item into a str ?

Tomibeck

Excellent!

Have you looked through here? You’re using the openhab package, which I never really used, so you may notice a difference in the examples. Just preference. But you were probably having trouble with type conversions.

Compare yours to this and see if what I’ve done makes sense. I used a dictionary to hold/return the payloads, and consolidated the other two rules into one. I also used format (I really like format!) in your logging. Format can convert pretty much anything into a string, and is the best way to concatenate strings. I’m not suggesting to change what you have (if it works, it works!), but hopefully what I’m showing will help you and others with your rules.

from openhab.rules import rule
from openhab.triggers import when
from org.slf4j import Logger, LoggerFactory
from org.eclipse.smarthome.model.script.actions.HTTP import sendHttpPutRequest
import openhab
log = LoggerFactory.getLogger("org.eclipse.smarthome.model.script.Rules")
url = "http://172.16.0.15/api/GBL1Hvu8d4kbK7G1iCb3BtsRQiPXFabP9RpKnjng/groups/10/action"

def setPayload(state):
    global payload
    payloadMap = { "Morning": '{"scene":"Roet3VxODPYz4eG"}',
                   "Day"    : '{"scene":"6ln5CRxZMhip1eo"}',
                   "Evening": '{"scene":"8I4bGQ6kMYV48MH"}',
                   "Night"  : '{"scene":"Off"}'
    }
    payload = payloadMap[str(state)]

setPayload(items["TimeofDay"]

@rule("Set Scene")
@when("Item TimeofDay changed")
def setscene(event):
    log.info("Time of Day is [{}]".format(event.itemState))
    setPayload(event.itemState)
    log.info("JSR223: {} light set [{}]".format(event.itemState, payload))d))

@rule("Hallway Light changed")
@when("Item Hallway_Motion changed")
def hallwaylight(event):
    if event.itemState == OnOffType.ON:
        sendHttpPutRequest(url, "application/json", payload)
    elif event.itemState == OnOffType.OFF:
        payload = '{"on": false}'
        sendHttpPutRequest(url, "application/json", payload)
    log.info("JSR223: Hallway Light [{}]".format(event.itemState))

EDIT:
The readme speaks of some magic about the openhab package…

It can also be used as a module for registering global variables that will outlive script reloads.

… that I do not believe it can do. Have you successfully triggered your motion sensor after a script reload and before TimeofDay changing, and not gotten a NameError for payload? I would think you would need to define payload with a value when the script loads. I’ve update my post to include this.

1 Like

Story of success here also! Installed everything per readme and works like a charm. Tested the telegram action:

from org.slf4j import Logger, LoggerFactory
from openhab.rules import rule
from openhab.triggers import when
from openhab.actions import Telegram

scriptExtension.importPreset("RuleSupport")
scriptExtension.importPreset("RuleSimple")

log = LoggerFactory.getLogger("org.eclipse.smarthome.model.script.MyTestRule")

@rule("Test rule")

@when("Item Test received command ON")

def testFunction(event):
    log.info("Test", "The test rule has been executed!")
    Telegram.sendTelegram("MyBot", "Test")

Thanks @5iver for your work. It was quite simple and straightforward to get this up and running.

2 Likes

FYI, when using the decorators, you do not need the presets. They are loaded in the modules.

+1 on finding the openhab.actions module! I’ve updated the Actions example in the readme to include this option.

1 Like

It seems to me that my rules are firing twice. Is that a known issue?

What version of OH? There was a very recent fix that I made to ItemStateChangedTrigger that could be affecting you. The fix is in the latest snapshot.

Scott, would it make sense to (is or is it possible) to use a decorator to have logging the same between the Rules DSL and Python? I think it would make switching easier.

Also maybe keeping the triggeringItem schema would be helpful for new users?

There is a openhab.log module that I haven’t really looked into, but I plan to. But a simple solution for the way I do logging, is to just do this.

from org.slf4j import Logger, LoggerFactory
logDebug = LoggerFactory.getLogger("org.eclipse.smarthome.model.script.Rules").debug

logDebug("JSR223: Test")

That one is trickier. It would be a breaking change if done in ESH, that would probably not be accepted. I could also change it in the rules module, but it is still a breaking change that others may have an issue with. The other objects being returned are also nicely consistent, and putting in previousState and receivedCommand could make it confusing. IMO, this is just a change in the new rule engine that people will need to learn and accept. I’ll add the following info to the docs (I think it is complete…):

[Table removed so I don’t have to update it in two places. Follow the link above.]

Strange timing… I had just updated the readme this morning with this sort of info… but nowhere near as detailed as this!

1 Like