Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
5a353d6
MotorSignals: add methods set_blocking, disconnect, and disconnect_al…
rocco8773 Jul 28, 2026
f4efd1e
add kwarg disconnect_signals to EventActor and all of its sub-classes
rocco8773 Jul 28, 2026
34d5d8f
Motor.run() set signal blockking to False
rocco8773 Jul 28, 2026
e726f89
MGWidget: created method _terminate_mg() to handle motion group termi…
rocco8773 Jul 28, 2026
b8dbb39
add whitespace
rocco8773 Jul 28, 2026
a6a1444
explictly pass disconnect_signals for all instances of terminate()
rocco8773 Jul 28, 2026
1a2998f
DriveBaseController._target_postion_changed: put log after retrievein…
rocco8773 Jul 28, 2026
c656cf4
MGWidget._upgate_position_in_plot: pass to Position.emit() an empty l…
rocco8773 Jul 28, 2026
65dde81
DriveControlWidget: created Slot _handle_controller_target_position_c…
rocco8773 Jul 28, 2026
e866452
DriveControlWidget: created Slot _handle_controller_drive_status_chan…
rocco8773 Jul 28, 2026
f51c858
MGWidget: created Slot _handle_drive_control_target_position_changed …
rocco8773 Jul 28, 2026
4c548ad
Merge branch 'main' into patch_drive_switching_in_mgwidget
rocco8773 Jul 28, 2026
feead01
MGWidget._validate_drive: add if-clause case to cover when the drive …
rocco8773 Jul 28, 2026
d4211b4
ConfigureGUI.update_display_mg_list: add conditional to cover a motio…
rocco8773 Jul 28, 2026
3f98505
DriveBaseController: in _drive_connection_lost and _drive_connection_…
rocco8773 Jul 29, 2026
40adb39
MGWidget: create @Slot _handle_drive_status_changed
rocco8773 Jul 29, 2026
c6d4d2e
rework DriveConfigOverlay._validate_drive() to have the validate butt…
rocco8773 Jul 29, 2026
869d76a
fix typo
rocco8773 Jul 29, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions bapsf_motion/actors/axis_.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,9 +132,16 @@ def run(self, auto_run: bool = True, force_run: bool = True):
if isinstance(self.motor, Motor):
self.motor.run(auto_run=auto_run, force_run=force_run)

def terminate(self, delay_loop_stop=False):
self.motor.terminate(delay_loop_stop=True)
super().terminate(delay_loop_stop=delay_loop_stop)
def terminate(
self,
delay_loop_stop: bool = False,
disconnect_signals: bool = False,
):
self.motor.terminate(delay_loop_stop=True, disconnect_signals=disconnect_signals)
super().terminate(
delay_loop_stop=delay_loop_stop,
disconnect_signals=disconnect_signals,
)

def _spawn_motor(self, ip, motor_settings: Optional[dict] = None):
self.logger.debug("Spawning Motor")
Expand Down
21 changes: 15 additions & 6 deletions bapsf_motion/actors/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -314,19 +314,28 @@ def run(self, auto_run: bool = True, force_run: bool = True):
self._thread = threading.Thread(target=self._loop.run_forever)
self._thread.start()

def terminate(self, delay_loop_stop=False):
def terminate(
self,
delay_loop_stop: bool = False,
disconnect_signals: bool = False,
):
r"""
Stop the actor's `event loop`_\ . All actor tasks will be
cancelled, the connection to the motor will be shutdown, and
the event loop will be stopped.

Parameters
----------
delay_loop_stop: bool
If `True`, then do NOT stop the `event loop`_\ . In this
case it is assumed the calling functionality is managing
additional tasks in the event loop, and it is up to that
functionality to stop the loop. (DEFAULT: `False`)
delay_loop_stop: `bool`
(DEFAULT: `False`) If `True`, then do NOT stop the
`event loop`_\ . In this case it is assumed the calling
functionality is managing additional tasks in the event
loop, and it is up to that functionality to stop the loop.

disconnect_signals: `bool`
(DEFAULT: `False`) If `True`, then disconnect any signal
when terminating the actor. If `False`, then signals
are NOT disconnected, but are block.
"""
for task in list(self.tasks):
self.loop.call_soon_threadsafe(task.cancel)
Expand Down
13 changes: 10 additions & 3 deletions bapsf_motion/actors/drive_.py
Original file line number Diff line number Diff line change
Expand Up @@ -286,11 +286,18 @@ def position(self) -> u.Quantity:

