mirror of
https://github.com/bec-project/bec_widgets.git
synced 2025-07-14 03:31:50 +02:00
refactor: motor movement as a QThread
This commit is contained in:
@ -60,17 +60,27 @@
|
|||||||
<property name="title">
|
<property name="title">
|
||||||
<string>General</string>
|
<string>General</string>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
<layout class="QGridLayout" name="gridLayout_4">
|
||||||
<item>
|
<item row="0" column="0">
|
||||||
<widget class="QLabel" name="label_5">
|
<widget class="QLabel" name="label_5">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Decimal precision</string>
|
<string>Decimal precision</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item row="0" column="1">
|
||||||
<widget class="QSpinBox" name="spinBox"/>
|
<widget class="QSpinBox" name="spinBox"/>
|
||||||
</item>
|
</item>
|
||||||
|
<item row="1" column="0">
|
||||||
|
<widget class="QLabel" name="label_8">
|
||||||
|
<property name="text">
|
||||||
|
<string>Speed</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="1">
|
||||||
|
<widget class="QDoubleSpinBox" name="doubleSpinBox_speed"/>
|
||||||
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
@ -3,15 +3,19 @@ import os
|
|||||||
import numpy as np
|
import numpy as np
|
||||||
import pyqtgraph as pg
|
import pyqtgraph as pg
|
||||||
from PyQt5 import QtGui
|
from PyQt5 import QtGui
|
||||||
|
from PyQt5.QtCore import QThread
|
||||||
from PyQt5.QtCore import pyqtSignal, Qt
|
from PyQt5.QtCore import pyqtSignal, Qt
|
||||||
from PyQt5.QtWidgets import QApplication, QWidget
|
from PyQt5.QtWidgets import QApplication, QWidget
|
||||||
from pyqtgraph.Qt import QtWidgets, uic
|
from pyqtgraph.Qt import QtWidgets, uic
|
||||||
|
|
||||||
|
from bec_lib.core import MessageEndpoints, BECMessage
|
||||||
|
|
||||||
|
|
||||||
class MotorApp(QWidget):
|
class MotorApp(QWidget):
|
||||||
motor_position = pyqtSignal(float) # TODO hook to motor position update
|
motor_position = pyqtSignal(float) # TODO hook to motor position update
|
||||||
motor_update = pyqtSignal()
|
motor_update = pyqtSignal()
|
||||||
motor_position_xy = pyqtSignal(list)
|
motor_position_xy = pyqtSignal(list)
|
||||||
|
coordinates_updated = pyqtSignal(float, float)
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
@ -20,6 +24,7 @@ class MotorApp(QWidget):
|
|||||||
|
|
||||||
# UI
|
# UI
|
||||||
self.start_x, self.start_y = self.get_xy()
|
self.start_x, self.start_y = self.get_xy()
|
||||||
|
self.current_x, self.current_y = self.get_xy()
|
||||||
self.limit_x, self.limit_y = self.get_motor_limits(dev.samx), self.get_motor_limits(
|
self.limit_x, self.limit_y = self.get_motor_limits(dev.samx), self.get_motor_limits(
|
||||||
dev.samy
|
dev.samy
|
||||||
)
|
)
|
||||||
@ -29,8 +34,39 @@ class MotorApp(QWidget):
|
|||||||
self.label_status.setText(
|
self.label_status.setText(
|
||||||
f"Motor position: ({dev.samx.position():.2f}, {dev.samy.position():.2f})"
|
f"Motor position: ({dev.samx.position():.2f}, {dev.samy.position():.2f})"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# self.samx_consumer = client.connector.consumer(topics=[MessageEndpoints.device_readback('samx'),
|
||||||
|
# MessageEndpoints.device_readback('samy')],
|
||||||
|
# cb=self._device_status_callback,parent=self)
|
||||||
|
# self.samx_consumer.start()
|
||||||
|
|
||||||
|
# self.samx_consumer = client.connector.consumer(
|
||||||
|
# topics=[MessageEndpoints.device_readback("samx")],
|
||||||
|
# cb=self._device_status_callback_samx,
|
||||||
|
# parent=self,
|
||||||
|
# )
|
||||||
|
#
|
||||||
|
# self.samy_consumer = client.connector.consumer(
|
||||||
|
# topics=[MessageEndpoints.device_readback("samy")],
|
||||||
|
# cb=self._device_status_callback_samy,
|
||||||
|
# parent=self,
|
||||||
|
# )
|
||||||
|
# self.samx_consumer.start()
|
||||||
|
# self.samy_consumer.start()
|
||||||
|
|
||||||
|
self.motor_thread = MotorMovement()
|
||||||
|
|
||||||
self.init_ui()
|
self.init_ui()
|
||||||
|
|
||||||
|
def on_move_to_coordinates_button_clicked(self):
|
||||||
|
x = self.spinBox_absolute_x.value()
|
||||||
|
y = self.spinBox_absolute_y.value()
|
||||||
|
target_coordinates = (x, y)
|
||||||
|
self.motor_thread.move_to_coordinates(target_coordinates)
|
||||||
|
|
||||||
|
def on_move_relative_button_clicked(self, motor, value: float):
|
||||||
|
self.motor_thread.move_relative(motor, value)
|
||||||
|
|
||||||
def init_ui(self):
|
def init_ui(self):
|
||||||
"""Setup all ui elements"""
|
"""Setup all ui elements"""
|
||||||
##########################
|
##########################
|
||||||
@ -56,17 +92,30 @@ class MotorApp(QWidget):
|
|||||||
##########################
|
##########################
|
||||||
|
|
||||||
# Buttons - Motor Movement
|
# Buttons - Motor Movement
|
||||||
|
# self.toolButton_right.clicked.connect(
|
||||||
|
# lambda: self.move_motor(dev.samx, self.spinBox_step.value())
|
||||||
|
# )
|
||||||
|
# self.toolButton_left.clicked.connect(
|
||||||
|
# lambda: self.move_motor(dev.samx, -self.spinBox_step.value())
|
||||||
|
# )
|
||||||
|
# self.toolButton_up.clicked.connect(
|
||||||
|
# lambda: self.move_motor(dev.samy, self.spinBox_step.value())
|
||||||
|
# )
|
||||||
|
# self.toolButton_down.clicked.connect(
|
||||||
|
# lambda: self.move_motor(dev.samy, -self.spinBox_step.value())
|
||||||
|
# )
|
||||||
|
|
||||||
self.toolButton_right.clicked.connect(
|
self.toolButton_right.clicked.connect(
|
||||||
lambda: self.move_motor(dev.samx, self.spinBox_step.value())
|
lambda: self.on_move_relative_button_clicked(dev.samx, self.spinBox_step.value())
|
||||||
)
|
)
|
||||||
self.toolButton_left.clicked.connect(
|
self.toolButton_left.clicked.connect(
|
||||||
lambda: self.move_motor(dev.samx, -self.spinBox_step.value())
|
lambda: self.on_move_relative_button_clicked(dev.samx, -self.spinBox_step.value())
|
||||||
)
|
)
|
||||||
self.toolButton_up.clicked.connect(
|
self.toolButton_up.clicked.connect(
|
||||||
lambda: self.move_motor(dev.samy, self.spinBox_step.value())
|
lambda: self.on_move_relative_button_clicked(dev.samy, self.spinBox_step.value())
|
||||||
)
|
)
|
||||||
self.toolButton_down.clicked.connect(
|
self.toolButton_down.clicked.connect(
|
||||||
lambda: self.move_motor(dev.samy, -self.spinBox_step.value())
|
lambda: self.on_move_relative_button_clicked(dev.samy, -self.spinBox_step.value())
|
||||||
)
|
)
|
||||||
|
|
||||||
self.checkBox_enableArrows.stateChanged.connect(self.update_arrow_key_shortcuts)
|
self.checkBox_enableArrows.stateChanged.connect(self.update_arrow_key_shortcuts)
|
||||||
@ -80,11 +129,6 @@ class MotorApp(QWidget):
|
|||||||
)
|
)
|
||||||
|
|
||||||
# SpinBoxes - Motor Limits #TODO make spinboxes own limits updated, currently is [-1000, 1000]
|
# SpinBoxes - Motor Limits #TODO make spinboxes own limits updated, currently is [-1000, 1000]
|
||||||
# manually set limits before readout #TODO will be removed when spinboxes have own limits
|
|
||||||
# dev.samx.low_limit = -100
|
|
||||||
# dev.samx.high_limit = 100
|
|
||||||
# dev.samy.low_limit = -100
|
|
||||||
# dev.samy.high_limit = 100
|
|
||||||
|
|
||||||
# display initial limits
|
# display initial limits
|
||||||
self.spinBox_x_min.setValue(self.limit_x[0])
|
self.spinBox_x_min.setValue(self.limit_x[0])
|
||||||
@ -110,10 +154,14 @@ class MotorApp(QWidget):
|
|||||||
self.motor_update.connect(lambda: 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_position.connect(lambda x: print(f"motor position updated: {x}"))
|
||||||
self.motor_update.connect(
|
# self.motor_thread.coordinates_updated.connect(
|
||||||
lambda: self.label_status.setText(
|
# lambda: self.label_status.setText(
|
||||||
f"Motor position: ({dev.samx.position():.2f}, {dev.samy.position():.2f})"
|
# f"Motor position: ({dev.samx.position():.2f}, {dev.samy.position():.2f})"
|
||||||
)
|
# )
|
||||||
|
# )
|
||||||
|
|
||||||
|
self.motor_thread.coordinates_updated.connect(
|
||||||
|
lambda x, y: self.update_image_map(int(x), int(y))
|
||||||
)
|
)
|
||||||
|
|
||||||
# Coordinates table
|
# Coordinates table
|
||||||
@ -157,10 +205,10 @@ class MotorApp(QWidget):
|
|||||||
"""Update the image map with the new motor position"""
|
"""Update the image map with the new motor position"""
|
||||||
|
|
||||||
# Define the dimming factor
|
# Define the dimming factor
|
||||||
dimming_factor = 0.80
|
dimming_factor = 0.95
|
||||||
|
|
||||||
# Apply the dimming factor only to pixels above the background value
|
# Apply the dimming factor only to pixels above the background value
|
||||||
self.image_map_data[self.image_map_data > self.background_value] *= dimming_factor
|
self.image_map_data[self.image_map_data > 50] *= dimming_factor
|
||||||
|
|
||||||
# Mapping of motor coordinates to pixel coordinates
|
# Mapping of motor coordinates to pixel coordinates
|
||||||
pixel_x = int(x - self.offset_x)
|
pixel_x = int(x - self.offset_x)
|
||||||
@ -223,29 +271,37 @@ class MotorApp(QWidget):
|
|||||||
self.init_motor_map() # reinitialize the map with the new limits
|
self.init_motor_map() # reinitialize the map with the new limits
|
||||||
|
|
||||||
def move_motor(self, motor, value: float) -> None:
|
def move_motor(self, motor, value: float) -> None:
|
||||||
try:
|
self.current_x, self.current_y = self.get_xy()
|
||||||
status = scans.mv(motor, value, relative=True)
|
scans.mv(motor, value, relative=True)
|
||||||
|
|
||||||
while status.status not in ["COMPLETED", "STOPPED"]:
|
# def move_motor(self, motor, value: float) -> None:
|
||||||
print(status.status)
|
# # self.current_x, self.current_y = self.get_xy()
|
||||||
motor_position = motor.position()
|
# scans.mv(motor, value, relative=True)
|
||||||
self.motor_position.emit(motor_position)
|
# try:
|
||||||
|
# status = scans.mv(motor, value, relative=True)
|
||||||
|
#
|
||||||
|
# # while status.status not in ["COMPLETED", "STOPPED"]:
|
||||||
|
# # print(status.status)
|
||||||
|
# # print(f"motor read{motor.read(cached=True)}")
|
||||||
|
# # motor_position = motor.position()
|
||||||
|
# # self.motor_position.emit(motor_position)
|
||||||
|
#
|
||||||
|
# # return status
|
||||||
|
# except Exception as e:
|
||||||
|
# print(e)
|
||||||
|
|
||||||
# return status
|
# motor_position = motor.position()
|
||||||
except Exception as e:
|
|
||||||
print(e)
|
|
||||||
|
|
||||||
motor_position = motor.position()
|
# self.motor_position.emit(motor_position)
|
||||||
|
# self.motor_update.emit()
|
||||||
self.motor_position.emit(motor_position)
|
|
||||||
self.motor_update.emit()
|
|
||||||
|
|
||||||
def move_motor_coordinates(self, coor: list) -> None:
|
def move_motor_coordinates(self, coor: list) -> None:
|
||||||
|
scans.mv(dev.samx, coor[0], dev.samy, coor[1], relative=False)
|
||||||
try:
|
try:
|
||||||
status = scans.mv(dev.samx, coor[0], dev.samy, coor[1], relative=False)
|
status = scans.mv(dev.samx, coor[0], dev.samy, coor[1], relative=False)
|
||||||
|
|
||||||
while status.status not in ["COMPLETED", "STOPPED"]:
|
# while status.status not in ["COMPLETED", "STOPPED"]:
|
||||||
print(status.status)
|
# print(status.status)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(e)
|
print(e)
|
||||||
@ -313,19 +369,137 @@ class MotorApp(QWidget):
|
|||||||
|
|
||||||
table.resizeColumnsToContents()
|
table.resizeColumnsToContents()
|
||||||
|
|
||||||
|
# @staticmethod
|
||||||
|
# def _device_status_callback(msg, *, parent, **_kwargs) -> None:
|
||||||
|
# deviceMSG = BECMessage.DeviceMessage.loads(msg.value)
|
||||||
|
# # current_coordinates = (deviceMSG.content['signals']['samx']['value'],deviceMSG.content['signals']['samy']['value'])
|
||||||
|
# # print(f'current coordinates: {current_coordinates}')
|
||||||
|
# # parent.scan_storage.update_with_scan_status(scan)
|
||||||
|
# print(deviceMSG)
|
||||||
|
|
||||||
|
# print(deviceMSG.metadata)
|
||||||
|
# parent.motor_update.emit(deviceMSG)
|
||||||
|
|
||||||
|
# @staticmethod
|
||||||
|
# def _device_status_callback_samx(msg, *, parent, **_kwargs) -> None:
|
||||||
|
# deviceMSG = BECMessage.DeviceMessage.loads(msg.value)
|
||||||
|
# x = deviceMSG.content["signals"]["samx"]["value"]
|
||||||
|
# print(f"x: {x}")
|
||||||
|
# # Assuming you have a way to retrieve or store the current samy value
|
||||||
|
# y = parent.current_y
|
||||||
|
# parent.coordinates_updated.emit(x, y)
|
||||||
|
#
|
||||||
|
# @staticmethod
|
||||||
|
# def _device_status_callback_samy(msg, *, parent, **_kwargs) -> None:
|
||||||
|
# deviceMSG = BECMessage.DeviceMessage.loads(msg.value)
|
||||||
|
# y = deviceMSG.content["signals"]["samy"]["value"]
|
||||||
|
# print(f"y: {y}")
|
||||||
|
# # # Assuming you have a way to retrieve or store the current samx value
|
||||||
|
# x = parent.current_x
|
||||||
|
# parent.coordinates_updated.emit(x, y)
|
||||||
|
|
||||||
|
|
||||||
|
class MotorMovement(QThread):
|
||||||
|
coordinates_updated = pyqtSignal(float, float) # Signal to emit current coordinates
|
||||||
|
|
||||||
|
# progress_updated = pyqtSignal(int) # 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
|
||||||
|
|
||||||
|
# Create consumer for samx and samy
|
||||||
|
self.samx_consumer = client.connector.consumer(
|
||||||
|
topics=[MessageEndpoints.device_readback("samx")],
|
||||||
|
cb=self._device_status_callback_samx,
|
||||||
|
parent=self,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.samy_consumer = client.connector.consumer(
|
||||||
|
topics=[MessageEndpoints.device_readback("samy")],
|
||||||
|
cb=self._device_status_callback_samy,
|
||||||
|
parent=self,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.samx_consumer.start()
|
||||||
|
self.samy_consumer.start()
|
||||||
|
|
||||||
|
self.get_xy()
|
||||||
|
|
||||||
|
def move_to_coordinates(self, target_coordinates: tuple):
|
||||||
|
self.action = "move_to_coordinates"
|
||||||
|
self.target_coordinates = target_coordinates
|
||||||
|
self.start()
|
||||||
|
|
||||||
|
def move_relative(self, motor, value: float):
|
||||||
|
self.action = "move_relative"
|
||||||
|
self.motor = motor
|
||||||
|
self.value = value
|
||||||
|
self.start()
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
if self.action == "move_to_coordinates":
|
||||||
|
self.move_motor_coordinate()
|
||||||
|
elif self.action == "move_relative":
|
||||||
|
self.move_motor_relative(self.motor, self.value)
|
||||||
|
|
||||||
|
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):
|
||||||
|
"""Move the motor to the specified coordinates"""
|
||||||
|
self.get_xy()
|
||||||
|
scans.mv(
|
||||||
|
dev.samx,
|
||||||
|
self.target_coordinates[0],
|
||||||
|
dev.samy,
|
||||||
|
self.target_coordinates[1],
|
||||||
|
relative=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
def move_motor_relative(self, motor, value: float) -> None:
|
||||||
|
self.get_xy()
|
||||||
|
scans.mv(motor, value, relative=True)
|
||||||
|
|
||||||
|
@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
|
||||||
|
print(f"samy moving: {parent.current_x,parent.current_y}")
|
||||||
|
# x = parent.current_y
|
||||||
|
parent.coordinates_updated.emit(parent.current_x, parent.current_y)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
from bec_widgets.bec_dispatcher import bec_dispatcher
|
from bec_lib import BECClient
|
||||||
|
|
||||||
# from bec_lib import BECClient
|
|
||||||
# from bec_lib.core import ServiceConfig,RedisConnector
|
# from bec_lib.core import ServiceConfig,RedisConnector
|
||||||
|
|
||||||
# client = BECClient()
|
client = BECClient()
|
||||||
# client.initialize(config=ServiceConfig(config_path="test_config.yaml"))
|
|
||||||
# client.initialize(config=ServiceConfig(config_path="test_config.yaml"))
|
# client.initialize(config=ServiceConfig(config_path="test_config.yaml"))
|
||||||
|
|
||||||
# Client initialization - by dispatcher
|
# Client initialization - by dispatcher
|
||||||
client = bec_dispatcher.client
|
# client = bec_dispatcher.client
|
||||||
client.start()
|
client.start()
|
||||||
dev = client.device_manager.devices
|
dev = client.device_manager.devices
|
||||||
scans = client.scans
|
scans = client.scans
|
||||||
|
Reference in New Issue
Block a user