pre local llm test
This commit is contained in:
+2
-1
@@ -3,5 +3,6 @@
|
|||||||
"bbd_port": "/dev/ttyAPT",
|
"bbd_port": "/dev/ttyAPT",
|
||||||
"oscope_ip": "192.168.100.105",
|
"oscope_ip": "192.168.100.105",
|
||||||
"laser_freq_hz": 20000.0,
|
"laser_freq_hz": 20000.0,
|
||||||
"save_dir": "/opt/scanengine-3/scans"
|
"save_dir": "/opt/scanengine-3/scans",
|
||||||
|
"helios_port": "/dev/ttyUSB0"
|
||||||
}
|
}
|
||||||
+17
-10
@@ -280,21 +280,28 @@ class HeliosLaser:
|
|||||||
logger.error(f"Invalid response for LDS: {response}")
|
logger.error(f"Invalid response for LDS: {response}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def get_power_mw(self) -> Optional[float]:
|
def _query_millicelsius(self, command: str) -> Optional[float]:
|
||||||
"""
|
"""Query a temperature register (returns milli-°C) and convert to °C."""
|
||||||
Get laser output power in mW.
|
response = self._query(command)
|
||||||
|
|
||||||
Returns:
|
|
||||||
Power in mW or None if error
|
|
||||||
"""
|
|
||||||
response = self._query("HMP")
|
|
||||||
if response:
|
if response:
|
||||||
try:
|
try:
|
||||||
return float(response)
|
return int(response) / 1000.0
|
||||||
except ValueError:
|
except ValueError:
|
||||||
logger.error(f"Invalid response for HMP: {response}")
|
logger.error(f"Invalid response for {command}: {response}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def get_diode_temp_c(self) -> Optional[float]:
|
||||||
|
"""Diode temperature in °C (LTA, 5000–50000 milli-°C)."""
|
||||||
|
return self._query_millicelsius("LTA")
|
||||||
|
|
||||||
|
def get_power_stage_temp_c(self) -> Optional[float]:
|
||||||
|
"""Power stage temperature in °C (LTT, 5000–65355 milli-°C)."""
|
||||||
|
return self._query_millicelsius("LTT")
|
||||||
|
|
||||||
|
def get_qswitch_temp_c(self) -> Optional[float]:
|
||||||
|
"""Q-switch temperature in °C (EOA, 5000–50000 milli-°C)."""
|
||||||
|
return self._query_millicelsius("EOA")
|
||||||
|
|
||||||
def get_controller_serial(self) -> Optional[str]:
|
def get_controller_serial(self) -> Optional[str]:
|
||||||
"""
|
"""
|
||||||
Get controller serial number.
|
Get controller serial number.
|
||||||
|
|||||||
@@ -60,6 +60,24 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_stream">
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="uc480_start_btn">
|
||||||
|
<property name="text">
|
||||||
|
<string>Start Stream</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="uc480_stop_btn">
|
||||||
|
<property name="text">
|
||||||
|
<string>Stop Stream</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QPushButton" name="uc480_close_window_btn">
|
<widget class="QPushButton" name="uc480_close_window_btn">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
|
|||||||
@@ -897,6 +897,16 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="show_helios_toggle">
|
||||||
|
<property name="text">
|
||||||
|
<string>Show Helios Panel</string>
|
||||||
|
</property>
|
||||||
|
<property name="checkable">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QPushButton" name="start_scan_btn">
|
<widget class="QPushButton" name="start_scan_btn">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
|
|||||||
+382
-18
@@ -26,6 +26,7 @@ from PyQt6.QtWidgets import (
|
|||||||
ROOT = Path(__file__).parent
|
ROOT = Path(__file__).parent
|
||||||
sys.path.insert(0, str(ROOT))
|
sys.path.insert(0, str(ROOT))
|
||||||
|
|
||||||
|
from hardware.helios_laser import HeliosLaser
|
||||||
from hardware.pybbd202 import AXIS_X, AXIS_Y, ThorlabsServoDriver
|
from hardware.pybbd202 import AXIS_X, AXIS_Y, ThorlabsServoDriver
|
||||||
from hardware.tektronix_base import TektronixOscilloscopeBase
|
from hardware.tektronix_base import TektronixOscilloscopeBase
|
||||||
from hardware.uc480_camera import CameraStreamThread, UC480Camera
|
from hardware.uc480_camera import CameraStreamThread, UC480Camera
|
||||||
@@ -39,6 +40,7 @@ _AUI_DEFAULTS_FALLBACK = {
|
|||||||
"oscope_ip": "192.168.0.1",
|
"oscope_ip": "192.168.0.1",
|
||||||
"laser_freq_hz": 2000,
|
"laser_freq_hz": 2000,
|
||||||
"save_dir": str(ROOT / "scans"),
|
"save_dir": str(ROOT / "scans"),
|
||||||
|
"helios_port": "/dev/ttyUSB2",
|
||||||
}
|
}
|
||||||
|
|
||||||
def _load_aui_defaults() -> dict:
|
def _load_aui_defaults() -> dict:
|
||||||
@@ -66,6 +68,7 @@ DEFAULT_BBD_PORT = _aui["bbd_port"]
|
|||||||
DEFAULT_OSCOPE_IP = _aui["oscope_ip"]
|
DEFAULT_OSCOPE_IP = _aui["oscope_ip"]
|
||||||
DEFAULT_LASER_FREQ_HZ = float(_aui["laser_freq_hz"])
|
DEFAULT_LASER_FREQ_HZ = float(_aui["laser_freq_hz"])
|
||||||
DEFAULT_SAVE_DIR = _aui["save_dir"]
|
DEFAULT_SAVE_DIR = _aui["save_dir"]
|
||||||
|
DEFAULT_HELIOS_PORT = _aui.get("helios_port", "/dev/ttyUSB2")
|
||||||
|
|
||||||
# ── Scan constants ───────────────────────────────────────────────────────────
|
# ── Scan constants ───────────────────────────────────────────────────────────
|
||||||
|
|
||||||
@@ -512,7 +515,7 @@ class OscopeWorker(QObject):
|
|||||||
|
|
||||||
# ── CH1 — RF Acoustic Packet ──────────────────────────────────────────
|
# ── CH1 — RF Acoustic Packet ──────────────────────────────────────────
|
||||||
s.set_channel_label_name(1, "RF Acoustic Packet")
|
s.set_channel_label_name(1, "RF Acoustic Packet")
|
||||||
s.set_channel_scale(1, 0.05) # 100 mV/div
|
s.set_channel_scale(1, 0.07) # 70 mV/div
|
||||||
s.set_channel_position(1, 0.0) # 0 divs
|
s.set_channel_position(1, 0.0) # 0 divs
|
||||||
s.set_channel_termination(1, 50) # 50 Ohm
|
s.set_channel_termination(1, 50) # 50 Ohm
|
||||||
s.set_channel_coupling(1, "DC")
|
s.set_channel_coupling(1, "DC")
|
||||||
@@ -536,7 +539,7 @@ class OscopeWorker(QObject):
|
|||||||
|
|
||||||
# ── CH4 — Bias B ──────────────────────────────────────────────────────
|
# ── CH4 — Bias B ──────────────────────────────────────────────────────
|
||||||
s.set_channel_label_name(4, "Bias - B")
|
s.set_channel_label_name(4, "Bias - B")
|
||||||
s.set_channel_scale(4, 0.1) # 50 mV/div
|
s.set_channel_scale(4, 0.1) # 100 mV/div
|
||||||
s.set_channel_position(4, -2.72) # -2.72 divs
|
s.set_channel_position(4, -2.72) # -2.72 divs
|
||||||
s.set_channel_termination(4, 1000000) # 1 Mohm
|
s.set_channel_termination(4, 1000000) # 1 Mohm
|
||||||
s.set_channel_coupling(4, "DC")
|
s.set_channel_coupling(4, "DC")
|
||||||
@@ -562,11 +565,167 @@ class OscopeWorker(QObject):
|
|||||||
self._cmd_q.put({"type": "stop"})
|
self._cmd_q.put({"type": "stop"})
|
||||||
|
|
||||||
|
|
||||||
|
# ── Helios laser worker ───────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
class HeliosWorker(QObject):
|
||||||
|
"""Manages HeliosLaser in a worker thread using a command queue."""
|
||||||
|
connected = pyqtSignal()
|
||||||
|
disconnected = pyqtSignal()
|
||||||
|
connection_failed = pyqtSignal(str)
|
||||||
|
enabled_updated = pyqtSignal(bool)
|
||||||
|
current_updated = pyqtSignal(int)
|
||||||
|
diode_temp_updated = pyqtSignal(float)
|
||||||
|
pstage_temp_updated = pyqtSignal(float)
|
||||||
|
qswitch_temp_updated = pyqtSignal(float)
|
||||||
|
status_registers_updated = pyqtSignal(object, object, object) # LER, LCE, CCE (int|None)
|
||||||
|
error_occurred = pyqtSignal(str)
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
self._laser: HeliosLaser | None = None
|
||||||
|
self._cmd_q: queue.Queue = queue.Queue()
|
||||||
|
self._running = False
|
||||||
|
self.is_connected = False
|
||||||
|
|
||||||
|
@pyqtSlot()
|
||||||
|
def run(self):
|
||||||
|
self._running = True
|
||||||
|
while self._running:
|
||||||
|
try:
|
||||||
|
cmd = self._cmd_q.get(timeout=0.1)
|
||||||
|
self._dispatch(cmd)
|
||||||
|
except queue.Empty:
|
||||||
|
pass
|
||||||
|
if self._laser:
|
||||||
|
try:
|
||||||
|
self._laser.disconnect()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def _dispatch(self, cmd: dict):
|
||||||
|
t = cmd["type"]
|
||||||
|
if t == "connect":
|
||||||
|
self._do_connect(cmd["port"])
|
||||||
|
elif t == "disconnect":
|
||||||
|
self._do_disconnect()
|
||||||
|
elif t == "set_enable":
|
||||||
|
self._do_set_enable(cmd["enable"])
|
||||||
|
elif t == "set_current":
|
||||||
|
self._do_set_current(cmd["current_ma"])
|
||||||
|
elif t == "poll_status":
|
||||||
|
self._do_poll_status()
|
||||||
|
elif t == "stop":
|
||||||
|
self._running = False
|
||||||
|
|
||||||
|
def _do_connect(self, port: str):
|
||||||
|
try:
|
||||||
|
self._laser = HeliosLaser(timeout=1.0)
|
||||||
|
if not self._laser.connect(port):
|
||||||
|
self._laser = None
|
||||||
|
self.connection_failed.emit("Failed to open serial port")
|
||||||
|
return
|
||||||
|
self.is_connected = True
|
||||||
|
self.connected.emit()
|
||||||
|
self._do_poll_status()
|
||||||
|
except Exception as e:
|
||||||
|
self._laser = None
|
||||||
|
self.connection_failed.emit(str(e))
|
||||||
|
|
||||||
|
def _do_disconnect(self):
|
||||||
|
if self._laser:
|
||||||
|
try:
|
||||||
|
self._laser.set_laser_enable(False)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
self._laser.disconnect()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
self._laser = None
|
||||||
|
self.is_connected = False
|
||||||
|
self.disconnected.emit()
|
||||||
|
|
||||||
|
def _do_set_enable(self, enable: bool):
|
||||||
|
if not self._laser:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
self._laser.set_laser_enable(enable)
|
||||||
|
self.enabled_updated.emit(self._laser.is_laser_enabled())
|
||||||
|
except Exception as e:
|
||||||
|
self.error_occurred.emit(str(e))
|
||||||
|
|
||||||
|
def _do_set_current(self, current_ma: int):
|
||||||
|
if not self._laser:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
self._laser.set_current_ma(current_ma)
|
||||||
|
self.current_updated.emit(current_ma)
|
||||||
|
except Exception as e:
|
||||||
|
self.error_occurred.emit(str(e))
|
||||||
|
|
||||||
|
def _do_poll_status(self):
|
||||||
|
if not self._laser or not self._laser.is_connected:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
ler, lce, cce = self._laser.get_status_registers()
|
||||||
|
self.status_registers_updated.emit(ler, lce, cce)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
self.enabled_updated.emit(self._laser.is_laser_enabled())
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
current = self._laser.get_current_ma()
|
||||||
|
if current is not None:
|
||||||
|
self.current_updated.emit(current)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
t = self._laser.get_diode_temp_c()
|
||||||
|
if t is not None:
|
||||||
|
self.diode_temp_updated.emit(t)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
t = self._laser.get_power_stage_temp_c()
|
||||||
|
if t is not None:
|
||||||
|
self.pstage_temp_updated.emit(t)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
t = self._laser.get_qswitch_temp_c()
|
||||||
|
if t is not None:
|
||||||
|
self.qswitch_temp_updated.emit(t)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def queue_connect(self, port: str):
|
||||||
|
self._cmd_q.put({"type": "connect", "port": port})
|
||||||
|
|
||||||
|
def queue_disconnect(self):
|
||||||
|
self._cmd_q.put({"type": "disconnect"})
|
||||||
|
|
||||||
|
def queue_set_enable(self, enable: bool):
|
||||||
|
self._cmd_q.put({"type": "set_enable", "enable": enable})
|
||||||
|
|
||||||
|
def queue_set_current(self, current_ma: int):
|
||||||
|
self._cmd_q.put({"type": "set_current", "current_ma": current_ma})
|
||||||
|
|
||||||
|
def queue_poll_status(self):
|
||||||
|
self._cmd_q.put({"type": "poll_status"})
|
||||||
|
|
||||||
|
def stop_worker(self):
|
||||||
|
self._cmd_q.put({"type": "stop"})
|
||||||
|
|
||||||
|
|
||||||
# ── Camera window popup ───────────────────────────────────────────────────────
|
# ── Camera window popup ───────────────────────────────────────────────────────
|
||||||
|
|
||||||
class CameraWindow(QWidget):
|
class CameraWindow(QWidget):
|
||||||
"""Camera display popup — auto-connects on show, auto-disconnects on close."""
|
"""Camera display popup — auto-connects on show, auto-disconnects on close."""
|
||||||
|
|
||||||
|
closed = pyqtSignal()
|
||||||
|
|
||||||
def __init__(self, parent: QWidget | None = None):
|
def __init__(self, parent: QWidget | None = None):
|
||||||
super().__init__(parent, Qt.WindowType.Window)
|
super().__init__(parent, Qt.WindowType.Window)
|
||||||
uic.loadUi(ROOT / "sc3-aui-camera.ui", self)
|
uic.loadUi(ROOT / "sc3-aui-camera.ui", self)
|
||||||
@@ -590,18 +749,25 @@ class CameraWindow(QWidget):
|
|||||||
|
|
||||||
self.uc480_exposure_slider.valueChanged.connect(self._set_exposure)
|
self.uc480_exposure_slider.valueChanged.connect(self._set_exposure)
|
||||||
self.uc480_gain_slider.valueChanged.connect(self._set_gain)
|
self.uc480_gain_slider.valueChanged.connect(self._set_gain)
|
||||||
|
self.uc480_start_btn.clicked.connect(self._start_stream)
|
||||||
|
self.uc480_stop_btn.clicked.connect(self._stop_stream)
|
||||||
self.uc480_close_window_btn.clicked.connect(self.close)
|
self.uc480_close_window_btn.clicked.connect(self.close)
|
||||||
|
|
||||||
|
self._update_controls()
|
||||||
|
|
||||||
def showEvent(self, event):
|
def showEvent(self, event):
|
||||||
super().showEvent(event)
|
super().showEvent(event)
|
||||||
self._start_camera()
|
self._connect_camera()
|
||||||
|
self._start_stream()
|
||||||
|
|
||||||
def closeEvent(self, event):
|
def closeEvent(self, event):
|
||||||
self._stop_camera()
|
self._stop_stream()
|
||||||
|
self._disconnect_camera()
|
||||||
super().closeEvent(event)
|
super().closeEvent(event)
|
||||||
|
self.closed.emit()
|
||||||
|
|
||||||
def _start_camera(self):
|
def _connect_camera(self):
|
||||||
if self._stream is not None:
|
if self._camera is not None:
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
self._camera = UC480Camera()
|
self._camera = UC480Camera()
|
||||||
@@ -610,22 +776,33 @@ class CameraWindow(QWidget):
|
|||||||
self.uc480_exposure_slider.blockSignals(True)
|
self.uc480_exposure_slider.blockSignals(True)
|
||||||
self.uc480_exposure_slider.setValue(max(1, round(exp_ms * 10)))
|
self.uc480_exposure_slider.setValue(max(1, round(exp_ms * 10)))
|
||||||
self.uc480_exposure_slider.blockSignals(False)
|
self.uc480_exposure_slider.blockSignals(False)
|
||||||
self._stream = CameraStreamThread(self._camera)
|
|
||||||
self._stream.frame_ready.connect(self._on_frame)
|
|
||||||
self._stream.start()
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
QMessageBox.warning(self, "Camera Error", f"Could not open camera:\n{e}")
|
self._camera = None
|
||||||
|
self._frame_label.setText(f"Camera error:\n{e}")
|
||||||
|
self._update_controls()
|
||||||
|
|
||||||
def _stop_camera(self):
|
def _disconnect_camera(self):
|
||||||
if self._stream:
|
|
||||||
self._stream.stop()
|
|
||||||
self._stream = None
|
|
||||||
if self._camera:
|
if self._camera:
|
||||||
try:
|
try:
|
||||||
self._camera.cleanup()
|
self._camera.cleanup()
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
self._camera = None
|
self._camera = None
|
||||||
|
self._update_controls()
|
||||||
|
|
||||||
|
def _start_stream(self):
|
||||||
|
if self._stream is not None or self._camera is None:
|
||||||
|
return
|
||||||
|
self._stream = CameraStreamThread(self._camera)
|
||||||
|
self._stream.frame_ready.connect(self._on_frame)
|
||||||
|
self._stream.start()
|
||||||
|
self._update_controls()
|
||||||
|
|
||||||
|
def _stop_stream(self):
|
||||||
|
if self._stream:
|
||||||
|
self._stream.stop()
|
||||||
|
self._stream = None
|
||||||
|
self._update_controls()
|
||||||
|
|
||||||
def _on_frame(self, image: QImage):
|
def _on_frame(self, image: QImage):
|
||||||
px = QPixmap.fromImage(image).scaled(
|
px = QPixmap.fromImage(image).scaled(
|
||||||
@@ -650,6 +827,162 @@ class CameraWindow(QWidget):
|
|||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def _update_controls(self):
|
||||||
|
camera_ok = self._camera is not None
|
||||||
|
streaming = self._stream is not None
|
||||||
|
self.uc480_start_btn.setEnabled(camera_ok and not streaming)
|
||||||
|
self.uc480_stop_btn.setEnabled(streaming)
|
||||||
|
self.uc480_exposure_slider.setEnabled(camera_ok)
|
||||||
|
self.uc480_gain_slider.setEnabled(camera_ok)
|
||||||
|
|
||||||
|
|
||||||
|
# ── Helios laser panel ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
class HeliosWindow(QWidget):
|
||||||
|
"""Helios laser control panel — shown/hidden with a toggle in the main window."""
|
||||||
|
|
||||||
|
closed = pyqtSignal()
|
||||||
|
|
||||||
|
def __init__(self, worker: HeliosWorker, parent: QWidget | None = None):
|
||||||
|
super().__init__(parent, Qt.WindowType.Window)
|
||||||
|
uic.loadUi(ROOT / "sc3-aui-helios.ui", self)
|
||||||
|
self.setWindowTitle("Helios Laser")
|
||||||
|
|
||||||
|
self._worker = worker
|
||||||
|
|
||||||
|
self._poll_timer = QTimer(self)
|
||||||
|
self._poll_timer.setInterval(1000)
|
||||||
|
self._poll_timer.timeout.connect(self._worker.queue_poll_status)
|
||||||
|
|
||||||
|
self._wire_signals()
|
||||||
|
self._update_controls(False)
|
||||||
|
self._set_emission_indicator(False)
|
||||||
|
|
||||||
|
def closeEvent(self, event):
|
||||||
|
self._poll_timer.stop()
|
||||||
|
if self._worker.is_connected:
|
||||||
|
self._worker.queue_disconnect()
|
||||||
|
super().closeEvent(event)
|
||||||
|
self.closed.emit()
|
||||||
|
|
||||||
|
def _wire_signals(self):
|
||||||
|
self.helios_connect_toggle.toggled.connect(self._on_connect_toggle)
|
||||||
|
self._worker.connected.connect(self._on_connected)
|
||||||
|
self._worker.disconnected.connect(self._on_disconnected)
|
||||||
|
self._worker.connection_failed.connect(self._on_connection_failed)
|
||||||
|
|
||||||
|
self.helios_enable_btn.toggled.connect(self._on_enable_toggle)
|
||||||
|
self._worker.enabled_updated.connect(self._on_enabled_updated)
|
||||||
|
|
||||||
|
self.helios_set_current_btn.clicked.connect(self._on_set_current)
|
||||||
|
self._worker.current_updated.connect(self._on_current_updated)
|
||||||
|
self._worker.diode_temp_updated.connect(
|
||||||
|
lambda t: self.helios_temp_diode_label.setText(f"{t:.1f} °C")
|
||||||
|
)
|
||||||
|
self._worker.pstage_temp_updated.connect(
|
||||||
|
lambda t: self.helios_temp_pstage_label.setText(f"{t:.1f} °C")
|
||||||
|
)
|
||||||
|
self._worker.qswitch_temp_updated.connect(
|
||||||
|
lambda t: self.helios_temp_qswitch_label.setText(f"{t:.1f} °C")
|
||||||
|
)
|
||||||
|
self._worker.status_registers_updated.connect(self._on_status_registers)
|
||||||
|
self._worker.error_occurred.connect(
|
||||||
|
lambda m: self.helios_status_label.setText(m)
|
||||||
|
)
|
||||||
|
|
||||||
|
def _on_connect_toggle(self, checked: bool):
|
||||||
|
if checked:
|
||||||
|
self.helios_connect_toggle.setText("Connecting…")
|
||||||
|
self.helios_connect_toggle.setEnabled(False)
|
||||||
|
port = self.helios_port_edit.text().strip()
|
||||||
|
d = _load_aui_defaults()
|
||||||
|
d["helios_port"] = port
|
||||||
|
_save_aui_defaults(d)
|
||||||
|
self._worker.queue_connect(port)
|
||||||
|
else:
|
||||||
|
self._poll_timer.stop()
|
||||||
|
self._worker.queue_disconnect()
|
||||||
|
|
||||||
|
def _on_connected(self):
|
||||||
|
self.helios_connect_toggle.setEnabled(True)
|
||||||
|
self.helios_connect_toggle.setText("Disconnect")
|
||||||
|
self.helios_status_label.setText("Connected")
|
||||||
|
self._update_controls(True)
|
||||||
|
self._poll_timer.start()
|
||||||
|
|
||||||
|
def _on_disconnected(self):
|
||||||
|
self._poll_timer.stop()
|
||||||
|
self.helios_connect_toggle.blockSignals(True)
|
||||||
|
self.helios_connect_toggle.setChecked(False)
|
||||||
|
self.helios_connect_toggle.setText("Connect")
|
||||||
|
self.helios_connect_toggle.setEnabled(True)
|
||||||
|
self.helios_connect_toggle.blockSignals(False)
|
||||||
|
self.helios_status_label.setText("Disconnected")
|
||||||
|
self._update_controls(False)
|
||||||
|
|
||||||
|
def _on_connection_failed(self, msg: str):
|
||||||
|
self.helios_connect_toggle.blockSignals(True)
|
||||||
|
self.helios_connect_toggle.setChecked(False)
|
||||||
|
self.helios_connect_toggle.setText("Connect")
|
||||||
|
self.helios_connect_toggle.setEnabled(True)
|
||||||
|
self.helios_connect_toggle.blockSignals(False)
|
||||||
|
self.helios_status_label.setText(f"Failed: {msg}")
|
||||||
|
self._update_controls(False)
|
||||||
|
|
||||||
|
def _set_emission_indicator(self, enabled: bool):
|
||||||
|
if enabled:
|
||||||
|
self.helios_emission_indicator.setStyleSheet(
|
||||||
|
"background-color: #ff2222; border-radius: 10px;"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
self.helios_emission_indicator.setStyleSheet(
|
||||||
|
"background-color: #444444; border-radius: 10px;"
|
||||||
|
)
|
||||||
|
|
||||||
|
def _on_enable_toggle(self, checked: bool):
|
||||||
|
self.helios_enable_btn.setText("Disable Laser" if checked else "Enable Laser")
|
||||||
|
self._worker.queue_set_enable(checked)
|
||||||
|
|
||||||
|
def _on_enabled_updated(self, enabled: bool):
|
||||||
|
self.helios_enable_btn.blockSignals(True)
|
||||||
|
self.helios_enable_btn.setChecked(enabled)
|
||||||
|
self.helios_enable_btn.setText("Disable Laser" if enabled else "Enable Laser")
|
||||||
|
self.helios_enable_btn.blockSignals(False)
|
||||||
|
self._set_emission_indicator(enabled)
|
||||||
|
|
||||||
|
def _on_set_current(self):
|
||||||
|
self._worker.queue_set_current(self.helios_current_spin.value())
|
||||||
|
|
||||||
|
def _on_current_updated(self, current_ma: int):
|
||||||
|
self.helios_current_spin.blockSignals(True)
|
||||||
|
self.helios_current_spin.setValue(current_ma)
|
||||||
|
self.helios_current_spin.blockSignals(False)
|
||||||
|
|
||||||
|
def _reset_temperatures(self):
|
||||||
|
self.helios_temp_diode_label.setText("--- °C")
|
||||||
|
self.helios_temp_pstage_label.setText("--- °C")
|
||||||
|
self.helios_temp_qswitch_label.setText("--- °C")
|
||||||
|
|
||||||
|
def _on_status_registers(self, ler, lce, cce):
|
||||||
|
self.helios_ler_label.setText(f"0x{ler:04X}" if ler is not None else "---")
|
||||||
|
self.helios_lce_label.setText(f"0x{lce:04X}" if lce is not None else "---")
|
||||||
|
self.helios_cce_label.setText(f"0x{cce:04X}" if cce is not None else "---")
|
||||||
|
|
||||||
|
def _update_controls(self, connected: bool):
|
||||||
|
self.helios_enable_btn.setEnabled(connected)
|
||||||
|
self.helios_current_spin.setEnabled(connected)
|
||||||
|
self.helios_set_current_btn.setEnabled(connected)
|
||||||
|
if not connected:
|
||||||
|
self.helios_enable_btn.blockSignals(True)
|
||||||
|
self.helios_enable_btn.setChecked(False)
|
||||||
|
self.helios_enable_btn.setText("Enable Laser")
|
||||||
|
self.helios_enable_btn.blockSignals(False)
|
||||||
|
self._set_emission_indicator(False)
|
||||||
|
self._reset_temperatures()
|
||||||
|
self.helios_ler_label.setText("---")
|
||||||
|
self.helios_lce_label.setText("---")
|
||||||
|
self.helios_cce_label.setText("---")
|
||||||
|
|
||||||
|
|
||||||
# ── Scan progress popup ───────────────────────────────────────────────────────
|
# ── Scan progress popup ───────────────────────────────────────────────────────
|
||||||
|
|
||||||
@@ -1013,8 +1346,14 @@ class MainWindow(QMainWindow):
|
|||||||
self._oscope_worker.moveToThread(self._oscope_thread)
|
self._oscope_worker.moveToThread(self._oscope_thread)
|
||||||
self._oscope_thread.started.connect(self._oscope_worker.run)
|
self._oscope_thread.started.connect(self._oscope_worker.run)
|
||||||
|
|
||||||
|
self._helios_thread = QThread(self)
|
||||||
|
self._helios_worker = HeliosWorker()
|
||||||
|
self._helios_worker.moveToThread(self._helios_thread)
|
||||||
|
self._helios_thread.started.connect(self._helios_worker.run)
|
||||||
|
|
||||||
# ── Popup windows ─────────────────────────────────────────────────────
|
# ── Popup windows ─────────────────────────────────────────────────────
|
||||||
self._camera_win = CameraWindow()
|
self._camera_win = CameraWindow()
|
||||||
|
self._helios_win = HeliosWindow(self._helios_worker)
|
||||||
self._scan_progress = ScanProgressWindow()
|
self._scan_progress = ScanProgressWindow()
|
||||||
|
|
||||||
self._scan_worker: ScanWorker | None = None
|
self._scan_worker: ScanWorker | None = None
|
||||||
@@ -1038,6 +1377,7 @@ class MainWindow(QMainWindow):
|
|||||||
self._t3r_thread.start()
|
self._t3r_thread.start()
|
||||||
self._bbd_thread.start()
|
self._bbd_thread.start()
|
||||||
self._oscope_thread.start()
|
self._oscope_thread.start()
|
||||||
|
self._helios_thread.start()
|
||||||
|
|
||||||
# ── UI initialisation ─────────────────────────────────────────────────────
|
# ── UI initialisation ─────────────────────────────────────────────────────
|
||||||
|
|
||||||
@@ -1045,6 +1385,7 @@ class MainWindow(QMainWindow):
|
|||||||
self.t3r_comport_edit.setText(DEFAULT_T3R_PORT)
|
self.t3r_comport_edit.setText(DEFAULT_T3R_PORT)
|
||||||
self.bbd202_comport_edit.setText(DEFAULT_BBD_PORT)
|
self.bbd202_comport_edit.setText(DEFAULT_BBD_PORT)
|
||||||
self.oscope_ip_edit.setText(DEFAULT_OSCOPE_IP)
|
self.oscope_ip_edit.setText(DEFAULT_OSCOPE_IP)
|
||||||
|
self._helios_win.helios_port_edit.setText(DEFAULT_HELIOS_PORT)
|
||||||
self.t3r_jog_speed_edit.setText(str(GR_MOVE_SPEED_HZ))
|
self.t3r_jog_speed_edit.setText(str(GR_MOVE_SPEED_HZ))
|
||||||
self.lineEdit_10.setText(str(BBD_DEFAULT_JOG_MM))
|
self.lineEdit_10.setText(str(BBD_DEFAULT_JOG_MM))
|
||||||
|
|
||||||
@@ -1131,8 +1472,17 @@ class MainWindow(QMainWindow):
|
|||||||
|
|
||||||
# Camera
|
# Camera
|
||||||
self.show_camera_toggle.toggled.connect(self._on_camera_toggle)
|
self.show_camera_toggle.toggled.connect(self._on_camera_toggle)
|
||||||
self._camera_win.uc480_close_window_btn.clicked.connect(
|
self._camera_win.closed.connect(
|
||||||
lambda: self.show_camera_toggle.setChecked(False)
|
lambda: self._set_toggle(self.show_camera_toggle, False, "Show Camera Window")
|
||||||
|
)
|
||||||
|
|
||||||
|
# Helios
|
||||||
|
self.show_helios_toggle.toggled.connect(self._on_helios_toggle)
|
||||||
|
self._helios_win.closed.connect(
|
||||||
|
lambda: self._set_toggle(self.show_helios_toggle, False, "Show Helios Panel")
|
||||||
|
)
|
||||||
|
self._helios_win.helios_close_btn.clicked.connect(
|
||||||
|
lambda: self.show_helios_toggle.setChecked(False)
|
||||||
)
|
)
|
||||||
|
|
||||||
# Scan
|
# Scan
|
||||||
@@ -1345,6 +1695,16 @@ class MainWindow(QMainWindow):
|
|||||||
self.show_camera_toggle.setText("Show Camera Window")
|
self.show_camera_toggle.setText("Show Camera Window")
|
||||||
self._camera_win.close()
|
self._camera_win.close()
|
||||||
|
|
||||||
|
# ── Helios toggle ─────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
def _on_helios_toggle(self, checked: bool):
|
||||||
|
if checked:
|
||||||
|
self.show_helios_toggle.setText("Hide Helios Panel")
|
||||||
|
self._helios_win.show()
|
||||||
|
else:
|
||||||
|
self.show_helios_toggle.setText("Show Helios Panel")
|
||||||
|
self._helios_win.close()
|
||||||
|
|
||||||
# ── Scan ──────────────────────────────────────────────────────────────────
|
# ── Scan ──────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
def _on_browse_save_dir(self):
|
def _on_browse_save_dir(self):
|
||||||
@@ -1459,7 +1819,10 @@ class MainWindow(QMainWindow):
|
|||||||
self._bbd_worker.queue_disconnect()
|
self._bbd_worker.queue_disconnect()
|
||||||
if self._oscope_worker.is_connected:
|
if self._oscope_worker.is_connected:
|
||||||
self._oscope_worker.queue_disconnect()
|
self._oscope_worker.queue_disconnect()
|
||||||
|
if self._helios_worker.is_connected:
|
||||||
|
self._helios_worker.queue_disconnect()
|
||||||
self._camera_win.close()
|
self._camera_win.close()
|
||||||
|
self._helios_win.close()
|
||||||
QTimer.singleShot(1500, QApplication.instance().quit)
|
QTimer.singleShot(1500, QApplication.instance().quit)
|
||||||
|
|
||||||
# ── Helpers ───────────────────────────────────────────────────────────────
|
# ── Helpers ───────────────────────────────────────────────────────────────
|
||||||
@@ -1478,9 +1841,10 @@ class MainWindow(QMainWindow):
|
|||||||
def closeEvent(self, event):
|
def closeEvent(self, event):
|
||||||
self._camera_win.close()
|
self._camera_win.close()
|
||||||
self._scan_progress.close()
|
self._scan_progress.close()
|
||||||
for w in (self._t3r_worker, self._bbd_worker, self._oscope_worker):
|
self._helios_win.close()
|
||||||
|
for w in (self._t3r_worker, self._bbd_worker, self._oscope_worker, self._helios_worker):
|
||||||
w.stop_worker()
|
w.stop_worker()
|
||||||
for t in (self._t3r_thread, self._bbd_thread, self._oscope_thread):
|
for t in (self._t3r_thread, self._bbd_thread, self._oscope_thread, self._helios_thread):
|
||||||
t.quit()
|
t.quit()
|
||||||
t.wait(2000)
|
t.wait(2000)
|
||||||
super().closeEvent(event)
|
super().closeEvent(event)
|
||||||
|
|||||||
Reference in New Issue
Block a user