feat(flomni,bec_widgets): also hard-stop the optics Galil controller
The abort button only stopped ftransy's controller (the sample-transfer board). foptx/fopty sit on a physically separate Galil controller (a different socket port), so motion there kept running through an abort. Adds an optional extra_hard_stop_device_name to ConsoleButtonsWidget, wired to "foptx" for flomni. It calls .controller.stop_all_axes() rather than hard_abort_and_restore_positioning_mode(): the latter's #POSMODE/mntmod handling is specific to the sample-transfer/mount program that only runs on ftransy's controller, so the plain stop_all_axes() (XQ#STOP,1) is the correct generic stop for any other board. Each device is stopped and logged independently so a failure on one doesn't skip the other. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit was merged in pull request #260.
This commit is contained in:
@@ -136,12 +136,16 @@ class flomniGuiTools:
|
||||
# .hard_abort_and_restore_positioning_mode() (the same Galil
|
||||
# hard-stop ftransfer_abort() uses) -- see ConsoleButtonsWidget's
|
||||
# docstring for why this replaced a blind stop-all-devices
|
||||
# broadcast.
|
||||
# broadcast. foptx sits on a physically separate Galil
|
||||
# controller (the optics stage board, not the transfer stage
|
||||
# board) so it needs its own stop_all_axes() call -- also
|
||||
# explained in ConsoleButtonsWidget's docstring.
|
||||
self.console = self.gui.flomni.new(
|
||||
"ConsoleButtonsWidget",
|
||||
object_name="console",
|
||||
where="bottom",
|
||||
hard_stop_device_name="ftransy",
|
||||
extra_hard_stop_device_name="foptx",
|
||||
hard_stop_label="Flomni Motion Stop",
|
||||
)
|
||||
# set_layout_ratios uses relative weights, not pixels -- there is
|
||||
|
||||
@@ -28,7 +28,9 @@ class ConsoleButtonsWidget(BECWidget, QWidget):
|
||||
the BEC IPython client process (the parent of the GUI server
|
||||
process), then directly calls ``hard_stop_device_name``'s
|
||||
``.controller.hard_abort_and_restore_positioning_mode()`` -- the same
|
||||
Galil hard-stop (XQ#STOP,1) the CLI's ``ftransfer_abort()`` uses.
|
||||
Galil hard-stop (XQ#STOP,1) the CLI's ``ftransfer_abort()`` uses --
|
||||
and, if configured, ``extra_hard_stop_device_name``'s
|
||||
``.controller.stop_all_axes()``.
|
||||
|
||||
SIGINT is sent BEFORE the hard stop, not after: if a client-side loop
|
||||
is actively polling this same transfer (e.g. ftransfer_get_sample's
|
||||
@@ -53,6 +55,19 @@ class ConsoleButtonsWidget(BECWidget, QWidget):
|
||||
of time -- it's re-resolved at click time in ``_on_abort()``, which
|
||||
already logs and no-ops on failure -- so the button being enabled is
|
||||
not a guarantee the device exists, only that a target was configured.
|
||||
|
||||
``extra_hard_stop_device_name`` optionally names a second device on a
|
||||
physically separate Galil controller (e.g. ``"foptx"`` for flomni's
|
||||
optics stage, a different board/socket port from the transfer stage
|
||||
``ftransy`` sits on -- one abort press only halts axes on ``ftransy``'s
|
||||
controller otherwise). Its ``.controller.stop_all_axes()`` is called
|
||||
instead of ``hard_abort_and_restore_positioning_mode()``: the latter's
|
||||
``#POSMODE``/``mntmod`` handling is specific to the sample-transfer/
|
||||
mount program that only runs on the transfer controller, so
|
||||
``stop_all_axes()`` (a plain ``XQ#STOP,1``) is the correct generic
|
||||
"halt everything on this controller" primitive for any other board.
|
||||
Entirely optional -- unset means only ``hard_stop_device_name`` is
|
||||
stopped, as before.
|
||||
"""
|
||||
|
||||
USER_ACCESS = ["message", "message.setter", "response", "clear_response"]
|
||||
@@ -60,6 +75,7 @@ class ConsoleButtonsWidget(BECWidget, QWidget):
|
||||
|
||||
def __init__(self, parent=None, **kwargs):
|
||||
self._hard_stop_device_name = kwargs.pop("hard_stop_device_name", None)
|
||||
self._extra_hard_stop_device_name = kwargs.pop("extra_hard_stop_device_name", None)
|
||||
self._hard_stop_label = kwargs.pop("hard_stop_label", "Motion Stop")
|
||||
super().__init__(parent=parent, **kwargs)
|
||||
self._response = ""
|
||||
@@ -174,22 +190,34 @@ class ConsoleButtonsWidget(BECWidget, QWidget):
|
||||
logger.warning(f"ConsoleButtonsWidget: sending SIGINT to client pid {self._client_pid}")
|
||||
os.kill(self._client_pid, signal.SIGINT)
|
||||
|
||||
# 3) Hard motion stop -- not delayed by anything above: sending
|
||||
# SIGINT is a near-instant os.kill() call, so this still runs
|
||||
# effectively immediately.
|
||||
# 3) Hard motion stop(s) -- not delayed by anything above: sending
|
||||
# SIGINT is a near-instant os.kill() call, so these still run
|
||||
# effectively immediately. The two devices are independent
|
||||
# controllers (see class docstring): each is attempted and
|
||||
# logged on its own, so a failure on one doesn't skip the other.
|
||||
if self._hard_stop_device_name:
|
||||
try:
|
||||
device = getattr(self.dev, self._hard_stop_device_name, None)
|
||||
except Exception:
|
||||
device = None
|
||||
if device is not None:
|
||||
logger.warning(
|
||||
f"ConsoleButtonsWidget: hard-stopping {self._hard_stop_device_name}"
|
||||
)
|
||||
try:
|
||||
device.controller.hard_abort_and_restore_positioning_mode()
|
||||
except Exception:
|
||||
logger.exception("ConsoleButtonsWidget: hard motion stop failed")
|
||||
self._stop_device_controller(
|
||||
self._hard_stop_device_name, "hard_abort_and_restore_positioning_mode"
|
||||
)
|
||||
if self._extra_hard_stop_device_name:
|
||||
self._stop_device_controller(self._extra_hard_stop_device_name, "stop_all_axes")
|
||||
|
||||
def _stop_device_controller(self, device_name: str, method_name: str) -> None:
|
||||
"""Resolve device_name and call method_name() on its .controller,
|
||||
logging and swallowing any failure -- so a missing device or a
|
||||
failed stop on one controller can't prevent an already-issued stop
|
||||
on another, or crash the rest of _on_abort()."""
|
||||
try:
|
||||
device = getattr(self.dev, device_name, None)
|
||||
except Exception:
|
||||
device = None
|
||||
if device is None:
|
||||
return
|
||||
logger.warning(f"ConsoleButtonsWidget: hard-stopping {device_name} ({method_name})")
|
||||
try:
|
||||
getattr(device.controller, method_name)()
|
||||
except Exception:
|
||||
logger.exception(f"ConsoleButtonsWidget: hard motion stop of {device_name} failed")
|
||||
|
||||
@SafeProperty(str)
|
||||
def message(self):
|
||||
|
||||
Reference in New Issue
Block a user