How to Setup Jython

I had the same issue on RPI and solved with Scott’s script

import time

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

log.info("JSR223: Startup: Checking for initialized context")

while True:
    try:
        scriptExtension.importPreset("RuleSupport")
        if automationManager != None:
            log.info("JSR223: Startup: context initialized... waiting 30s before allowing files to load")
            break;
    except:
        pass
    log.info("JSR223: Startup: Context not initialized yet... waiting 10s before checking again")
    time.sleep(10)

time.sleep(30)
log.info("JSR223: Startup: Complete")

Place this in the 000_components directory and name it 000_startup.py then rename the other components to something higher than 000_othercomponets. You can see a description on post #94 of how I renamed everything. Restart OH and the errors should go away.

@5iver, thanks for the quick reply – egg on my face, all I had to do was search this topic (:astonished:)!

I should add this script to the readme…

@Scott testing with 000_startup.py placed in /automation/jsr223/ does not clear the errors.

Placing this script in /automation/jsr223/000_components and naming the other components as something higher than 000 does clear the errors.

Note I didn’t test without renaming the other components so that may not be required.

1 Like

@scottk if placing 000_startup.py in /automation/jsr223/ does not clear the errors, didn’t for me, please test by placing the 000_startup.py in /automation/jsr223/000_components directory and without changing the names of other components. If this works let me know, if not then changing the other components names does work. See picture in post #94 for example.

Thanks

Thank you for the recommendation, @H102. I named the script 000_0_delay_startup.py and placed it into /etc/openhab2/automation/jsr223/000_components/ (actually, /etc/openhab2/automation is symlinked to /usr/share/openhab2/automation.)

During my first OH2 restart for testing, startup appears to be hung some time after the message “delaying ‘Initialize key item states at system startup’ rule for 30 seconds” shows up in the log.

[Updates:]
I’m still troubleshooting… My initial assessment is that the hang was probably not related to JSR223 since I have some logging in my DSL rules that indicates rule globals were not correctly initialized.

I’m now not so sure that my initial assessment was correct, OH2 startup is stalled at the same ponit with the same two uninitialized DSL rule globals.

What version of OH are you using?

While testing I used a spare RPI with a fresh install running 2.4 snapshot build #1381. I also had no DSL rules in the rules file.

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