How to Setup Jython

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

That is really really useful info!
Im on the latest Milestone. I think 2.4M5. Which is almost a month old anyway. Okay so i will watch it and wait for the next Milestone to be released.

1 Like

I just made some test how to simplify logging. Under /lib/python/local I created empty __init__py and logging.py with this content:

from org.slf4j import Logger, LoggerFactory

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

def logInfo(message):
    log.info(message)

Now in my rule file I just need to import that module and use logInfo():

from openhab.rules import rule
from openhab.triggers import when
from local.logging import logInfo

@rule("TestRule")
@when("Item Test changed")
@when("0/15 * * * * ?")

def execute(event):
    if event is None:
        trigger = "Cron"
    else:
        trigger = event.itemName

    string = "Hello"
    logInfo("Message: \"{}\" triggered by: \"{}\"".format(string, trigger))

Output of script:

2018-10-25 10:36:00.003 [DEBUG] [omation.core.internal.RuleEngineImpl] - The trigger 'Cron-execute-9c74774fd82811e89ba3890da93d9b17' of rule '0502cc98-ddac-4e4f-bb7b-ca732fdca45f' is triggered.

2018-10-25 10:36:00.004 [INFO ] [ipse.smarthome.model.script.TestLogger] - Message: "Hello" triggered by: "Cron"

2018-10-25 10:36:00.004 [DEBUG] [omation.core.internal.RuleEngineImpl] - The rule '0502cc98-ddac-4e4f-bb7b-ca732fdca45f' is executed.

==> /var/log/openhab2/events.log <==

2018-10-25 10:36:03.616 [ome.event.ItemCommandEvent] - Item 'Test' received command ON

2018-10-25 10:36:03.618 [vent.ItemStateChangedEvent] - Test changed from OFF to ON

==> /var/log/openhab2/openhab.log <==

2018-10-25 10:36:03.619 [DEBUG] [omation.core.internal.RuleEngineImpl] - The trigger 'Item-Test-changed' of rule '0502cc98-ddac-4e4f-bb7b-ca732fdca45f' is triggered.

2018-10-25 10:36:03.620 [INFO ] [ipse.smarthome.model.script.TestLogger] - Message: "Hello" triggered by: "Test"

2018-10-25 10:36:03.620 [DEBUG] [omation.core.internal.RuleEngineImpl] - The rule '0502cc98-ddac-4e4f-bb7b-ca732fdca45f' is executed.

Hopefully I’m not inventing the wheel again :smile:
@5iver how would I go and make this import statement automatic so in my environment I would not need separate import in every rule file but just straight out use logInfo()?

1 Like

FYi: Updated to the newly released Milesone 5 Build. Seems like the double executions of the scripts is gone ! SO all good now

2 Likes

Few question about moving from the OH2.3 directory structure to the new structure.

  1. From repository, the contents of Core, ie the automation folder, goes in to the openHABconf folder, replacing the existing automation directory.
  2. Previous personal scripts that were in the automation/JSR232 folder now go into automation\lib\python\personal.
  3. For personal modules, ie not scripts, where should we put these?
  4. For modules that we want to share/add to the community, where do these go?
  5. For scripts that we want to share/add to the community, where do these go?

Mike

Hi, Michael (@mjcumming) Rather than duplicate the documentation @5iver generated, take a look at the Getting-Started page on github:

https://github.com/OH-Jython-Scripters/openhab2-jython/blob/master/Docs/Getting-Started.md#file-locations

Hi Scott, its after reading the instructions that I am asking… kinda highlights the difficulties writing documentation - what seems obvious/intuitive to one is confusion to another :slight_smile: