-
-
Notifications
You must be signed in to change notification settings - Fork 97
Open
Labels
Description
- Python State Machine version: 2.3.1
Description
Sometime action can't be completed and we need sort of cleanup code, which can be run after any action.
In my case (for irrigation automation) : confirm the valve is closed and closed it if its not.
Idea to solve:
Ability to issue special "from any state" transition to the cleanup handling final state. Specify the error handler in FSM configuration. Specify which states can be handled by the handler.
The current workaround
# the decorator to enter functions which could be handled
def catch_errors(handler=None):
def actual_decorator(func):
@wraps(func)
async def _wrapper(*args, **kwargs):
try:
return await func(*args, **kwargs)
except Exception as e:
if handler:
return await handler(*args, **kwargs)
return None
return _wrapper
return actual_decorator
class WorkItem(StateMachine):
created = State('created', initial=True)
open = State('open')
opened = State('opened')
close = State('close')
closed = State('closed', final=True)
error = State('error', final=True)
do_work = (created.to(open) |
open.to(opened, cond="is_open") |
opened.to(close, cond="can_close") |
close.to(closed, cond="is_closed"))
# the error transition map
do_error = (created.to(error) | open.to(error) | opened.to(error) | close.to(error))
# this will be sent if error (e.g. exception in enter handler occur)
async def on_error(self, *args, **kwargs):
await self.async_send("do_error")
# the decorated handler
@catch_errors(handler=on_error)
async def on_enter_created(self):
self._app.log(f"Entering 'created' state.")
if not self.is_closed():
async with asyncio.timeout(30):
self._app.error(f"Valve {self.zone.valve} is already open, closing it")
# this will throw exception
await self._app.call_service('homeassistant/turn_off1', entity_id=self.zone.valve)
# cleanup handler
async def on_enter_error(self):
self._app.error(f"Error in {self.zone.valve} {self.zone.moisture}")
try:
async with asyncio.timeout(60):
self._app.log(f"Closing valve {self.zone.valve} because of error")
await self._app.call_service('homeassistant/turn_off', entity_id=self.zone.valve)
while not self.is_closed():
await asyncio.sleep(1)
finally:
self._app.error(
f"Error in {self.zone.valve} {self.zone.moisture} the valve status is {self.zone.get_valve_state()}")
fgmacedo