0
0
mirror of https://github.com/bec-project/bec_widgets.git synced 2025-07-13 19:21:50 +02:00

refactor: getting motor limits and coordinates moved to MotorControl(QThread)

This commit is contained in:
wyzula-jan
2023-08-23 12:57:48 +02:00
parent 11aa15fefd
commit 349c06bcac
2 changed files with 109 additions and 113 deletions

View File

@ -260,7 +260,7 @@
<item row="2" column="3">
<widget class="QLabel" name="label_3">
<property name="text">
<string>- samx</string>
<string>+ samx</string>
</property>
</widget>
</item>

View File

@ -3,7 +3,7 @@ import os
import numpy as np
import pyqtgraph as pg
from PyQt5 import QtGui
from PyQt5.QtCore import QThread
from PyQt5.QtCore import QThread, pyqtSlot
from PyQt5.QtCore import pyqtSignal, Qt
from PyQt5.QtWidgets import QApplication, QWidget
from pyqtgraph.Qt import QtWidgets, uic
@ -12,9 +12,6 @@ from bec_lib.core import MessageEndpoints, BECMessage
class MotorApp(QWidget):
motor_position = pyqtSignal(float) # TODO hook to motor position update
motor_update = pyqtSignal()
motor_position_xy = pyqtSignal(list)
coordinates_updated = pyqtSignal(float, float)
def __init__(self):
@ -22,34 +19,51 @@ class MotorApp(QWidget):
current_path = os.path.dirname(__file__)
uic.loadUi(os.path.join(current_path, "motor_controller.ui"), self)
# UI
self.start_x, self.start_y = self.get_xy()
self.current_x, self.current_y = self.get_xy()
self.limit_x, self.limit_y = None, None
self.limit_x, self.limit_y = self.get_motor_limits(dev.samx), self.get_motor_limits(
dev.samy
)
# QThread for motor movement + signals
self.motor_thread = MotorControl()
self.motor_thread.limits_retrieved.connect(self.update_limits)
# UI
self.init_ui()
# Initialize current coordinates with the provided initial coordinates
self.motor_thread.retrieve_motor_limits(dev.samx, dev.samy)
print(f"Init limits: samx:{self.limit_x}, samy:{self.limit_y}")
self.label_status.setText(
f"Motor position: ({dev.samx.position():.2f}, {dev.samy.position():.2f})"
)
) # TODO has to be change to get positions from thread not directly from device
self.motor_thread = MotorMovement()
@pyqtSlot(list, list)
def update_limits(self, x_limits: list, y_limits: list) -> None:
self.limit_x = x_limits
self.limit_y = y_limits
self.spinBox_x_min.setValue(self.limit_x[0])
self.spinBox_x_max.setValue(self.limit_x[1])
self.spinBox_y_min.setValue(self.limit_y[0])
self.spinBox_y_max.setValue(self.limit_y[1])
self.init_ui()
for spinBox in (
self.spinBox_x_min,
self.spinBox_x_max,
self.spinBox_y_min,
self.spinBox_y_max,
):
spinBox.setStyleSheet("")
def move_motor_absolute(self, x, y):
# x = self.spinBox_absolute_x.value()
# y = self.spinBox_absolute_y.value()
self.init_motor_map() # reinitialize the map with the new limits
def move_motor_absolute(self, x: float, y: float) -> None:
target_coordinates = (x, y)
self.motor_thread.move_to_coordinates(target_coordinates)
def move_motor_relative(self, motor, value: float):
def move_motor_relative(self, motor, value: float) -> None:
self.motor_thread.move_relative(motor, value)
def init_ui(self):
def init_ui(self) -> None:
"""Setup all ui elements"""
##########################
@ -80,6 +94,7 @@ class MotorApp(QWidget):
lambda: self.move_motor_relative(dev.samy, -self.spinBox_step.value())
)
# Switch between key shortcuts active
self.checkBox_enableArrows.stateChanged.connect(self.update_arrow_key_shortcuts)
self.update_arrow_key_shortcuts()
@ -92,12 +107,6 @@ class MotorApp(QWidget):
# SpinBoxes - Motor Limits #TODO make spinboxes own limits updated, currently is [-1000, 1000]
# display initial limits
self.spinBox_x_min.setValue(self.limit_x[0])
self.spinBox_x_max.setValue(self.limit_x[1])
self.spinBox_y_min.setValue(self.limit_y[0])
self.spinBox_y_max.setValue(self.limit_y[1])
# SpinBoxes change color to yellow before updated, limits are updated with update button
self.spinBox_x_min.valueChanged.connect(lambda: self.param_changed(self.spinBox_x_min))
self.spinBox_x_max.valueChanged.connect(lambda: self.param_changed(self.spinBox_x_max))
@ -111,28 +120,19 @@ class MotorApp(QWidget):
)
)
# Map
self.motor_position.connect(lambda x: self.update_image_map(*self.get_xy()))
self.motor_update.connect(lambda: self.update_image_map(*self.get_xy()))
self.motor_position.connect(lambda x: print(f"motor position updated: {x}"))
# self.motor_thread.coordinates_updated.connect(
# lambda: self.label_status.setText(
# f"Motor position: ({dev.samx.position():.2f}, {dev.samy.position():.2f})"
# )
# )
# TODO map with floats as well -> or decide system for higher precision
self.motor_thread.coordinates_updated.connect(
lambda x, y: self.update_image_map(int(x), int(y))
)
# Coordinates table
self.generate_table_coordinate(
self.tableWidget_coordinates, self.get_xy(), tag="Initial", precision=0
self.tableWidget_coordinates,
self.motor_thread.retrieve_coordinates(),
tag="Initial",
precision=0,
)
self.init_motor_map()
def init_motor_map(self):
# Get motor limits
limit_x_min, limit_x_max = self.get_motor_limits(dev.samx)
@ -152,8 +152,7 @@ class MotorApp(QWidget):
)
# Set the initial position on the map
x, y = self.get_xy()
self.prev_x, self.prev_y = x, y
x, y = self.motor_thread.retrieve_coordinates()
self.update_image_map(x, y)
# Translate and scale the image item to match the motor coordinates
@ -189,58 +188,10 @@ class MotorApp(QWidget):
return low_limit, high_limit
def update_motor_limits(
self, motor, low_limit=None, high_limit=None
): # TODO limits cannot be smaller that the current location of motor
# Get current limits
current_low_limit, current_high_limit = motor.limits[0], motor.limits[1]
# Check if the low limit has changed and is not None
if low_limit is not None and low_limit != current_low_limit:
motor.low_limit = low_limit
# Check if the high limit has changed and is not None
if high_limit is not None and high_limit != current_high_limit:
motor.high_limit = high_limit
# self.init_motor_map() # reinitialize the map with the new limits
def update_all_motor_limits(self, x_limit: list = None, y_limit: list = None) -> None:
# check if current motor position if within the new limits
current_position = self.get_xy()
if x_limit is not None:
if current_position[0] < x_limit[0] or current_position[0] > x_limit[1]:
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])
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])
for spinBox in (
self.spinBox_x_min,
self.spinBox_x_max,
self.spinBox_y_min,
self.spinBox_y_max,
):
spinBox.setStyleSheet("")
self.init_motor_map() # reinitialize the map with the new limits
def get_xy(self): # TODO decide if useful
"""Get current motor position"""
x = dev.samx.position()
y = dev.samy.position()
return x, y
@staticmethod
def param_changed(ui_element):
ui_element.setStyleSheet("background-color: #FFA700;")
def update_all_motor_limits(
self, x_limit: list = None, y_limit: list = None
) -> None: # TODO will be moved to thread
self.motor_thread.update_all_motor_limits(x_limit=x_limit, y_limit=y_limit)
def update_arrow_key_shortcuts(self):
if self.checkBox_enableArrows.isChecked():
@ -257,7 +208,7 @@ class MotorApp(QWidget):
self.toolButton_down.setShortcut("")
def generate_table_coordinate(
self, table: QtWidgets.QTableWidget, coordinates: list, tag: str = None, precision: int = 0
self, table: QtWidgets.QTableWidget, coordinates: tuple, tag: str = None, precision: int = 0
) -> None:
current_row_count = table.rowCount()
@ -288,20 +239,26 @@ class MotorApp(QWidget):
)
table.resizeColumnsToContents()
@staticmethod
def param_changed(ui_element):
ui_element.setStyleSheet("background-color: #FFA700;")
class MotorMovement(QThread):
class MotorControl(QThread):
coordinates_updated = pyqtSignal(float, float) # Signal to emit current coordinates
# progress_updated = pyqtSignal(int) # Signal to emit progress percentage
limits_retrieved = pyqtSignal(list, list) # Signal to emit current limits (samx, samy)
# progress_updated = pyqtSignal(int) #TODO Signal to emit progress percentage
def __init__(self, parent=None):
super().__init__(parent)
# self.initial_coordinates = None
self.target_coordinates = None
self.running = False
# Initialize current coordinates with the provided initial coordinates
self.current_x, self.current_y = None, None
(
self.current_x,
self.current_y,
) = self.get_coordinates() # TODO maybe not needed, coordinates always stored in redis
# Create consumer for samx and samy
self.samx_consumer = client.connector.consumer(
@ -319,7 +276,56 @@ class MotorMovement(QThread):
self.samx_consumer.start()
self.samy_consumer.start()
self.get_xy()
def get_coordinates(self) -> tuple:
# TODO decide if the get_coordinates and retrieve coordinates makes sense
# TODO or move also to signal/slot as in the case of limits
# TODO or move just to redis CB function
"""Get current motor position"""
x = dev.samx.position()
y = dev.samy.position()
return x, y
def retrieve_coordinates(self) -> tuple:
"""Get current motor position for export to main app"""
return self.current_x, self.current_y
def get_motor_limits(self, motor) -> list:
"""Get the limits of a motor"""
return motor.limits
def retrieve_motor_limits(self, motor_x, motor_y):
limit_x = self.get_motor_limits(motor_x)
limit_y = self.get_motor_limits(motor_y)
self.limits_retrieved.emit(limit_x, limit_y)
def update_motor_limits(self, motor, low_limit=None, high_limit=None) -> None:
current_low_limit, current_high_limit = self.get_motor_limits(motor)
# Check if the low limit has changed and is not None
if low_limit is not None and low_limit != current_low_limit:
motor.low_limit = low_limit
# Check if the high limit has changed and is not None
if high_limit is not None and high_limit != current_high_limit:
motor.high_limit = high_limit
def update_all_motor_limits(self, x_limit: list = None, y_limit: list = None) -> None:
# TODO can be generalized to any motor not just samx samy
current_position = self.get_coordinates()
if x_limit is not None:
if current_position[0] < x_limit[0] or current_position[0] > x_limit[1]:
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])
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.retrieve_motor_limits(dev.samx, dev.samy)
def move_to_coordinates(self, target_coordinates: tuple):
self.action = "move_to_coordinates"
@ -341,14 +347,8 @@ class MotorMovement(QThread):
def set_target_coordinates(self, target_coordinates: tuple) -> None:
self.target_coordinates = target_coordinates
def get_xy(self) -> tuple:
"""Get current motor position"""
self.current_x = dev.samx.position()
self.current_y = dev.samy.position()
def move_motor_coordinate(self):
def move_motor_coordinate(self) -> None:
"""Move the motor to the specified coordinates"""
self.get_xy()
scans.mv(
dev.samx,
self.target_coordinates[0],
@ -358,25 +358,21 @@ class MotorMovement(QThread):
)
def move_motor_relative(self, motor, value: float) -> None:
self.get_xy()
scans.mv(motor, value, relative=True)
# TODO decide if one or multiple callback functions
@staticmethod
def _device_status_callback_samx(msg, *, parent, **_kwargs) -> None:
deviceMSG = BECMessage.DeviceMessage.loads(msg.value)
parent.current_x = deviceMSG.content["signals"]["samx"]["value"]
print(f"samx moving: {parent.current_x,parent.current_y}")
# y = parent.current_y
parent.coordinates_updated.emit(parent.current_x, parent.current_y)
@staticmethod
def _device_status_callback_samy(msg, *, parent, **_kwargs) -> None:
deviceMSG = BECMessage.DeviceMessage.loads(msg.value)
parent.current_y = deviceMSG.content["signals"]["samy"][
"value"
] # TODO can be move to parent instance
parent.current_y = deviceMSG.content["signals"]["samy"]["value"]
print(f"samy moving: {parent.current_x,parent.current_y}")
# x = parent.current_y
parent.coordinates_updated.emit(parent.current_x, parent.current_y)