return pos * self.axes[0].units

def terminate(self, delay_loop_stop=False):
def terminate(
self,
delay_loop_stop: bool = False,
disconnect_signals: bool = False,
):
for ax in self.axes:
ax.terminate(delay_loop_stop=True)
ax.terminate(delay_loop_stop=True, disconnect_signals=disconnect_signals)

super().terminate(delay_loop_stop=delay_loop_stop)
super().terminate(
delay_loop_stop=delay_loop_stop,
disconnect_signals=disconnect_signals,
)

def send_command(self, command: str, *args, axis: int | None = None):
"""
Expand Down
13 changes: 10 additions & 3 deletions bapsf_motion/actors/manager_.py
Original file line number Diff line number Diff line change
Expand Up @@ -346,10 +346,17 @@ def config(self) -> RunManagerConfig:

config.__doc__ = EventActor.config.__doc__

def terminate(self, delay_loop_stop=False):
def terminate(
self,
delay_loop_stop: bool = False,
disconnect_signals: bool = False,
):
for mg in self.mgs.values():
mg.terminate(delay_loop_stop=True)
super().terminate(delay_loop_stop=delay_loop_stop)
mg.terminate(delay_loop_stop=True, disconnect_signals=disconnect_signals)
super().terminate(
delay_loop_stop=delay_loop_stop,
disconnect_signals=disconnect_signals,
)

def _spawn_motion_group(self, config: Dict[str, Any]) -> MotionGroup:
return MotionGroup(
Expand Down
16 changes: 13 additions & 3 deletions bapsf_motion/actors/motion_group_.py
Original file line number Diff line number Diff line change
Expand Up @@ -864,10 +864,20 @@ def _spawn_transform(
)
return self._transform

def terminate(self, delay_loop_stop=False):
def terminate(
self,
delay_loop_stop: bool = False,
disconnect_signals: bool = True,
):
if self.drive is not None:
self.drive.terminate(delay_loop_stop=True)
super().terminate(delay_loop_stop=delay_loop_stop)
self.drive.terminate(
delay_loop_stop=True,
disconnect_signals=disconnect_signals,
)
super().terminate(
delay_loop_stop=delay_loop_stop,
disconnect_signals=disconnect_signals,
)

@property
def config(self) -> "MotionGroupConfig":
Expand Down
50 changes: 44 additions & 6 deletions bapsf_motion/actors/motor_.py
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,14 @@ def __init__(self):
self._movement_started = SimpleSignal()
self._status_changed = SimpleSignal()

self._signal_names = (
"connection_established",
"connection_lost",
"movement_finished",
"movement_started",
"status_changed",
)

@property
def connection_established(self) -> SimpleSignal:
"""
Expand Down Expand Up @@ -238,6 +246,28 @@ def status_changed(self) -> SimpleSignal:
`~Motor.status` is changes."""
return self._status_changed

def _get_signal(self, name: str) -> SimpleSignal:
return getattr(self, name)

def set_blocking(self, block: bool):
"""Block or unblock the all signals."""

for name in self._signal_names:
signal = self._get_signal(name)
signal.set_blocking(block)

def disconnect(self, func: Callable):
"""Disconnect the callback/handler ``func`` from all signals."""
for name in self._signal_names:
signal = getattr(self, name)
signal.disconnect(func)

def disconnect_all(self):
"""Disconnect all callbacks/handlers from all signals."""
for name in self._signal_names:
signal = getattr(self, name)
signal.disconnect_all()


class Motor(EventActor):
"""
Expand Down Expand Up @@ -767,6 +797,7 @@ def run(self, auto_run: bool = True, force_run: bool = True):
self._initialize_tasks()

super().run(auto_run=auto_run)
self.signals.set_blocking(False)

@property
def connected(self):
Expand Down Expand Up @@ -1888,19 +1919,26 @@ async def _heartbeat(self):
old_HR = heartrate
await asyncio.sleep(heartrate)

def terminate(self, delay_loop_stop=False):
def terminate(
self,
delay_loop_stop: bool = False,
disconnect_signals: bool = False,
):
self.logger.info("Terminating motor")

# disconnect all signals before terminating
self.signals.status_changed.disconnect_all()
self.signals.movement_started.disconnect_all()
self.signals.movement_finished.disconnect_all()
# handle signals
self.signals.set_blocking(True)
if disconnect_signals:
self.signals.disconnect_all()

if not self.terminated and self.connected:
self.stop()
self.disable()

super().terminate(delay_loop_stop=delay_loop_stop)
super().terminate(
delay_loop_stop=delay_loop_stop,
disconnect_signals=disconnect_signals,
)
self._heartbeat_task = None

try:
Expand Down
12 changes: 6 additions & 6 deletions bapsf_motion/gui/configure/configure_.py
Original file line number Diff line number Diff line change
Expand Up @@ -529,7 +529,7 @@ def rm(self, new_rm):
if not isinstance(new_rm, RunManager):
return
elif isinstance(self._rm, RunManager):
self._rm.terminate()
self._rm.terminate(disconnect_signals=True)

self._rm = new_rm

Expand All @@ -546,7 +546,7 @@ def _config_changed_handler(self):

def replace_rm(self, config):
if isinstance(self.rm, RunManager):
self.rm.terminate()
self.rm.terminate(disconnect_signals=True)

self.logger.info(f"Replacing the run manager with new config: {config}.")
_rm = RunManager(config=config, auto_run=True, build_mode=True)
Expand Down Expand Up @@ -600,7 +600,7 @@ def update_display_mg_list(self):
self.logger.info(f"Adding to MG List - {label}")
_icon = (
qta.icon(icon_name_dict["window-close"], color="red")
if mg.terminated
if mg.terminated or not mg.connected
else qta.icon(icon_name_dict["check-circle"], color="green")
) # type: QIcon
_item = QListWidgetItem(
Expand Down Expand Up @@ -631,7 +631,7 @@ def _motion_group_modify_existing(self):
mg = self.rm.mgs[key]

if not mg.terminated:
mg.terminate(delay_loop_stop=True)
mg.terminate(delay_loop_stop=True, disconnect_signals=True)

self._mg_being_modified = mg
self._spawn_mg_widget(mg)
Expand Down Expand Up @@ -753,7 +753,7 @@ def _spawn_mg_widget(self, mg: MotionGroup = None):
# terminate RunManager so we can avoid communication issue during
# MotionGroup configuration
if isinstance(self.rm, RunManager) and not self.rm.terminated:
self.rm.terminate()
self.rm.terminate(disconnect_signals=True)

self._mg_widget = MGWidget(
mg_config=config,
Expand Down Expand Up @@ -921,7 +921,7 @@ def closeEvent(self, event: "QCloseEvent") -> None:
self.configChanged.disconnect()

if isinstance(self.rm, RunManager) and not self.rm.terminated:
self.rm.terminate()
self.rm.terminate(disconnect_signals=True)
self.rm = None

if isinstance(self._mg_widget, MGWidget):
Expand Down
3 changes: 2 additions & 1 deletion bapsf_motion/gui/configure/controllers.py
Original file line number Diff line number Diff line change
Expand Up @@ -999,7 +999,6 @@ def unlink_motion_group(self):

acw.setVisible(visible)

# self.mg.terminate(delay_loop_stop=True)
self._mg = None
self._mspace_drive_polarity = None
self.setEnabled(False)
Expand Down Expand Up @@ -1034,6 +1033,7 @@ def enable_motion_buttons(self):
def _drive_connection_lost(self):
self.mg.drive.stop()
self.setEnabled(False)
self.driveStatusChanged.emit()

@Slot()
def _drive_connection_established(self):
Expand All @@ -1042,6 +1042,7 @@ def _drive_connection_established(self):

if self.mg.drive.connected:
self.setEnabled(True)
self.driveStatusChanged.emit()

@Slot(int)
def _drive_movement_started(self, axis_index):
Expand Down
Loading