jmri_scripting.automaton
AbstractAutomaton — the inheritable base every JMRI script extends.
In JMRI's Jython world, the common pattern is::
class MyScript(AbstractAutomaton):
def init(self): ...
def handle(self): ... # returns True to keep running, False to stop
MyScript().start()
We provide the same shape with one tweak: scripts run on a worker thread
owned by ScriptRunner rather than spawning their own. start
just registers them; the runner pumps handle in a loop until it
returns False, the script is stopped externally, or it raises.
1"""``AbstractAutomaton`` — the inheritable base every JMRI script extends. 2 3In JMRI's Jython world, the common pattern is:: 4 5 class MyScript(AbstractAutomaton): 6 def init(self): ... 7 def handle(self): ... # returns True to keep running, False to stop 8 9 MyScript().start() 10 11We provide the same shape with one tweak: scripts run on a worker thread 12owned by :class:`ScriptRunner` rather than spawning their own. ``start`` 13just registers them; the runner pumps ``handle`` in a loop until it 14returns False, the script is stopped externally, or it raises. 15""" 16from __future__ import annotations 17 18import logging 19import threading 20from typing import TYPE_CHECKING 21 22if TYPE_CHECKING: 23 from jmri_scripting.shim import JmriShim 24 25 26class AbstractAutomaton: 27 """Base class for scripted automations. 28 29 Subclass and override ``init`` (one-time setup) and ``handle`` 30 (called in a loop, returns ``True`` to continue). Scripts can call 31 :meth:`stop` to terminate themselves. 32 """ 33 34 # The shim is injected by the runner before init() runs. 35 shim: JmriShim 36 37 def __init__(self, name: str = "") -> None: 38 self.name = name or type(self).__name__ 39 self._stop_event = threading.Event() 40 self._log = logging.getLogger(f"jmri_scripting.{self.name}") 41 42 # ----- lifecycle (overridden by subclasses) ----------------------------- 43 44 def init(self) -> None: 45 """One-time setup before the handle loop starts.""" 46 47 def handle(self) -> bool: 48 """Run one iteration. Return True to keep running, False to stop.""" 49 return False 50 51 def cleanup(self) -> None: 52 """Optional teardown after the handle loop exits.""" 53 54 # ----- control ------------------------------------------------------- 55 56 def stop(self) -> None: 57 """Request the automaton to stop. Effective on the next handle iteration.""" 58 self._stop_event.set() 59 60 @property 61 def should_stop(self) -> bool: 62 return self._stop_event.is_set() 63 64 # ----- runner-driven loop ------------------------------------------------ 65 66 def run(self) -> None: 67 """Run the full lifecycle: init → handle-loop → cleanup.""" 68 try: 69 self.init() 70 while not self._stop_event.is_set(): 71 if not self.handle(): 72 break 73 except Exception: 74 self._log.exception("automaton %s crashed", self.name) 75 raise 76 finally: 77 try: 78 self.cleanup() 79 except Exception: 80 self._log.exception("automaton %s cleanup failed", self.name)
class
AbstractAutomaton:
27class AbstractAutomaton: 28 """Base class for scripted automations. 29 30 Subclass and override ``init`` (one-time setup) and ``handle`` 31 (called in a loop, returns ``True`` to continue). Scripts can call 32 :meth:`stop` to terminate themselves. 33 """ 34 35 # The shim is injected by the runner before init() runs. 36 shim: JmriShim 37 38 def __init__(self, name: str = "") -> None: 39 self.name = name or type(self).__name__ 40 self._stop_event = threading.Event() 41 self._log = logging.getLogger(f"jmri_scripting.{self.name}") 42 43 # ----- lifecycle (overridden by subclasses) ----------------------------- 44 45 def init(self) -> None: 46 """One-time setup before the handle loop starts.""" 47 48 def handle(self) -> bool: 49 """Run one iteration. Return True to keep running, False to stop.""" 50 return False 51 52 def cleanup(self) -> None: 53 """Optional teardown after the handle loop exits.""" 54 55 # ----- control ------------------------------------------------------- 56 57 def stop(self) -> None: 58 """Request the automaton to stop. Effective on the next handle iteration.""" 59 self._stop_event.set() 60 61 @property 62 def should_stop(self) -> bool: 63 return self._stop_event.is_set() 64 65 # ----- runner-driven loop ------------------------------------------------ 66 67 def run(self) -> None: 68 """Run the full lifecycle: init → handle-loop → cleanup.""" 69 try: 70 self.init() 71 while not self._stop_event.is_set(): 72 if not self.handle(): 73 break 74 except Exception: 75 self._log.exception("automaton %s crashed", self.name) 76 raise 77 finally: 78 try: 79 self.cleanup() 80 except Exception: 81 self._log.exception("automaton %s cleanup failed", self.name)
Base class for scripted automations.
Subclass and override init (one-time setup) and handle
(called in a loop, returns True to continue). Scripts can call
stop() to terminate themselves.
shim: jmri_scripting.JmriShim
def
handle(self) -> bool:
48 def handle(self) -> bool: 49 """Run one iteration. Return True to keep running, False to stop.""" 50 return False
Run one iteration. Return True to keep running, False to stop.
def
stop(self) -> None:
57 def stop(self) -> None: 58 """Request the automaton to stop. Effective on the next handle iteration.""" 59 self._stop_event.set()
Request the automaton to stop. Effective on the next handle iteration.
def
run(self) -> None:
67 def run(self) -> None: 68 """Run the full lifecycle: init → handle-loop → cleanup.""" 69 try: 70 self.init() 71 while not self._stop_event.is_set(): 72 if not self.handle(): 73 break 74 except Exception: 75 self._log.exception("automaton %s crashed", self.name) 76 raise 77 finally: 78 try: 79 self.cleanup() 80 except Exception: 81 self._log.exception("automaton %s cleanup failed", self.name)
Run the full lifecycle: init → handle-loop → cleanup.