fixed app to use new thorlabs driver
This commit is contained in:
+139
-93
@@ -21,7 +21,7 @@ from hardware.helios_laser import HeliosLaser, PulseMode
|
||||
from hardware.uc480_camera import UC480Camera, CameraStreamThread
|
||||
|
||||
# Import stage controller and motion worker
|
||||
from hardware.bbd202 import MotionController
|
||||
from hardware.pybbd202 import ThorlabsServoDriver, AXIS_X, AXIS_Y, TriggerBitsServo
|
||||
from scanengine.motion_worker import MotionWorker
|
||||
|
||||
# Import scan planning tool
|
||||
@@ -54,33 +54,7 @@ class ScanWorker(QtCore.QObject):
|
||||
|
||||
@QtCore.pyqtSlot()
|
||||
def run_scan(self):
|
||||
"""
|
||||
Execute the full scanning process.
|
||||
|
||||
TODO: Implement your own motion control logic here.
|
||||
|
||||
Available data:
|
||||
self.scan_params - dict containing:
|
||||
'scan_boxes' - list of scan box dicts with:
|
||||
'angle_degrees' - rotation angle
|
||||
'start' - (x_start, y_start) in mm
|
||||
'end' - (x_end, y_end) in mm
|
||||
'row_spacing' - spacing between rows in mm
|
||||
|
||||
self.motion_worker.controller - MotionController instance (if connected)
|
||||
|
||||
self.should_stop - set to True when user requests abort
|
||||
|
||||
Signals to emit:
|
||||
self.scan_started.emit() - at start
|
||||
self.angle_started.emit(angle_idx, total_angles) - when starting each angle
|
||||
self.line_started.emit(line_idx, total_lines, y_pos) - when starting each line
|
||||
self.current_progress.emit(percent) - progress for current angle
|
||||
self.overall_progress.emit(percent) - overall progress
|
||||
self.status_message.emit(message) - status updates
|
||||
self.scan_completed.emit() - on successful completion
|
||||
self.scan_failed.emit(error_msg) - on failure
|
||||
"""
|
||||
"""Execute the full scanning process."""
|
||||
# Pause motion worker polling during scan to avoid conflicts
|
||||
if self.motion_worker:
|
||||
self.motion_worker.scanning_active = True
|
||||
@@ -88,40 +62,114 @@ class ScanWorker(QtCore.QObject):
|
||||
try:
|
||||
self.scan_started.emit()
|
||||
|
||||
# Extract parameters
|
||||
scan_boxes = self.scan_params.get('scan_boxes', [])
|
||||
num_angles = len(scan_boxes)
|
||||
row_spacing = self.scan_params.get('row_spacing', 0.1)
|
||||
scan_velocity = self.scan_params.get('scan_velocity_mm_s', 200.0)
|
||||
scan_accel = self.scan_params.get('scan_acceleration_mm_s2', 500.0)
|
||||
|
||||
print(f"Scan worker: Starting scan with {num_angles} angles")
|
||||
print(f"Row spacing: {row_spacing} mm")
|
||||
print(f"Row spacing: {row_spacing} mm, velocity: {scan_velocity} mm/s")
|
||||
|
||||
controller = self.motion_worker.controller if self.motion_worker else None
|
||||
if not controller:
|
||||
self.scan_failed.emit("No motion controller connected")
|
||||
return
|
||||
|
||||
# TODO: Implement your motion control logic here
|
||||
#
|
||||
# For each angle in scan_boxes:
|
||||
# - Move to start position (x_start, y_start)
|
||||
# - For each row from y_start to y_end with row_spacing:
|
||||
# - Move X from x_start to x_end (flying scan)
|
||||
# - Move to next row (X back to x_start, Y to next row)
|
||||
#
|
||||
# The MotionController provides these methods:
|
||||
# controller.move_to_fast(x=mm, y=mm) - send move commands
|
||||
# controller.get_position(dest, timeout) - get current position
|
||||
# controller.poll_until_idle(tolerance, timeout) - poll until settled
|
||||
# controller.set_velocity_params(dest, min_vel, accel, max_vel)
|
||||
# controller.start_update_messages() - enable status updates
|
||||
# etc.
|
||||
# Store original velocity params for restoration
|
||||
orig_x_velocity = controller.max_velocities[0]
|
||||
orig_x_accel = controller.max_accels[0]
|
||||
orig_y_velocity = controller.max_velocities[1]
|
||||
orig_y_accel = controller.max_accels[1]
|
||||
|
||||
self.scan_failed.emit("Motion control not implemented - please implement run_scan()")
|
||||
# Configure stage for high-speed scanning
|
||||
controller.set_velocity_params(AXIS_X, max_velocity=scan_velocity, acceleration=scan_accel)
|
||||
controller.set_velocity_params(AXIS_Y, max_velocity=scan_velocity, acceleration=scan_accel)
|
||||
|
||||
# Set X-axis trigger output HIGH during motion (for oscilloscope sync)
|
||||
controller.set_trigger(AXIS_X, TriggerBitsServo.TRIGOUT_INMOTION)
|
||||
|
||||
self.status_message.emit("Scan configured, starting raster...")
|
||||
|
||||
# Main scan loop
|
||||
for angle_idx, scan_box in enumerate(scan_boxes):
|
||||
if self.should_stop:
|
||||
self.scan_failed.emit("Scan aborted by user")
|
||||
return
|
||||
|
||||
self.angle_started.emit(angle_idx, num_angles)
|
||||
angle_deg = scan_box.get('angle_degrees', 0)
|
||||
self.status_message.emit(f"Angle {angle_idx + 1}/{num_angles} ({angle_deg:.1f} deg)")
|
||||
|
||||
# Extract scan area boundaries
|
||||
x_start, y_start = scan_box['start']
|
||||
x_end, y_end = scan_box['end']
|
||||
|
||||
# Calculate scan lines
|
||||
y_range = y_end - y_start
|
||||
num_lines = max(1, int(y_range / row_spacing) + 1)
|
||||
|
||||
# Move to start position only for first angle
|
||||
if angle_idx == 0:
|
||||
self.status_message.emit("Moving to scan start position...")
|
||||
controller.move_axis_absolute(AXIS_X, x_start, timeout=30.0)
|
||||
controller.move_axis_absolute(AXIS_Y, y_start, timeout=30.0)
|
||||
|
||||
# Scan each line (snake/boustrophedon pattern)
|
||||
for line_idx in range(num_lines):
|
||||
if self.should_stop:
|
||||
self.scan_failed.emit("Scan aborted by user")
|
||||
return
|
||||
|
||||
y_current = y_start + line_idx * row_spacing
|
||||
if y_current > y_end:
|
||||
y_current = y_end
|
||||
|
||||
# Alternate scan direction for snake pattern
|
||||
if line_idx % 2 == 0:
|
||||
scan_start_x, scan_end_x = x_start, x_end
|
||||
else:
|
||||
scan_start_x, scan_end_x = x_end, x_start
|
||||
|
||||
self.line_started.emit(line_idx, num_lines, y_current)
|
||||
|
||||
# Position to line start (diagonal move - X and Y simultaneously)
|
||||
# Y move issued first (non-blocking from stage perspective),
|
||||
# then X move; both complete before scan line begins
|
||||
controller.move_axis_absolute(AXIS_Y, y_current, timeout=20.0)
|
||||
controller.move_axis_absolute(AXIS_X, scan_start_x, timeout=20.0)
|
||||
|
||||
# Perform scan line (trigger is HIGH during this move)
|
||||
scan_distance = abs(scan_end_x - scan_start_x)
|
||||
scan_timeout = max(10.0, scan_distance / scan_velocity * 3)
|
||||
controller.move_axis_absolute(AXIS_X, scan_end_x, timeout=scan_timeout)
|
||||
|
||||
# Update progress
|
||||
line_progress = int(100 * (line_idx + 1) / num_lines)
|
||||
self.current_progress.emit(line_progress)
|
||||
|
||||
overall = int(100 * (angle_idx + (line_idx + 1) / num_lines) / num_angles)
|
||||
self.overall_progress.emit(overall)
|
||||
|
||||
# Cleanup: disable triggers and restore original velocity
|
||||
controller.set_trigger(AXIS_X, 0)
|
||||
controller.set_velocity_params(AXIS_X, max_velocity=orig_x_velocity, acceleration=orig_x_accel)
|
||||
controller.set_velocity_params(AXIS_Y, max_velocity=orig_y_velocity, acceleration=orig_y_accel)
|
||||
|
||||
self.status_message.emit("Scan complete")
|
||||
self.scan_completed.emit()
|
||||
|
||||
except Exception as e:
|
||||
print(f"ERROR in scan worker: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
# Best-effort cleanup on error
|
||||
try:
|
||||
if self.motion_worker and self.motion_worker.controller:
|
||||
self.motion_worker.controller.set_trigger(AXIS_X, 0)
|
||||
except Exception:
|
||||
pass
|
||||
self.scan_failed.emit(str(e))
|
||||
finally:
|
||||
# Re-enable motion worker polling
|
||||
@@ -2395,8 +2443,8 @@ class ScanVisualizationDialog(QtWidgets.QDialog):
|
||||
self.current_scan_label.setText("Checking home status...")
|
||||
|
||||
# Get home status for both axes
|
||||
x_homed = controller.is_homed_x
|
||||
y_homed = controller.is_homed_y
|
||||
x_homed = controller.am_homed[0]
|
||||
y_homed = controller.am_homed[1]
|
||||
|
||||
print(f"Home status - X: {x_homed}, Y: {y_homed}")
|
||||
|
||||
@@ -2430,26 +2478,25 @@ class ScanVisualizationDialog(QtWidgets.QDialog):
|
||||
QtWidgets.QApplication.processEvents()
|
||||
|
||||
try:
|
||||
success = controller.home_axis(controller.DEST_X_AXIS, timeout=60.0)
|
||||
if success:
|
||||
print("X-axis homed successfully")
|
||||
self.current_scan_progress.setValue(50)
|
||||
controller.home_axis(AXIS_X, timeout=60.0)
|
||||
print("X-axis homed successfully")
|
||||
self.current_scan_progress.setValue(50)
|
||||
|
||||
# Small delay to let system stabilize
|
||||
import time
|
||||
time.sleep(0.5)
|
||||
else:
|
||||
print("ERROR: X-axis homing timeout")
|
||||
QtWidgets.QMessageBox.critical(
|
||||
self,
|
||||
"Homing Error",
|
||||
"X-axis homing timed out after 60 seconds.\n\n"
|
||||
"Please check:\n"
|
||||
"• Stage can move freely\n"
|
||||
"• No obstructions\n"
|
||||
"• Stage is connected properly"
|
||||
)
|
||||
return False
|
||||
# Small delay to let system stabilize
|
||||
import time
|
||||
time.sleep(0.5)
|
||||
except TimeoutError:
|
||||
print("ERROR: X-axis homing timeout")
|
||||
QtWidgets.QMessageBox.critical(
|
||||
self,
|
||||
"Homing Error",
|
||||
"X-axis homing timed out after 60 seconds.\n\n"
|
||||
"Please check:\n"
|
||||
"• Stage can move freely\n"
|
||||
"• No obstructions\n"
|
||||
"• Stage is connected properly"
|
||||
)
|
||||
return False
|
||||
except Exception as e:
|
||||
print(f"ERROR: Failed to home X-axis: {e}")
|
||||
QtWidgets.QMessageBox.critical(
|
||||
@@ -2467,26 +2514,25 @@ class ScanVisualizationDialog(QtWidgets.QDialog):
|
||||
QtWidgets.QApplication.processEvents()
|
||||
|
||||
try:
|
||||
success = controller.home_axis(controller.DEST_Y_AXIS, timeout=60.0)
|
||||
if success:
|
||||
print("Y-axis homed successfully")
|
||||
self.current_scan_progress.setValue(90)
|
||||
controller.home_axis(AXIS_Y, timeout=60.0)
|
||||
print("Y-axis homed successfully")
|
||||
self.current_scan_progress.setValue(90)
|
||||
|
||||
# Small delay to let system stabilize
|
||||
import time
|
||||
time.sleep(0.5)
|
||||
else:
|
||||
print("ERROR: Y-axis homing timeout")
|
||||
QtWidgets.QMessageBox.critical(
|
||||
self,
|
||||
"Homing Error",
|
||||
"Y-axis homing timed out after 60 seconds.\n\n"
|
||||
"Please check:\n"
|
||||
"• Stage can move freely\n"
|
||||
"• No obstructions\n"
|
||||
"• Stage is connected properly"
|
||||
)
|
||||
return False
|
||||
# Small delay to let system stabilize
|
||||
import time
|
||||
time.sleep(0.5)
|
||||
except TimeoutError:
|
||||
print("ERROR: Y-axis homing timeout")
|
||||
QtWidgets.QMessageBox.critical(
|
||||
self,
|
||||
"Homing Error",
|
||||
"Y-axis homing timed out after 60 seconds.\n\n"
|
||||
"Please check:\n"
|
||||
"• Stage can move freely\n"
|
||||
"• No obstructions\n"
|
||||
"• Stage is connected properly"
|
||||
)
|
||||
return False
|
||||
except Exception as e:
|
||||
print(f"ERROR: Failed to home Y-axis: {e}")
|
||||
QtWidgets.QMessageBox.critical(
|
||||
@@ -2689,16 +2735,16 @@ class OptionsDialog(QtWidgets.QDialog):
|
||||
|
||||
def populate_trigger_modes(self):
|
||||
"""Populate the trigger mode combo boxes with available options"""
|
||||
from hardware.bbd202 import TriggerMode
|
||||
from hardware.pybbd202 import TriggerBitsServo
|
||||
|
||||
trigger_options = [
|
||||
("Disabled", TriggerMode.DISABLED),
|
||||
("In/Out Relative Move", TriggerMode.IN_OUT_RELATIVE_MOVE),
|
||||
("In/Out Absolute Move", TriggerMode.IN_OUT_ABSOLUTE_MOVE),
|
||||
("In/Out Home", TriggerMode.IN_OUT_HOME),
|
||||
("In/Out Stop", TriggerMode.IN_OUT_STOP),
|
||||
("Out Only (HIGH during motion)", TriggerMode.OUT_ONLY),
|
||||
("Out Position", TriggerMode.OUT_POSITION),
|
||||
("Disabled", 0),
|
||||
("Trigger In: Relative Move", TriggerBitsServo.TRIGIN_RELMOVE),
|
||||
("Trigger In: Absolute Move", TriggerBitsServo.TRIGIN_ABSMOVE),
|
||||
("Trigger In: Home", TriggerBitsServo.TRIGIN_HOMEMOVE),
|
||||
("Trigger Out: In Motion", TriggerBitsServo.TRIGOUT_INMOTION),
|
||||
("Trigger Out: Motion Complete", TriggerBitsServo.TRIGOUT_MOTIONCOMPLETE),
|
||||
("Trigger Out: Max Velocity", TriggerBitsServo.TRIGOUT_MAXVELOCITY),
|
||||
]
|
||||
|
||||
for label, mode in trigger_options:
|
||||
|
||||
+58
-89
@@ -6,7 +6,7 @@ Provides async command queueing and position updates via Qt signals.
|
||||
"""
|
||||
|
||||
from PyQt6 import QtCore
|
||||
from hardware.bbd202 import MotionController
|
||||
from hardware.pybbd202 import ThorlabsServoDriver, AXIS_X, AXIS_Y
|
||||
import queue
|
||||
import time
|
||||
from typing import Optional, Dict, Any
|
||||
@@ -45,7 +45,7 @@ class MotionWorker(QtCore.QObject):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.controller: Optional[MotionController] = None
|
||||
self.controller: Optional[ThorlabsServoDriver] = None
|
||||
self.is_connected = False
|
||||
self.command_queue = queue.Queue()
|
||||
self.running = True
|
||||
@@ -65,9 +65,9 @@ class MotionWorker(QtCore.QObject):
|
||||
self.last_x_moving = None
|
||||
self.last_y_moving = None
|
||||
|
||||
# Position update throttling (active requests can be slow)
|
||||
# Position update throttling
|
||||
self.last_position_update_time = 0
|
||||
self.position_update_interval = 0.2 # seconds between position requests
|
||||
self.position_update_interval = 0.2 # seconds between position reads
|
||||
|
||||
# Flag to pause polling during scanning (scan worker handles its own position queries)
|
||||
self.scanning_active = False
|
||||
@@ -133,27 +133,25 @@ class MotionWorker(QtCore.QObject):
|
||||
def do_connect(self):
|
||||
"""Connect to the motion controller"""
|
||||
try:
|
||||
self.controller = MotionController()
|
||||
self.controller.connect() # ACKs are sent reactively when enable_updates=True
|
||||
self.controller = ThorlabsServoDriver()
|
||||
self.controller.connect()
|
||||
|
||||
# Enable channels
|
||||
self.controller.set_channel_enable_state(self.controller.DEST_X_AXIS, True)
|
||||
self.controller.set_channel_enable_state(self.controller.DEST_Y_AXIS, True)
|
||||
self.controller.enable_axis(AXIS_X)
|
||||
self.controller.enable_axis(AXIS_Y)
|
||||
|
||||
# Request status updates for both axes to populate status bits (including homed state)
|
||||
# This is necessary because status bits are not sent automatically after connect
|
||||
self.controller.request_status_update(self.controller.DEST_X_AXIS)
|
||||
self.controller.request_status_update(self.controller.DEST_Y_AXIS)
|
||||
# Wait a moment for the asynchronous status update responses
|
||||
time.sleep(0.2)
|
||||
# Start polling to populate cached state (positions, homed, moving, errors)
|
||||
self.controller.start_polling(interval=0.2)
|
||||
|
||||
# Wait for first polling cycle to populate status
|
||||
time.sleep(0.3)
|
||||
|
||||
# Set initial velocity parameters
|
||||
for dest in [self.controller.DEST_X_AXIS, self.controller.DEST_Y_AXIS]:
|
||||
for dest in [AXIS_X, AXIS_Y]:
|
||||
self.controller.set_velocity_params(
|
||||
dest,
|
||||
min_velocity=0.0,
|
||||
acceleration=self.acceleration,
|
||||
max_velocity=self.jog_speed
|
||||
max_velocity=self.jog_speed,
|
||||
acceleration=self.acceleration
|
||||
)
|
||||
|
||||
self.is_connected = True
|
||||
@@ -188,29 +186,25 @@ class MotionWorker(QtCore.QObject):
|
||||
return
|
||||
|
||||
try:
|
||||
# Determine destination
|
||||
dest = self.controller.DEST_X_AXIS if axis == 'x' else self.controller.DEST_Y_AXIS
|
||||
dest = AXIS_X if axis == 'x' else AXIS_Y
|
||||
|
||||
# Calculate relative distance
|
||||
distance = self.step_size * direction
|
||||
|
||||
# Set relative move parameters
|
||||
self.controller.set_move_rel_params(dest, distance)
|
||||
|
||||
# Execute the move (non-blocking - we don't wait for completion)
|
||||
# Use a very short timeout since we're doing continuous jogging
|
||||
self.controller.move_relative(dest, timeout=0.5)
|
||||
# Execute the move (blocking, with short timeout for continuous jogging)
|
||||
self.controller.move_axis_relative(dest, distance, timeout=0.5)
|
||||
|
||||
# Update position
|
||||
self.update_position()
|
||||
|
||||
self.move_completed.emit(axis)
|
||||
|
||||
except TimeoutError:
|
||||
# Timeout is expected during continuous jog - don't report as error
|
||||
pass
|
||||
except Exception as e:
|
||||
# Don't emit errors for timeout - that's expected during continuous jog
|
||||
if "timeout" not in str(e).lower():
|
||||
print(f"Jog error: {e}")
|
||||
self.error_occurred.emit(f"Jog failed: {str(e)}")
|
||||
print(f"Jog error: {e}")
|
||||
self.error_occurred.emit(f"Jog failed: {str(e)}")
|
||||
|
||||
def do_home(self, axis: str):
|
||||
"""Home an axis"""
|
||||
@@ -218,7 +212,7 @@ class MotionWorker(QtCore.QObject):
|
||||
return
|
||||
|
||||
try:
|
||||
dest = self.controller.DEST_X_AXIS if axis == 'x' else self.controller.DEST_Y_AXIS
|
||||
dest = AXIS_X if axis == 'x' else AXIS_Y
|
||||
|
||||
print(f"Homing {axis.upper()} axis...")
|
||||
self.controller.home_axis(dest, timeout=20.0)
|
||||
@@ -229,6 +223,9 @@ class MotionWorker(QtCore.QObject):
|
||||
|
||||
print(f"{axis.upper()} axis homed successfully")
|
||||
|
||||
except TimeoutError:
|
||||
print(f"Home timeout: {axis.upper()} axis")
|
||||
self.error_occurred.emit(f"Homing {axis.upper()} timed out")
|
||||
except Exception as e:
|
||||
print(f"Home error: {e}")
|
||||
self.error_occurred.emit(f"Homing {axis.upper()} failed: {str(e)}")
|
||||
@@ -244,12 +241,11 @@ class MotionWorker(QtCore.QObject):
|
||||
self.jog_speed = speed
|
||||
self.acceleration = accel
|
||||
|
||||
for dest in [self.controller.DEST_X_AXIS, self.controller.DEST_Y_AXIS]:
|
||||
for dest in [AXIS_X, AXIS_Y]:
|
||||
self.controller.set_velocity_params(
|
||||
dest,
|
||||
min_velocity=0.0,
|
||||
acceleration=self.acceleration,
|
||||
max_velocity=self.jog_speed
|
||||
max_velocity=self.jog_speed,
|
||||
acceleration=self.acceleration
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
@@ -261,8 +257,11 @@ class MotionWorker(QtCore.QObject):
|
||||
return
|
||||
|
||||
try:
|
||||
dest = self.controller.DEST_X_AXIS if axis == 'x' else self.controller.DEST_Y_AXIS
|
||||
self.controller.set_channel_enable_state(dest, enabled)
|
||||
dest = AXIS_X if axis == 'x' else AXIS_Y
|
||||
if enabled:
|
||||
self.controller.enable_axis(dest)
|
||||
else:
|
||||
self.controller.disable_axis(dest)
|
||||
state_str = "enabled" if enabled else "disabled"
|
||||
print(f"{axis.upper()} axis {state_str}")
|
||||
|
||||
@@ -275,35 +274,25 @@ class MotionWorker(QtCore.QObject):
|
||||
if not self.is_connected or not self.controller:
|
||||
return
|
||||
|
||||
# Throttle position requests to avoid slowing down the main loop
|
||||
# Throttle position reads to avoid excessive signal emission
|
||||
current_time = time.time()
|
||||
if current_time - self.last_position_update_time < self.position_update_interval:
|
||||
return
|
||||
self.last_position_update_time = current_time
|
||||
|
||||
try:
|
||||
# Actively request positions from the controller instead of relying on cached values
|
||||
# This ensures we always have up-to-date position data
|
||||
# Use longer timeout (1.5s) to accommodate high-speed scanning at 200mm/s
|
||||
x_pos = self.controller.get_position(self.controller.DEST_X_AXIS, timeout=1.5)
|
||||
y_pos = self.controller.get_position(self.controller.DEST_Y_AXIS, timeout=1.5)
|
||||
# Read cached positions (populated by polling worker)
|
||||
x_pos = self.controller.positions[0]
|
||||
y_pos = self.controller.positions[1]
|
||||
|
||||
if x_pos is not None and y_pos is not None:
|
||||
# Always emit on first update, or if position changed significantly (> 0.001mm)
|
||||
if (self.last_x is None or self.last_y is None or
|
||||
abs(x_pos - self.last_x) > 0.001 or abs(y_pos - self.last_y) > 0.001):
|
||||
self.last_x = x_pos
|
||||
self.last_y = y_pos
|
||||
print(f"Position update: X={x_pos:.3f}mm, Y={y_pos:.3f}mm")
|
||||
self.position_updated.emit(x_pos, y_pos)
|
||||
# else: silently skip incomplete position data during busy scanning
|
||||
# Always emit on first update, or if position changed significantly (> 0.001mm)
|
||||
if (self.last_x is None or self.last_y is None or
|
||||
abs(x_pos - self.last_x) > 0.001 or abs(y_pos - self.last_y) > 0.001):
|
||||
self.last_x = x_pos
|
||||
self.last_y = y_pos
|
||||
print(f"Position update: X={x_pos:.3f}mm, Y={y_pos:.3f}mm")
|
||||
self.position_updated.emit(x_pos, y_pos)
|
||||
|
||||
except RuntimeError as e:
|
||||
# RuntimeError indicates actual hardware error (overtemp, encoder fault, etc.)
|
||||
print(f"CRITICAL: Motor error detected: {e}")
|
||||
self.error_occurred.emit(str(e))
|
||||
# Stop requesting position updates to avoid spam
|
||||
self.is_connected = False
|
||||
except Exception as e:
|
||||
print(f"Error updating position: {e}")
|
||||
import traceback
|
||||
@@ -315,8 +304,8 @@ class MotionWorker(QtCore.QObject):
|
||||
return
|
||||
|
||||
try:
|
||||
x_homed = self.controller.is_homed_x
|
||||
y_homed = self.controller.is_homed_y
|
||||
x_homed = self.controller.am_homed[0]
|
||||
y_homed = self.controller.am_homed[1]
|
||||
|
||||
# Only emit if status changed
|
||||
if x_homed != self.last_x_homed or y_homed != self.last_y_homed:
|
||||
@@ -332,42 +321,22 @@ class MotionWorker(QtCore.QObject):
|
||||
def update_motion_status(self):
|
||||
"""Update motion status and emit signal if changed.
|
||||
|
||||
NOTE: On BBD202 firmware v2.1.5, the is_in_motion_x/y properties
|
||||
do NOT reliably detect motion - they may always return False even
|
||||
during active movement. This is a known firmware limitation.
|
||||
|
||||
For scan execution, use the ScanWorker.move_and_wait() method which
|
||||
uses position-based motion detection instead of status bits.
|
||||
|
||||
This UI status display is best-effort only.
|
||||
The new driver's polling worker keeps am_moving[], am_error[]
|
||||
up to date automatically via status update messages.
|
||||
"""
|
||||
if not self.is_connected or not self.controller:
|
||||
return
|
||||
|
||||
try:
|
||||
# Actively poll for status updates
|
||||
self.controller.poll_status()
|
||||
|
||||
# Check for any error conditions
|
||||
error_msg = self.controller.check_for_errors()
|
||||
if error_msg:
|
||||
print(f"Motor error detected: {error_msg}")
|
||||
self.error_occurred.emit(error_msg)
|
||||
self.controller.clear_last_error()
|
||||
if self.controller.am_error[0]:
|
||||
self.error_occurred.emit("X-axis error detected")
|
||||
if self.controller.am_error[1]:
|
||||
self.error_occurred.emit("Y-axis error detected")
|
||||
|
||||
# Read the cached status (may not accurately reflect motion on some firmware)
|
||||
x_moving = self.controller.is_in_motion_x
|
||||
y_moving = self.controller.is_in_motion_y
|
||||
|
||||
# Also check if there are pending moves (more reliable)
|
||||
if self.controller.is_move_pending():
|
||||
# If there are pending moves, we're likely still moving
|
||||
# This provides a backup indication when status bits fail
|
||||
pending = self.controller.get_pending_targets()
|
||||
if self.controller.DEST_X_AXIS in pending:
|
||||
x_moving = True
|
||||
if self.controller.DEST_Y_AXIS in pending:
|
||||
y_moving = True
|
||||
# Read cached motion status (updated by polling worker)
|
||||
x_moving = self.controller.am_moving[0]
|
||||
y_moving = self.controller.am_moving[1]
|
||||
|
||||
# Only emit if status changed
|
||||
if x_moving != self.last_x_moving or y_moving != self.last_y_moving:
|
||||
|
||||
Reference in New Issue
Block a user