Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FRQ: special state for error handling #455

Open
AlexMKX opened this issue Jun 28, 2024 · 1 comment
Open

FRQ: special state for error handling #455

AlexMKX opened this issue Jun 28, 2024 · 1 comment

Comments

@AlexMKX
Copy link

AlexMKX commented Jun 28, 2024

  • 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
Copy link
Owner

fgmacedo commented Sep 9, 2024

Hi @AlexMKX , how are you? Thanks for your suggestion. I think that in the end this relates to #386, as an alternative implementation solving the same issue.

Do you believe that some kind of a "finalize event handler" may solve your issue as well?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants