diff --git a/bec_widgets/examples/motor_movement/motor_controller.ui b/bec_widgets/examples/motor_movement/motor_controller.ui index 5d0d8143..fbb122ff 100644 --- a/bec_widgets/examples/motor_movement/motor_controller.ui +++ b/bec_widgets/examples/motor_movement/motor_controller.ui @@ -6,8 +6,8 @@ 0 0 - 1115 - 506 + 1120 + 547 @@ -43,6 +43,12 @@ + + + + + + @@ -50,11 +56,12 @@ - - - - - + + + + Connect Motors + + @@ -208,7 +215,7 @@ - + Move Absolute @@ -271,7 +278,7 @@ 0 - + Coordinates @@ -307,7 +314,7 @@ - + Settings @@ -510,7 +517,7 @@ - + Queue diff --git a/bec_widgets/examples/motor_movement/motor_example.py b/bec_widgets/examples/motor_movement/motor_example.py index 5d32d97d..cba06a52 100644 --- a/bec_widgets/examples/motor_movement/motor_example.py +++ b/bec_widgets/examples/motor_movement/motor_example.py @@ -31,6 +31,10 @@ class MotorApp(QWidget): current_path = os.path.dirname(__file__) uic.loadUi(os.path.join(current_path, "motor_controller.ui"), self) + # Motor Control Thread + self.motor_thread = MotorControl() + + self.motor_x, self.motor_y = None, None # dev.samx, dev.samy self.limit_x, self.limit_y = None, None # Coordinates tracking @@ -39,21 +43,44 @@ class MotorApp(QWidget): self.num_dim_points = 15 # Number of points to dim gradually # QThread for motor movement + signals - self.motor_thread = MotorControl() + self.motor_thread.motors_loaded.connect(self.get_available_motors) + self.motor_thread.motors_selected.connect(self.get_selected_motors) self.motor_thread.limits_retrieved.connect(self.update_limits) # UI self.init_ui() self.tag_N = 1 # position label for saved coordinates - # Initialize current coordinates with the provided initial coordinates - self.motor_thread.retrieve_motor_limits(dev.samx, dev.samy) + # Get all motors available + self.motor_thread.retrieve_all_motors() - print(f"Init limits: samx:{self.limit_x}, samy:{self.limit_y}") + # Initialize current coordinates with the provided initial coordinates + # self.motor_thread.retrieve_motor_limits(self.motor_x, self.motor_y) #TODO move after connection of motors + + # print(f"Init limits: samx:{self.limit_x}, samy:{self.limit_y}") # TODO get to motor connect # Initialize the image map + # self.init_motor_map() + + def connect_motor(self, motor_x_name: str, motor_y_name: str): + self.motor_thread.connect_motors(motor_x_name, motor_y_name) + self.motor_thread.retrieve_motor_limits(self.motor_x, self.motor_y) self.init_motor_map() + self.motorControl.setEnabled(True) + self.motorControl_absolute.setEnabled(True) + self.tabWidget_tables.setTabEnabled(1, True) + + @pyqtSlot(object, object) + def get_selected_motors(self, motor_x, motor_y): + self.motor_x, self.motor_y = motor_x, motor_y + + @pyqtSlot(list, list) + def get_available_motors(self, motors_x, motors_y): + self.comboBox_motor_x.addItems(motors_x) + self.comboBox_motor_y.addItems(motors_y) + print(f"got motors {motors_x} and {motors_y}") + @pyqtSlot(list, list) def update_limits(self, x_limits: list, y_limits: list) -> None: self.limit_x = x_limits @@ -101,9 +128,12 @@ class MotorApp(QWidget): # 2D Plot ########################## + # self.label_coorditanes = self.glw.addLabel( + # f"Motor position: ({dev.samx.position():.2f}, {dev.samy.position():.2f})", row=0, col=0 + # ) #TODO remove hardcoded samx and samy self.label_coorditanes = self.glw.addLabel( - f"Motor position: ({dev.samx.position():.2f}, {dev.samy.position():.2f})", row=0, col=0 - ) + f"Motor position: (X, Y)", row=0, col=0 + ) # TODO remove hardcoded samx and samy self.plot_map = self.glw.addPlot(row=1, col=0) self.limit_map = pg.ImageItem() self.plot_map.addItem(self.limit_map) @@ -125,16 +155,16 @@ class MotorApp(QWidget): ########################## self.toolButton_right.clicked.connect( - lambda: self.move_motor_relative(dev.samx, self.spinBox_step.value()) + lambda: self.move_motor_relative(self.motor_x, self.spinBox_step.value()) ) self.toolButton_left.clicked.connect( - lambda: self.move_motor_relative(dev.samx, -self.spinBox_step.value()) + lambda: self.move_motor_relative(self.motor_x, -self.spinBox_step.value()) ) self.toolButton_up.clicked.connect( - lambda: self.move_motor_relative(dev.samy, self.spinBox_step.value()) + lambda: self.move_motor_relative(self.motor_y, self.spinBox_step.value()) ) self.toolButton_down.clicked.connect( - lambda: self.move_motor_relative(dev.samy, -self.spinBox_step.value()) + lambda: self.move_motor_relative(self.motor_y, -self.spinBox_step.value()) ) # Switch between key shortcuts active @@ -188,10 +218,23 @@ class MotorApp(QWidget): precision=0, ) + # Motor connections + self.pushButton_connecMotors.clicked.connect( + lambda: self.connect_motor( + self.comboBox_motor_x.currentText(), self.comboBox_motor_y.currentText() + ) + ) + + # Check if there are any motors connected + if self.motor_x or self.motor_y is None: + self.motorControl.setEnabled(False) + self.motorControl_absolute.setEnabled(False) + self.tabWidget_tables.setTabEnabled(1, False) + def init_motor_map(self): # Get motor limits - limit_x_min, limit_x_max = self.motor_thread.get_motor_limits(dev.samx) - limit_y_min, limit_y_max = self.motor_thread.get_motor_limits(dev.samy) + limit_x_min, limit_x_max = self.motor_thread.get_motor_limits(self.motor_x) + limit_y_min, limit_y_max = self.motor_thread.get_motor_limits(self.motor_y) self.offset_x = limit_x_min self.offset_y = limit_y_min @@ -241,9 +284,7 @@ class MotorApp(QWidget): self.motor_map.setData(pos=self.motor_positions, brush=self.brushes) - def update_all_motor_limits( - self, x_limit: list = None, y_limit: list = None - ) -> None: # TODO will be moved to thread + def update_all_motor_limits(self, x_limit: list = None, y_limit: list = None) -> None: self.motor_thread.update_all_motor_limits(x_limit=x_limit, y_limit=y_limit) def update_arrow_key_shortcuts(self): @@ -311,6 +352,8 @@ class MotorControl(QThread): coordinates_updated = pyqtSignal(float, float) # Signal to emit current coordinates limits_retrieved = pyqtSignal(list, list) # Signal to emit current limits (samx, samy) move_finished = pyqtSignal() # Signal to emit when the move is finished + motors_loaded = pyqtSignal(list, list) # Signal to emit when the motors are loaded + motors_selected = pyqtSignal(object, object) # Signal to emit when the motors are selected # progress_updated = pyqtSignal(int) #TODO Signal to emit progress percentage def __init__( @@ -319,21 +362,50 @@ class MotorControl(QThread): parent=None, ): super().__init__(parent) + + self.motor_x, self.motor_y = None, None + self.current_x, self.current_y = None, None + + self.motors_consumer = None + + # Get all available motors in the client + self.all_motors = self.get_all_motors() + self.all_motors_names = self.get_all_motors_names() + self.retrieve_all_motors() # send motor list to GUI + + self.connect_motors("samx", "samy") + self.target_coordinates = None - self.running = False - self.active_devices = active_devices + self.motor_x_debug = dev.samx + # self.all_motors = self.get_all_motors() + # self.all_motors_names = self.get_all_motors_names() - # Initialize current coordinates with the provided initial coordinates - ( - self.current_x, - self.current_y, - ) = self.get_coordinates() # TODO maybe not needed, coordinates always stored in redis + # find index of samx in self.all_motors_names + # self.motor_x_index = self.all_motors_names.index(self.active_devices[0]) + + print(f"Motor debug: {self.motor_x_debug}") + + def motor_by_string(self, motor_x_name: str, motor_y_name: str) -> tuple: + motor_x_index = self.all_motors_names.index(motor_x_name) + motor_y_index = self.all_motors_names.index(motor_y_name) + + motor_x = self.all_motors[motor_x_index] + motor_y = self.all_motors[motor_y_index] + return motor_x, motor_y + + def connect_motors(self, motor_x_name: str, motor_y_name: str) -> None: + self.motor_x, self.motor_y = self.motor_by_string(motor_x_name, motor_y_name) + + (self.current_x, self.current_y) = self.get_coordinates() + + if self.motors_consumer is not None: + self.motors_consumer.shutdown() self.motors_consumer = client.connector.consumer( topics=[ - MessageEndpoints.device_readback(self.active_devices[0]), - MessageEndpoints.device_readback(self.active_devices[1]), + MessageEndpoints.device_readback(self.motor_x.name), + MessageEndpoints.device_readback(self.motor_y.name), ], cb=self._device_status_callback_motors, parent=self, @@ -341,10 +413,29 @@ class MotorControl(QThread): self.motors_consumer.start() + self.motors_selected.emit(self.motor_x, self.motor_y) + + def get_all_motors(self) -> list: + all_motors = client.device_manager.devices.acquisition_group("motor") + return all_motors + + def get_all_motors_names(self) -> list: + all_motors = client.device_manager.devices.acquisition_group("motor") + all_motors_names = [motor.name for motor in all_motors] + return all_motors_names + + def retrieve_all_motors(self): + self.all_motors = self.get_all_motors() + self.all_motors_names = self.get_all_motors_names() + self.motors_loaded.emit(self.all_motors_names, self.all_motors_names) + print("motors sent to GUI") + + return self.all_motors, self.all_motors_names + def get_coordinates(self) -> tuple: """Get current motor position""" - x = dev.samx.read(cached=True)["value"] # TODO remove hardcoded samx and samy - y = dev.samx.read(cached=True)["value"] + x = self.motor_x.read(cached=True)["value"] # TODO remove hardcoded samx and samy + y = self.motor_y.read(cached=True)["value"] return x, y def retrieve_coordinates(self) -> tuple: @@ -380,16 +471,16 @@ class MotorControl(QThread): raise ValueError("Current motor position is outside the new limits (X)") else: self.update_motor_limits( - dev.samx, low_limit=x_limit[0], high_limit=x_limit[1] + self.motor_x, low_limit=x_limit[0], high_limit=x_limit[1] ) # TODO Remove hardcoded samx and samy if y_limit is not None: if current_position[1] < y_limit[0] or current_position[1] > y_limit[1]: raise ValueError("Current motor position is outside the new limits (Y)") else: - self.update_motor_limits(dev.samy, low_limit=y_limit[0], high_limit=y_limit[1]) + self.update_motor_limits(self.motor_y, low_limit=y_limit[0], high_limit=y_limit[1]) - self.retrieve_motor_limits(dev.samx, dev.samy) + self.retrieve_motor_limits(self.motor_x, self.motor_y) def move_to_coordinates(self, target_coordinates: tuple): self.action = "move_to_coordinates" @@ -414,9 +505,9 @@ class MotorControl(QThread): def move_motor_coordinate(self) -> None: """Move the motor to the specified coordinates""" status = scans.mv( - dev.samx, + self.motor_x, self.target_coordinates[0], - dev.samy, + self.motor_y, self.target_coordinates[1], relative=False, ) @@ -437,10 +528,10 @@ class MotorControl(QThread): @staticmethod def _device_status_callback_motors(msg, *, parent, **_kwargs) -> None: deviceMSG = BECMessage.DeviceMessage.loads(msg.value) - if parent.active_devices[0] in deviceMSG.content["signals"]: - parent.current_x = deviceMSG.content["signals"][parent.active_devices[0]]["value"] - elif parent.active_devices[1] in deviceMSG.content["signals"]: - parent.current_y = deviceMSG.content["signals"][parent.active_devices[1]]["value"] + if parent.motor_x.name in deviceMSG.content["signals"]: + parent.current_x = deviceMSG.content["signals"][parent.motor_x.name]["value"] + elif parent.motor_y.name in deviceMSG.content["signals"]: + parent.current_y = deviceMSG.content["signals"][parent.motor_y.name]["value"] parent.coordinates_updated.emit(parent.current_x, parent.current_y)