193 lines
6.6 KiB
Python
193 lines
6.6 KiB
Python
#!/usr/bin/env python
|
|
# -*- coding: utf-8 -*-
|
|
# *****************************************************************************
|
|
#
|
|
# This program is free software; you can redistribute it and/or modify it under
|
|
# the terms of the GNU General Public License as published by the Free Software
|
|
# Foundation; either version 2 of the License, or (at your option) any later
|
|
# version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful, but WITHOUT
|
|
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
|
# details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License along with
|
|
# this program; if not, write to the Free Software Foundation, Inc.,
|
|
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
#
|
|
# Module authors:
|
|
# Markus Zolliker <markus.zolliker@psi.ch>
|
|
#
|
|
# *****************************************************************************
|
|
"""state machine mixin
|
|
|
|
handles status depending on statemachine state
|
|
"""
|
|
|
|
|
|
from secop.core import BUSY, IDLE, ERROR, Parameter, Command, Done
|
|
from secop.lib.newstatemachine import StateMachine, Retry, Finish, Start, Stop
|
|
|
|
|
|
def status_code(code, text=None):
|
|
"""decorator, attaching a status to a state function
|
|
|
|
:param code: the first element of the secop status tuple
|
|
:param text: the second element of the secop status tuple. if not given,
|
|
the name of the state function is used (underscores replaced by spaces)
|
|
:return: the decorator function
|
|
|
|
if a state function has not attached status and is a method of the module running
|
|
the state machine, the status is inherited from an overridden method, if available
|
|
|
|
a state function without attached status does not change the status, or, if it is
|
|
used as the start function, BUSY is taken as default status code
|
|
"""
|
|
def wrapper(func):
|
|
func.status = code, func.__name__.replace('_', ' ') if text is None else text
|
|
return func
|
|
return wrapper
|
|
|
|
|
|
class HasStates:
|
|
status = Parameter() # make sure this is a parameter
|
|
all_status_changes = False # when true, send also updates for status changes within a cycle
|
|
_state_machine = None
|
|
_status = IDLE, ''
|
|
statusMap = None
|
|
|
|
def init_state_machine(self, **kwds):
|
|
self._state_machine = StateMachine(
|
|
logger=self.log,
|
|
idle_status=(IDLE, ''),
|
|
transition=self.state_transition,
|
|
reset_fast_poll=False,
|
|
status=(IDLE, ''),
|
|
**kwds)
|
|
|
|
def initModule(self):
|
|
super().initModule()
|
|
self.statusMap = {}
|
|
self.init_state_machine()
|
|
|
|
def state_transition(self, sm, newstate):
|
|
"""handle status updates"""
|
|
status = self.get_status(newstate)
|
|
if sm.next_task:
|
|
if isinstance(sm.next_task, Stop):
|
|
if newstate and status is not None:
|
|
status = status[0], 'stopping (%s)' % status[1]
|
|
elif newstate:
|
|
# restart case
|
|
if status is not None:
|
|
status = sm.status[0], 'restarting (%s)' % status[1]
|
|
else:
|
|
# start case
|
|
status = self.get_status(sm.next_task.newstate, BUSY)
|
|
if status:
|
|
sm.status = status
|
|
if self.all_status_changes:
|
|
self.read_status()
|
|
|
|
def get_status(self, statefunc, default_code=None):
|
|
if statefunc is None:
|
|
status = self._state_machine.idle_status or (ERROR, 'Finish was returned without final status')
|
|
else:
|
|
name = statefunc.__name__
|
|
try:
|
|
# look up in statusMap cache
|
|
status = self.statusMap[name]
|
|
except KeyError:
|
|
# try to get status from method or inherited method
|
|
cls = type(self)
|
|
for base in cls.__mro__:
|
|
try:
|
|
status = getattr(base, name, None).status
|
|
break
|
|
except AttributeError:
|
|
pass
|
|
else:
|
|
status = None
|
|
# store it in the cache for all further calls
|
|
self.statusMap[name] = status
|
|
if status is None and default_code is not None:
|
|
status = default_code, name.replace('_', ' ')
|
|
return status
|
|
|
|
def read_status(self):
|
|
sm = self._state_machine
|
|
if sm.status == self.status:
|
|
return Done
|
|
return sm.status
|
|
|
|
def cycle_machine(self):
|
|
sm = self._state_machine
|
|
sm.cycle()
|
|
if sm.statefunc is None:
|
|
if sm.reset_fast_poll:
|
|
sm.reset_fast_poll = False
|
|
self.setFastPoll(False)
|
|
self.read_status()
|
|
|
|
def doPoll(self):
|
|
super().doPoll()
|
|
self.cycle_machine()
|
|
|
|
def on_cleanup(self, sm):
|
|
if isinstance(sm.cleanup_reason, Exception):
|
|
return self.on_error(sm)
|
|
if isinstance(sm.cleanup_reason, Start):
|
|
return self.on_restart(sm)
|
|
if isinstance(sm.cleanup_reason, Stop):
|
|
return self.on_stop(sm)
|
|
self.log.error('bad cleanup reason %r', sm.cleanup_reason)
|
|
|
|
def on_error(self, sm):
|
|
self.log.error('handle error %r', sm.cleanup_reason)
|
|
self.final_status(ERROR, repr(sm.cleanup_reason))
|
|
return None
|
|
|
|
def on_restart(self, sm):
|
|
return None
|
|
|
|
def on_stop(self, sm):
|
|
return None
|
|
|
|
def start_machine(self, statefunc, fast_poll=True, **kwds):
|
|
sm = self._state_machine
|
|
sm.status = self.get_status(statefunc, BUSY)
|
|
if sm.statefunc:
|
|
sm.status = sm.status[0], 'restarting'
|
|
sm.start(statefunc, cleanup=kwds.pop('cleanup', self.on_cleanup), **kwds)
|
|
self.read_status()
|
|
if fast_poll:
|
|
sm.reset_fast_poll = True
|
|
self.setFastPoll(True)
|
|
self.pollInfo.trigger(True) # trigger poller
|
|
|
|
def stop_machine(self, stopped_status=(IDLE, 'stopped')):
|
|
sm = self._state_machine
|
|
if sm.is_active:
|
|
sm.idle_status = stopped_status
|
|
sm.stop()
|
|
sm.status = self.get_status(sm.statefunc, sm.status[0])[0], 'stopping'
|
|
self.read_status()
|
|
self.pollInfo.trigger(True) # trigger poller
|
|
|
|
@Command
|
|
def stop(self):
|
|
self.stop_machine()
|
|
|
|
def final_status(self, code=IDLE, text=''):
|
|
"""final status
|
|
|
|
Usage:
|
|
|
|
return self.final_status('IDLE', 'machine idle')
|
|
"""
|
|
sm = self._state_machine
|
|
sm.idle_status = code, text
|
|
sm.cleanup = None
|
|
return Finish
|