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.
@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.
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.
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.
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
"""
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
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?
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.
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?
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.)
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:
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 ?