From cd31b9e882e9b20a8d3058c76706629f49ef4133 Mon Sep 17 00:00:00 2001 From: Hinko Kocevar Date: Fri, 14 Mar 2025 14:27:05 +0100 Subject: [PATCH 1/5] Obtain PV control values for the purpose of setting enum strings. --- smlib/io.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/smlib/io.py b/smlib/io.py index 09b879d..468a3ac 100644 --- a/smlib/io.py +++ b/smlib/io.py @@ -114,6 +114,9 @@ def connected(self) -> bool: '''Return True if the PV is connected.''' return self._conn + def get_ctrlvars(self) -> bool: + '''Return PV control variables.''' + return self._pv.get_ctrlvars() class fsmIOs(): '''Class representing a list of epicsIO objects.''' @@ -527,6 +530,10 @@ def writeAccess(self) -> bool: def enumStrings(self) -> list: '''Possible string values of enum PV''' + if self._data.get('enum_strs', None) is None: + ctrlvars = self._reflectedIO.get_ctrlvars() + if ctrlvars and 'enum_strs' in ctrlvars: + self._data['enum_strs'] = ctrlvars['enum_strs'] return self._data.get('enum_strs', None) def displayLimits(self) -> tuple: From c105544abec7c363a2f74204b46dd6300a020ead Mon Sep 17 00:00:00 2001 From: Hinko Kocevar Date: Tue, 27 May 2025 10:24:08 +0200 Subject: [PATCH 2/5] english comments --- smlib/fsm.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/smlib/fsm.py b/smlib/fsm.py index ec1f443..084fbe3 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: @@ -162,11 +162,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() From 8e5862589a4630970c03268239e08f8280d7965e Mon Sep 17 00:00:00 2001 From: Hinko Kocevar Date: Tue, 27 May 2025 10:24:38 +0200 Subject: [PATCH 3/5] do not throw exception if 0 states defined --- smlib/fsm.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/smlib/fsm.py b/smlib/fsm.py index 084fbe3..871f2e2 100644 --- a/smlib/fsm.py +++ b/smlib/fsm.py @@ -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 From 5934dfb9436098df9dc0aa4708164b3b9ede2686 Mon Sep 17 00:00:00 2001 From: Hinko Kocevar Date: Tue, 27 May 2025 10:25:59 +0200 Subject: [PATCH 4/5] wip on loader --- smlib/loader.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) 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.''' From 31efc00e26e1fee4354f21e8ca4385317fea95a4 Mon Sep 17 00:00:00 2001 From: Hinko Kocevar Date: Tue, 27 May 2025 10:33:28 +0200 Subject: [PATCH 5/5] make sure that the enum strings and char_value are obtained at first callback --- smlib/io.py | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/smlib/io.py b/smlib/io.py index 468a3ac..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,10 +125,6 @@ def connected(self) -> bool: '''Return True if the PV is connected.''' return self._conn - def get_ctrlvars(self) -> bool: - '''Return PV control variables.''' - return self._pv.get_ctrlvars() - class fsmIOs(): '''Class representing a list of epicsIO objects.''' @@ -129,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 @@ -530,10 +537,7 @@ def writeAccess(self) -> bool: def enumStrings(self) -> list: '''Possible string values of enum PV''' - if self._data.get('enum_strs', None) is None: - ctrlvars = self._reflectedIO.get_ctrlvars() - if ctrlvars and 'enum_strs' in ctrlvars: - self._data['enum_strs'] = ctrlvars['enum_strs'] + # 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: