diff --git a/smlib/fsm.py b/smlib/fsm.py index ec1f443..871f2e2 100644 --- a/smlib/fsm.py +++ b/smlib/fsm.py @@ -59,10 +59,10 @@ def __init__(self, name:str, **args) -> None: self._awakerReason = "" self._watchdog = None - # populate the sensityvity list for each state + # populate the sensitivity list for each state def setSensLists(self, statesWithIos: dict) -> None: - # statesWithIos e un dizionario in cui la chiave e il nome dello stato e - # il valore un array di ingressi utilizzati dallo stato + # statesWithIos is a dictionary in which the key is the name of the state + # and the value an array of epicsIO inputs used by the state for state, iolist in statesWithIos.items(): iodict = {} for io in iolist: @@ -91,11 +91,11 @@ def gotoState(self, state: str, *args, **kwargs) -> None: return self._nextstatename = state self._nextstateargs = (args, kwargs) - # metodo eval del prossimo stato + # next state's eval method self._nextstate = getattr(self, '%s_eval' % state) - # metodo entry del prossimo stato + # next state's entry method self._nextentry = getattr(self, '%s_entry' % state, None) - # metodo exit del prossimo stato + # next state's exit method self._nextexit = getattr(self, '%s_exit' % state, None) def gotoPrevState(self, *args, **kwargs) -> None: @@ -134,6 +134,8 @@ def logTimeReset(self) -> None: def eval(self) -> bool: '''Execute the current state''' changed = False + if self._nextstate is None and self._curstate is None: + return False if self._nextstate != self._curstate: self.logD('%s => %s' % (self._curstatename, self._nextstatename)) self._prevstatename = self._curstatename @@ -162,11 +164,11 @@ def eval(self) -> bool: def eval_forever(self) -> None: '''Main loop of the FSM''' while not self._stop_thread: - changed = self.eval() # eval viene eseguito senza lock - self.lock() # blocca la coda degli eventi + changed = self.eval() # eval runs without lock + self.lock() # block on the queue for events if not changed and len(self._events) == 0: self.logD("No events to process going to sleep\n") - self._cond.wait() # la macchina va in sleep in attesa di un evento (da un IO, timer...) + self._cond.wait() # go to sleep waiting for an event (from an IO, timer ...) self.logD('awoken') self._process_one_event() # PROCESS ONLY IF RETURN TRUE? self.unlock() diff --git a/smlib/io.py b/smlib/io.py index 09b879d..999a0de 100644 --- a/smlib/io.py +++ b/smlib/io.py @@ -24,14 +24,25 @@ class epicsIO(): '''Class representing an IO with Epics support for a finite state machine.''' - def __init__(self, name:str) -> None: + def __init__(self, name:str, **args) -> None: self._name = name self._data = {} # keep all infos arriving with change callback self._conn = False # keeps all infos arriving with connection callback self._attached = set() # set of finite state machines using this IO self._cond = threading.Condition() - self._pv = epics.PV(name, callback=self.chgcb, connection_callback=self.concb, auto_monitor=True) + # get the enum strings and current char_value if this is an enum PV + # this handling is required because initial char_value that the monitor + # gets is *NOT* one of the enum strings, but a numeric value as string + if args.get('is_enum', False): + # make sure we do not get callback update until the enum strings are set + self._pv = epics.PV(name, connection_callback=self.concb, auto_monitor=False) + self._pv.get_ctrlvars() + # set and fire the callback + self._pv.add_callback(self.chgcb) + self._pv.auto_monitor = True + else: + self._pv = epics.PV(name, callback=self.chgcb, connection_callback=self.concb, auto_monitor=True) def ioname(self) -> str: '''Return the name of the PV.''' @@ -114,7 +125,6 @@ def connected(self) -> bool: '''Return True if the PV is connected.''' return self._conn - class fsmIOs(): '''Class representing a list of epicsIO objects.''' @@ -126,7 +136,7 @@ def get(self, name: str, fsm: 'fsmBase', **args) -> epicsIO: # first time this input was requested: we create and attach it if name not in self._ios: - self._ios[name] = epicsIO(name) + self._ios[name] = epicsIO(name, **args) # input already created: if not already attached to the fsm, trigger some # fake events to init fsm @@ -527,6 +537,7 @@ def writeAccess(self) -> bool: def enumStrings(self) -> list: '''Possible string values of enum PV''' + # print("XXX %s enum_strs: %s" % (self._name, self._data.get('enum_strs', None))) return self._data.get('enum_strs', None) def displayLimits(self) -> tuple: diff --git a/smlib/loader.py b/smlib/loader.py index b450c83..eefef15 100644 --- a/smlib/loader.py +++ b/smlib/loader.py @@ -63,22 +63,23 @@ def killAll(self, signum, frame): # pylint: disable=unused-argument for fsm in self._fsmsList: if fsm.is_alive(): fsm.kill() + print("Killed fsm", fsm) print("Killed all the fsms") if self._timerManager.is_alive(): # if no fsm is loaded it won't be alive self._timerManager.kill() + print("Killed the timer manager", self._timerManager) print("Killed the timer manager") def printUnconnectedIOs(self, signum, frame): # pylint: disable=unused-argument '''Print all the unconnected IOs.''' ios = self._ioManager.getAll() s = 0 - print("DISCONNECTED INPUTS:") + print("DISCONNECTED INPUTS (ios %s):" % self._ioManager) for i in ios: if not i.connected(): print(i.ioname()) s += 1 print("Total disconnected inputs: %d out of %d!" % (s, len(ios))) - signal.pause() def start(self, blocking=True): '''Start all the loaded fsms.'''