I’m currently using the latest stable version of OpenHAB and I’m running JSR223 python scripting for my rules. I started using the transitions library to develop some state machine driven rules and for the most part it’s going pretty well.
I ran into an issue today that I can not get past no matter how hard I try. I’ve created a class in Python using the transitions library:
from transitions import State, Machine
from org.joda.time import DateTime
from core.log import logging, LOG_PREFIX
from core.jsr223 import scope
from core.utils import post_update_if_different
from core.jsr223.scope import OnOffType, UnDefType
from core.actions import ScriptExecution
import time
from personal.SceneHelper import set_scene_ext
states = [
State(name='unoccupied'),
State(name='occupied'),
State(name='occupied_timed'),
State(name='occupied_latched'),
State(name='warn')
]
transitions = [
{'trigger':'motion', 'source':'unoccupied', 'dest':'occupied'},
{'trigger':'no_motion', 'source':'occupied', 'dest':'occupied_timed'},
{'trigger':'motion', 'source':'occupied_timed', 'dest':'occupied'},
{'trigger':'no_motion', 'source':'occupied_timed', 'dest':'warn'},
{'trigger':'motion', 'source':'warn', 'dest':'occupied'},
{'trigger':'no_motion', 'source':'warn', 'dest':'unoccupied'},
{'trigger':'latch', 'source':['unoccupied','occupied','occupied_timed','warn'], 'dest':'occupied_latched'},
{'trigger':'unlatch', 'source':'occupied_latched', 'dest':'occupied_timed'}
]
def timer_expired(self):
self.logger.debug("Timer Expired")
#self.motionTimer.cancel()
#self.motionTimer = None
self.no_motion()
motionTimers = {}
class OccupancySM(Machine):
def __init__(self, room):
self.sceneRoom = room
self.logger = logging.getLogger("{}.{}_Occupancy".format(LOG_PREFIX, self.sceneRoom))
self.motionTimer = None
try:
self.logger.debug("Getting occupancy state item: {}_Occupancy_State".format(self.sceneRoom))
self.stateItem = scope.itemRegistry.getItem("{}_Occupancy_State".format(self.sceneRoom))
self.logger.debug("Retrieved state item: {}".format(self.stateItem))
except:
self.logger.debug("Failed to retrieve occupancy state item.")
self.stateItem = None
Machine.__init__(self, name="{}_Occupancy_Machine".format(self.sceneRoom),states=states, initial='unoccupied', transitions=transitions)
def on_enter_unoccupied(self):
self.logger.debug("{} entering unoccupied state. Turning all items off.".format(self.sceneRoom))
post_update_if_different("Office_Occupancy_State", "Unoccupied", False)
roomLights = [item for item in scope.itemRegistry.getItem("g{}".format(self.sceneRoom)).members if "gLights" in item.groupNames]
self.logger.debug("Retrieved following lights: {}".format(str(roomLights)[1:-1]))
for light in roomLights:
self.logger.debug("Turning off {}".format(str(light)))
post_update_if_different(light, OnOffType.OFF, True)
def on_enter_occupied(self):
self.logger.debug("{} entering occupied state. Triggering occupancy items.".format(self.sceneRoom))
post_update_if_different("Office_Occupancy_State", "Occupied")
set_scene_ext(self.sceneRoom, "Occupancy", OnOffType.ON, self.logger)
if self.sceneRoom in motionTimers:
self.logger.debug("Timer running, cancelling...")
motionTimers[self.sceneRoom].cancel()
del motionTimers[self.sceneRoom]
def on_enter_occupied_timed(self):
self.logger.debug("{} entering timed occupied state. Starting timer.".format(self.sceneRoom))
post_update_if_different("Office_Occupancy_State", "Occupied - Timed")
itemRoomGroup = scope.itemRegistry.getItem("g{}".format(self.sceneRoom))
itemRoom = self.sceneRoom
# Gather timeout information - seconds
timeoutSecondsItems = filter(lambda item:item.name == "{}_Occupancy_Timeout_Seconds".format(itemRoom), itemRoomGroup.members)
if len(timeoutSecondsItems) > 0:
timeoutSecondsItem = timeoutSecondsItems[0]
self.logger.debug("Occupancy Timeout Seconds item {} found in state {}".format(timeoutSecondsItem.name, timeoutSecondsItem.state))
if timeoutSecondsItem.state == UnDefType.NULL:
self.logger.debug("Occupancy Timeout Seconds item in NULL state - using default setting")
timeoutSecondsItem = scope.itemRegistry.getItem("DefaultSensorTimeout_Seconds")
else:
timeoutSecondsItem = scope.itemRegistry.getItem("DefaultSensorTimeout_Seconds")
self.logger.debug("No Occupany Timeout Seconds item found - using default item {} in state {}".format(timeoutSecondsItem.name, timeoutSecondsItem.state))
# Gather timeout information - minutes
timeoutMinutesItems = filter(lambda item:item.name == "{}_Occupancy_Timeout_Minutes".format(itemRoom), itemRoomGroup.members)
if len(timeoutMinutesItems) > 0:
timeoutMinutesItem = timeoutMinutesItems[0]
self.logger.debug("Occupancy Timeout Minutes item {} found in state {}".format(timeoutMinutesItem.name, timeoutMinutesItem.state))
if timeoutMinutesItem.state == UnDefType.NULL:
self.logger.debug("Occupancy Timeout Minutes item in NULL state - using default setting")
timeoutMinutesItem = scope.itemRegistry.getItem("DefaultSensorTimeout_Minutes")
else:
timeoutMinutesItem = scope.itemRegistry.getItem("DefaultSensorTimeout_Minutes")
self.logger.debug("No Occupany Timeout Minutes item found - using default item {} in state {}".format(timeoutMinutesItem.name, timeoutMinutesItem.state))
#If one of the items is null just use a hard coded timeout
self.logger.debug("Got items {item1} in state {state1} and {item2} in state {state2}".format(item1=timeoutSecondsItem, state1 = timeoutSecondsItem.state, item2=timeoutMinutesItem, state2=timeoutMinutesItem.state))
if timeoutSecondsItem.state == UnDefType.NULL or timeoutMinutesItem.state == UnDefType.NULL:
timeoutSeconds=0
timeoutMinutes=15
else:
timeoutSeconds=timeoutSecondsItem.state.intValue()
timeoutMinutes=timeoutMinutesItem.state.intValue()
# Gather warn timeout information - seconds
warnTimeoutSecondsItems = filter(lambda item:item.name == "{}_Occupancy_WarnTime_Seconds".format(itemRoom), itemRoomGroup.members)
if len(warnTimeoutSecondsItems) > 0:
warnTimeoutSecondsItem = warnTimeoutSecondsItems[0]
self.logger.debug("Occupancy Warn Time Seconds item {} found in state {}".format(warnTimeoutSecondsItem.name, warnTimeoutSecondsItem.state))
if warnTimeoutSecondsItem.state == UnDefType.NULL:
self.logger.debug("Occupancy Warn Time Seconds item in NULL state - using default setting")
warnTimeoutSecondsItem = scope.itemRegistry.getItem("DefaultWarnTimeout_Seconds")
else:
warnTimeoutSecondsItem = scope.itemRegistry.getItem("DefaultWarnTimeout_Seconds")
self.logger.debug("No Occupany Warn Time Seconds item found - using default item {} in state {}".format(warnTimeoutSecondsItem.name, warnTimeoutSecondsItem.state))
# Gather warn timeout information - minutes
warnTimeoutMinutesItems = filter(lambda item:item.name == "{}_Occupancy_WarnTime_Minutes".format(itemRoom), itemRoomGroup.members)
if len(warnTimeoutMinutesItems) > 0:
warnTimeoutMinutesItem = warnTimeoutMinutesItems[0]
self.logger.debug("Occupancy Warn Time Minutes item {} found in state {}".format(warnTimeoutMinutesItem.name, warnTimeoutMinutesItem.state))
if warnTimeoutMinutesItem.state == UnDefType.NULL:
self.logger.debug("Occupancy Warn Timeout Minutes item in NULL state - using default setting")
warnTimeoutMinutesItem = scope.itemRegistry.getItem("DefaultWarnTimeout_Minutes")
else:
warnTimeoutMinutesItem = scope.itemRegistry.getItem("DefaultWarnTimeout_Minutes")
self.logger.debug("No Occupany Warn Timeout Minutes item found - using default item {} in state {}".format(warnTimeoutMinutesItem.name, warnTimeoutMinutesItem.state))
#If one of the items is null just use a hard coded timeout
self.logger.debug("Got items {item1} in state {state1} and {item2} in state {state2}".format(item1=warnTimeoutSecondsItem, state1 = warnTimeoutSecondsItem.state, item2=warnTimeoutMinutesItem, state2=warnTimeoutMinutesItem.state))
if warnTimeoutSecondsItem.state == UnDefType.NULL or warnTimeoutMinutesItem.state == UnDefType.NULL:
warnTimeoutSeconds=30
warnTimeoutMinutes=0
else:
warnTimeoutSeconds=warnTimeoutSecondsItem.state.intValue()
warnTimeoutMinutes=warnTimeoutMinutesItem.state.intValue()
# Calculate total timeout in seconds
self.totalTimeoutSeconds=timeoutSeconds + (60 * timeoutMinutes)
self.totalWarnTimeoutSeconds=warnTimeoutSeconds + (60 * warnTimeoutMinutes)
if (self.totalWarnTimeoutSeconds < self.totalTimeoutSeconds):
self.logger.debug("Sensor off: timeout in {timeout} seconds with warning at {warning} seconds to go".format(timeout=self.totalTimeoutSeconds, warning=self.totalWarnTimeoutSeconds))
self.prewarnTimeout = self.totalTimeoutSeconds - self.totalWarnTimeoutSeconds
self.logger.debug("Prewarn timeout: {}".format(str(self.prewarnTimeout)))
if self.sceneRoom in motionTimers:
motionTimers[self.sceneRoom].reschedule(DateTime.now().plusSeconds(self.prewarnTimeout))
self.logger.debug("Reschedule Timer")
else:
motionTimers[self.sceneRoom] = ScriptExecution.createTimer(DateTime.now().plusSeconds(self.prewarnTimeout), lambda: self.no_motion())
#self.no_motion()
else:
self.logger.debug("Warn timeout >= total timeout, skipping pre-warn")
self.no_motion()
def on_exit_occupied_timed(self):
self.logger.debug("Exiting occupied-timed and deleting timer")
if self.sceneRoom in motionTimers:
self.logger.debug("Timer found, canceling")
#motionTimers[self.sceneRoom].cancel()
self.logger.debug("Deleting timer")
del motionTimers[self.sceneRoom]
def on_enter_warn(self):
self.logger.debug("{} entering warning state. Starting timer and flashing lights.".format(self.sceneRoom))
post_update_if_different("Office_Occupancy_State", "Occupied - Warning")
roomGroupName = "g{}".format(self.sceneRoom)
self.logger.debug("Getting lights in {}".format(roomGroupName))
lightsToFlash = [item for item in scope.itemRegistry.getItem("g{}".format(self.sceneRoom)).allMembers if item in scope.itemRegistry.getItem("gLights").allMembers and not item in scope.itemRegistry.getItem("gLights").members]
self.logger.debug("Lights to flash: {}".format(str(lightsToFlash)[1:-1]))
lightsStates = {}
for light in lightsToFlash:
self.logger.debug("Storing light {}".format(str(light)))
lightsStates[light.name] = light.state
self.logger.debug("Turning light off")
post_update_if_different(light, OnOffType.OFF, True)
time.sleep(1)
for light in lightsToFlash:
self.logger.debug("Restoring {}".format(str(light)))
post_update_if_different(light, lightsStates[light.name], True)
if self.sceneRoom in motionTimers:
motionTimers[self.sceneRoom].reschedule(DateTime.now().plusSeconds(self.totalWarnTimeoutSeconds))
else:
motionTimers[self.sceneRoom] = ScriptExecution.createTimer(DateTime.now().plusSeconds(self.totalWarnTimeoutSeconds), lambda: self.no_motion())
def on_exit_warn(self):
self.logger.debug("Exiting Warn and deleting timer")
if self.sceneRoom in motionTimers:
self.logger.debug("Timer found, canceling")
#motionTimers[self.sceneRoom].cancel()
self.logger.debug("Deleting timer")
del motionTimers[self.sceneRoom]
I can make the transitions work, however when I added the timer to transition from the occupied_timed state to the warn state I get this error:
14:47:47.183 [DEBUG] [jsr223.jython.OccupancyTestRule ] - TestSwitch received command OFF
14:47:47.185 [DEBUG] [jsr223.jython.Office_Occupancy ] - Office entering timed occupied state. Starting timer.
14:47:47.187 [DEBUG] [jsr223.jython.core.utils ] - New postUpdate value for [Office_Occupancy_State] is [Occupied - Timed]
14:47:47.187 [INFO ] [smarthome.event.ItemStateChangedEvent] - Office_Occupancy_State changed from Occupied to Occupied - Timed
14:47:47.188 [DEBUG] [jsr223.jython.Office_Occupancy ] - Occupancy Timeout Seconds item Office_Occupancy_Timeout_Seconds found in state 10
14:47:47.189 [DEBUG] [jsr223.jython.Office_Occupancy ] - Occupancy Timeout Minutes item Office_Occupancy_Timeout_Minutes found in state 0.0
14:47:47.189 [DEBUG] [jsr223.jython.Office_Occupancy ] - Got items Office_Occupancy_Timeout_Seconds (Type=NumberItem, State=10, Label=Occupancy Timeout - Seconds, Category=null, Groups=[gOffice, gSettings]) in state 10 and Office_Occupancy_Timeout_Minutes (Type=NumberItem, State=0.0, Label=Occupancy Timdout - Minutes, Category=null, Groups=[gOffice, gSettings]) in state 0.0
14:47:47.190 [DEBUG] [jsr223.jython.Office_Occupancy ] - Occupancy Warn Time Seconds item Office_Occupancy_WarnTime_Seconds found in state 5
14:47:47.191 [DEBUG] [jsr223.jython.Office_Occupancy ] - Occupancy Warn Time Minutes item Office_Occupancy_WarnTime_Minutes found in state 0
14:47:47.191 [DEBUG] [jsr223.jython.Office_Occupancy ] - Got items Office_Occupancy_WarnTime_Seconds (Type=NumberItem, State=5, Label=Occupancy Warn Time - Seconds, Category=null, Groups=[gOffice, gSettings]) in state 5 and Office_Occupancy_WarnTime_Minutes (Type=NumberItem, State=0, Label=Occupancy Warn Time - Minutes, Category=null, Groups=[gOffice, gSettings]) in state 0
14:47:47.192 [DEBUG] [jsr223.jython.Office_Occupancy ] - Sensor off: timeout in 10 seconds with warning at 5 seconds to go
14:47:47.194 [DEBUG] [jsr223.jython.Office_Occupancy ] - Prewarn timeout: 5
14:47:47.195 [DEBUG] [jsr223.jython.OccupancyTestRule ] - State: occupied_timed
14:47:47.195 [DEBUG] [re.automation.internal.RuleEngineImpl] - The rule '557dab0f-12d5-4666-9d43-da1ce2e1c17f' is executed.
14:47:47.195 [INFO ] [smarthome.event.RuleStatusInfoEvent ] - 557dab0f-12d5-4666-9d43-da1ce2e1c17f updated: IDLE
14:47:51.214 [INFO ] [smarthome.event.ItemStateChangedEvent] - LivingRoom_OccupancySensor_Luminance changed from 25 to 37
14:47:52.197 [DEBUG] [jsr223.jython.Office_Occupancy ] - Exiting occupied-timed and deleting timer
14:47:52.198 [DEBUG] [jsr223.jython.Office_Occupancy ] - Timer found, canceling
14:47:52.198 [DEBUG] [jsr223.jython.Office_Occupancy ] - Deleting timer
14:47:52.199 [DEBUG] [jsr223.jython.Office_Occupancy ] - Office entering warning state. Starting timer and flashing lights.
14:47:52.200 [INFO ] [smarthome.event.ItemStateChangedEvent] - Office_Occupancy_State changed from Occupied - Timed to Occupied - Warning
14:47:52.200 [DEBUG] [jsr223.jython.core.utils ] - New postUpdate value for [Office_Occupancy_State] is [Occupied - Warning]
14:47:52.201 [DEBUG] [jsr223.jython.Office_Occupancy ] - Getting lights in gOffice
14:47:52.202 [ERROR] [org.quartz.core.JobRunShell ] - Job DEFAULT.Timer 477 2020-08-07T14:47:52.194-04:00: <function <lambda> at 0x204> threw an unhandled Exception:
org.python.core.PyException: null
at org.python.core.PyException.doRaise(PyException.java:198) ~[?:?]
at org.python.core.Py.makeException(Py.java:1337) ~[?:?]
at org.python.core.Py.makeException(Py.java:1341) ~[?:?]
at org.python.core.Py.makeException(Py.java:1345) ~[?:?]
at org.python.core.Py.makeException(Py.java:1349) ~[?:?]
at transitions.core$py._process$33(/etc/openhab2/automation/lib/python/transitions/core.py:427) ~[?:?]
at transitions.core$py.call_function(/etc/openhab2/automation/lib/python/transitions/core.py) ~[?:?]
at org.python.core.PyTableCode.call(PyTableCode.java:167) ~[?:?]
at org.python.core.PyBaseCode.call(PyBaseCode.java:153) ~[?:?]
at org.python.core.PyFunction.__call__(PyFunction.java:423) ~[?:?]
at org.python.core.PyMethod.__call__(PyMethod.java:141) ~[?:?]
at transitions.core$py._trigger$32(/etc/openhab2/automation/lib/python/transitions/core.py:408) ~[?:?]
at transitions.core$py.call_function(/etc/openhab2/automation/lib/python/transitions/core.py) ~[?:?]
at org.python.core.PyTableCode.call(PyTableCode.java:167) ~[?:?]
at org.python.core.PyBaseCode.call(PyBaseCode.java:307) ~[?:?]
at org.python.core.PyBaseCode.call(PyBaseCode.java:198) ~[?:?]
at org.python.core.PyFunction.__call__(PyFunction.java:482) ~[?:?]
at org.python.core.PyMethod.instancemethod___call__(PyMethod.java:237) ~[?:?]
at org.python.core.PyMethod.__call__(PyMethod.java:228) ~[?:?]
at org.python.core.PyMethod.__call__(PyMethod.java:223) ~[?:?]
at org.python.modules._functools.PyPartial.partial___call__(PyPartial.java:124) ~[?:?]
at org.python.modules._functools.PyPartial.__call__(PyPartial.java:79) ~[?:?]
at org.python.core.PyObject.__call__(PyObject.java:445) ~[?:?]
at org.python.core.PyObject.__call__(PyObject.java:449) ~[?:?]
at transitions.core$py._process$80(/etc/openhab2/automation/lib/python/transitions/core.py:1128) ~[?:?]
at transitions.core$py.call_function(/etc/openhab2/automation/lib/python/transitions/core.py) ~[?:?]
at org.python.core.PyTableCode.call(PyTableCode.java:167) ~[?:?]
at org.python.core.PyBaseCode.call(PyBaseCode.java:153) ~[?:?]
at org.python.core.PyFunction.__call__(PyFunction.java:423) ~[?:?]
at org.python.core.PyMethod.__call__(PyMethod.java:141) ~[?:?]
at transitions.core$py.trigger$31(/etc/openhab2/automation/lib/python/transitions/core.py:390) ~[?:?]
at transitions.core$py.call_function(/etc/openhab2/automation/lib/python/transitions/core.py) ~[?:?]
at org.python.core.PyTableCode.call(PyTableCode.java:167) ~[?:?]
at org.python.core.PyBaseCode.call(PyBaseCode.java:307) ~[?:?]
at org.python.core.PyBaseCode.call(PyBaseCode.java:198) ~[?:?]
at org.python.core.PyFunction.__call__(PyFunction.java:482) ~[?:?]
at org.python.core.PyMethod.instancemethod___call__(PyMethod.java:237) ~[?:?]
at org.python.core.PyMethod.__call__(PyMethod.java:228) ~[?:?]
at org.python.core.PyMethod.__call__(PyMethod.java:223) ~[?:?]
at org.python.modules._functools.PyPartial.partial___call__(PyPartial.java:124) ~[?:?]
at org.python.modules._functools.PyPartial.__call__(PyPartial.java:79) ~[?:?]
at org.python.core.PyObject.__call__(PyObject.java:445) ~[?:?]
at org.python.core.PyObject.__call__(PyObject.java:449) ~[?:?]
at personal.OccupancyStateMachine$py.f$11(/etc/openhab2/automation/lib/python/personal/OccupancyStateMachine.py:154) ~[?:?]
at personal.OccupancyStateMachine$py.call_function(/etc/openhab2/automation/lib/python/personal/OccupancyStateMachine.py) ~[?:?]
at org.python.core.PyTableCode.call(PyTableCode.java:167) ~[?:?]
at org.python.core.PyBaseCode.call(PyBaseCode.java:124) ~[?:?]
at org.python.core.PyFunction.__call__(PyFunction.java:403) ~[?:?]
at org.python.core.PyFunction.__call__(PyFunction.java:398) ~[?:?]
at org.python.core.PyFunction.invoke(PyFunction.java:533) ~[?:?]
at com.sun.proxy.$Proxy344.apply(Unknown Source) ~[?:?]
at org.eclipse.smarthome.model.script.internal.actions.TimerExecutionJob.execute(TimerExecutionJob.java:48) ~[?:?]
at org.quartz.core.JobRunShell.run(JobRunShell.java:202) [bundleFile:?]
at org.quartz.simpl.SimpleThreadPool$WorkerThread.run(SimpleThreadPool.java:573) [bundleFile:?]
14:47:52.211 [ERROR] [org.quartz.core.ErrorLogger ] - Job (DEFAULT.Timer 477 2020-08-07T14:47:52.194-04:00: <function <lambda> at 0x204> threw an exception.
org.quartz.SchedulerException: Job threw an unhandled exception.
at org.quartz.core.JobRunShell.run(JobRunShell.java:213) [bundleFile:?]
at org.quartz.simpl.SimpleThreadPool$WorkerThread.run(SimpleThreadPool.java:573) [bundleFile:?]
Caused by: org.python.core.PyException
at org.python.core.PyException.doRaise(PyException.java:198) ~[?:?]
at org.python.core.Py.makeException(Py.java:1337) ~[?:?]
at org.python.core.Py.makeException(Py.java:1341) ~[?:?]
at org.python.core.Py.makeException(Py.java:1345) ~[?:?]
at org.python.core.Py.makeException(Py.java:1349) ~[?:?]
at transitions.core$py._process$33(/etc/openhab2/automation/lib/python/transitions/core.py:427) ~[?:?]
at transitions.core$py.call_function(/etc/openhab2/automation/lib/python/transitions/core.py) ~[?:?]
at org.python.core.PyTableCode.call(PyTableCode.java:167) ~[?:?]
at org.python.core.PyBaseCode.call(PyBaseCode.java:153) ~[?:?]
at org.python.core.PyFunction.__call__(PyFunction.java:423) ~[?:?]
at org.python.core.PyMethod.__call__(PyMethod.java:141) ~[?:?]
at transitions.core$py._trigger$32(/etc/openhab2/automation/lib/python/transitions/core.py:408) ~[?:?]
at transitions.core$py.call_function(/etc/openhab2/automation/lib/python/transitions/core.py) ~[?:?]
at org.python.core.PyTableCode.call(PyTableCode.java:167) ~[?:?]
at org.python.core.PyBaseCode.call(PyBaseCode.java:307) ~[?:?]
at org.python.core.PyBaseCode.call(PyBaseCode.java:198) ~[?:?]
at org.python.core.PyFunction.__call__(PyFunction.java:482) ~[?:?]
at org.python.core.PyMethod.instancemethod___call__(PyMethod.java:237) ~[?:?]
at org.python.core.PyMethod.__call__(PyMethod.java:228) ~[?:?]
at org.python.core.PyMethod.__call__(PyMethod.java:223) ~[?:?]
at org.python.modules._functools.PyPartial.partial___call__(PyPartial.java:124) ~[?:?]
at org.python.modules._functools.PyPartial.__call__(PyPartial.java:79) ~[?:?]
at org.python.core.PyObject.__call__(PyObject.java:445) ~[?:?]
at org.python.core.PyObject.__call__(PyObject.java:449) ~[?:?]
at transitions.core$py._process$80(/etc/openhab2/automation/lib/python/transitions/core.py:1128) ~[?:?]
at transitions.core$py.call_function(/etc/openhab2/automation/lib/python/transitions/core.py) ~[?:?]
at org.python.core.PyTableCode.call(PyTableCode.java:167) ~[?:?]
at org.python.core.PyBaseCode.call(PyBaseCode.java:153) ~[?:?]
at org.python.core.PyFunction.__call__(PyFunction.java:423) ~[?:?]
at org.python.core.PyMethod.__call__(PyMethod.java:141) ~[?:?]
at transitions.core$py.trigger$31(/etc/openhab2/automation/lib/python/transitions/core.py:390) ~[?:?]
at transitions.core$py.call_function(/etc/openhab2/automation/lib/python/transitions/core.py) ~[?:?]
at org.python.core.PyTableCode.call(PyTableCode.java:167) ~[?:?]
at org.python.core.PyBaseCode.call(PyBaseCode.java:307) ~[?:?]
at org.python.core.PyBaseCode.call(PyBaseCode.java:198) ~[?:?]
at org.python.core.PyFunction.__call__(PyFunction.java:482) ~[?:?]
at org.python.core.PyMethod.instancemethod___call__(PyMethod.java:237) ~[?:?]
at org.python.core.PyMethod.__call__(PyMethod.java:228) ~[?:?]
at org.python.core.PyMethod.__call__(PyMethod.java:223) ~[?:?]
at org.python.modules._functools.PyPartial.partial___call__(PyPartial.java:124) ~[?:?]
at org.python.modules._functools.PyPartial.__call__(PyPartial.java:79) ~[?:?]
at org.python.core.PyObject.__call__(PyObject.java:445) ~[?:?]
at org.python.core.PyObject.__call__(PyObject.java:449) ~[?:?]
at personal.OccupancyStateMachine$py.f$11(/etc/openhab2/automation/lib/python/personal/OccupancyStateMachine.py:154) ~[?:?]
at personal.OccupancyStateMachine$py.call_function(/etc/openhab2/automation/lib/python/personal/OccupancyStateMachine.py) ~[?:?]
at org.python.core.PyTableCode.call(PyTableCode.java:167) ~[?:?]
at org.python.core.PyBaseCode.call(PyBaseCode.java:124) ~[?:?]
at org.python.core.PyFunction.__call__(PyFunction.java:403) ~[?:?]
at org.python.core.PyFunction.__call__(PyFunction.java:398) ~[?:?]
at org.python.core.PyFunction.invoke(PyFunction.java:533) ~[?:?]
at com.sun.proxy.$Proxy344.apply(Unknown Source) ~[?:?]
at org.eclipse.smarthome.model.script.internal.actions.TimerExecutionJob.execute(TimerExecutionJob.java:48) ~[?:?]
at org.quartz.core.JobRunShell.run(JobRunShell.java:202) ~[?:?]
... 1 more
I have a simple test rule that triggers the transitions:
from core.rules import rule
from core.triggers import when
from core.utils import post_update_if_different
from core.jsr223.scope import OnOffType
import time
import personal.OccupancyStateMachine
reload(personal.OccupancyStateMachine)
from personal.OccupancyStateMachine import OccupancySM
office_occupancy = OccupancySM("Office")
@rule("OccupancyTestRule")
@when("Item TestSwitch received command")
def occupancy_test_rule(event):
occupancy_test_rule.log.debug("TestSwitch received command {}".format(event.itemCommand))
if event.itemCommand == ON:
office_occupancy.motion()
occupancy_test_rule.log.debug("State: {}".format(office_occupancy.state))
else:
office_occupancy.no_motion()
occupancy_test_rule.log.debug("State: {}".format(office_occupancy.state))
#office_occupancy.no_motion()
#occupancy_test_rule.log.debug("State: {}".format(office_occupancy.state))
#office_occupancy.no_motion()
#occupancy_test_rule.log.debug("State: {}".format(office_occupancy.state))
#time.sleep(10)
#occupancy_test_rule.log.debug("State: {}".format(office_occupancy.state))
If I take out the line of code towards the end of the on_enter_occupied_timed state I can manual cause the state machine to transition from one state to the next and everything behaves exactly as I expect it to. This is the line of code I remove:
`motionTimers[self.sceneRoom] = ScriptExecution.createTimer(DateTime.now().plusSeconds(self.prewarnTimeout), lambda: self.no_motion())`
But as soon as I put it back in I get the above error. I really can’t figure out what’s causing the error and it’s such a non-descript error that has very little diagnistic help that I can’t figure out what’s causing it. I’ve found that if I don’t call no_motion() from the timer lambda I don’t get the error but I also don’t get the transition.
I know this is not a topic that’s addressed a lot here and it’s fairly specific so if I don’t get many suggestions I’ll understand but I was hoping someone might have some pointers before I give up and try to go a different route. I was hoping to make this a class so I can instantiate it for any room I want to use it in.
Help is always appreciated.
Thanks!
Matthew