Phase 1c: prune remaining dead functions, unused imports, quarantine Genesis
- uc480_camera: drop never-called _capture_paused/get_framerate (the hardware question _capture_paused encoded is now in KNOWN_ISSUES.md) - t3r_protocol: drop read_reg/write_reg/decode_reg/Reg (commands never wired into the driver) - bbd20x: drop _update0x0212 (never dispatched) and 8 of 9 unused trigger convenience wrappers; apt_constants: drop TriggerBitsStepper (servo-only rig) - ruff --fix: 35 unused imports across all apps; drop unused T3R_BAUD - genesis_core.py: quarantine warning header; docs/genesis_verification.md bench checklist for the 7 divergences vs tools/genesis_laser_gui.py Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,36 @@
|
|||||||
|
# Known issues requiring on-rig verification
|
||||||
|
|
||||||
|
Questions that cannot be answered from the code alone. Check these the next
|
||||||
|
time the hardware is available; each one gates a small code change.
|
||||||
|
|
||||||
|
## uC480 camera: gain/exposure during active capture
|
||||||
|
|
||||||
|
The driver used to carry an (unused) `_capture_paused` context manager whose
|
||||||
|
docstring claimed many IDS cameras return `IS_CANT_COMMUNICATE_WITH_DRIVER`
|
||||||
|
(17) or `IS_NO_SUCCESS` (-1) when gain/exposure commands are issued during
|
||||||
|
active capture. `set_exposure()` and `set_gain()` never used it, and the
|
||||||
|
helper was deleted in the Phase-1 cleanup.
|
||||||
|
|
||||||
|
**Bench check:** with live streaming running, move the exposure and gain
|
||||||
|
sliders in `camera_test_app.py` and watch the log for those error codes.
|
||||||
|
If they appear, the setters need a stop-live/apply/restart sequence
|
||||||
|
(re-create the helper around the two call sites in
|
||||||
|
[uc480_camera.py](hardware/uc480_camera.py)).
|
||||||
|
|
||||||
|
## Genesis laser: forked protocol implementations disagree
|
||||||
|
|
||||||
|
`hardware/genesis_core.py` and the reference implementation
|
||||||
|
`tools/genesis_laser_gui.py` disagree on ADC command bytes, LDD enable
|
||||||
|
polarity, shutter semantics, filtering, and scaling. Do not modify either
|
||||||
|
until the checklist in [docs/genesis_verification.md](docs/genesis_verification.md)
|
||||||
|
has been run on the bench.
|
||||||
|
|
||||||
|
## `lib/ueye_loader.so` — still needed?
|
||||||
|
|
||||||
|
`lib/ueye_loader.c` is an `LD_PRELOAD` shim that dlopens
|
||||||
|
`/usr/lib/libueye_api.so` — yet nothing in the repo references it, and the
|
||||||
|
vendored SDK copy is `lib/libueye_api64.so.3.82` (a different file). On the
|
||||||
|
rig, check whether the camera apps run without the shim; if they do, delete
|
||||||
|
`lib/ueye_loader.{c,so}`. Either way, record in SETUP.md where
|
||||||
|
`libueye_api64.so.3.82` came from (IDS SDK version) and how the loader is
|
||||||
|
meant to be used.
|
||||||
+2
-2
@@ -12,10 +12,10 @@ import time
|
|||||||
from PyQt6.QtWidgets import (
|
from PyQt6.QtWidgets import (
|
||||||
QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
|
QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
|
||||||
QGroupBox, QLabel, QLineEdit, QPushButton, QComboBox, QDoubleSpinBox,
|
QGroupBox, QLabel, QLineEdit, QPushButton, QComboBox, QDoubleSpinBox,
|
||||||
QStatusBar, QMessageBox, QGridLayout, QCheckBox, QFrame
|
QStatusBar, QMessageBox, QGridLayout
|
||||||
)
|
)
|
||||||
from PyQt6.QtCore import Qt, QThread, pyqtSignal, QObject, QTimer
|
from PyQt6.QtCore import Qt, QThread, pyqtSignal, QObject, QTimer
|
||||||
from PyQt6.QtGui import QFont, QKeySequence, QShortcut
|
from PyQt6.QtGui import QFont
|
||||||
|
|
||||||
from hardware.pybbd202 import ThorlabsServoDriver, AXIS_X, AXIS_Y
|
from hardware.pybbd202 import ThorlabsServoDriver, AXIS_X, AXIS_Y
|
||||||
from hardware.pybbd202.apt_constants import TriggerBitsServo
|
from hardware.pybbd202.apt_constants import TriggerBitsServo
|
||||||
|
|||||||
+2
-2
@@ -10,9 +10,9 @@ import logging
|
|||||||
from PyQt6.QtWidgets import (
|
from PyQt6.QtWidgets import (
|
||||||
QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
|
QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
|
||||||
QGroupBox, QLabel, QPushButton, QDoubleSpinBox, QSpinBox,
|
QGroupBox, QLabel, QPushButton, QDoubleSpinBox, QSpinBox,
|
||||||
QStatusBar, QSizePolicy
|
QSizePolicy
|
||||||
)
|
)
|
||||||
from PyQt6.QtCore import Qt, QTimer
|
from PyQt6.QtCore import Qt
|
||||||
from PyQt6.QtGui import QPixmap, QImage
|
from PyQt6.QtGui import QPixmap, QImage
|
||||||
|
|
||||||
from hardware.uc480_camera import UC480Camera, CameraStreamThread
|
from hardware.uc480_camera import UC480Camera, CameraStreamThread
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
# Genesis laser — hardware verification checklist
|
||||||
|
|
||||||
|
`hardware/genesis_core.py` was extracted from `tools/genesis_laser_gui.py`,
|
||||||
|
but the extraction changed behavior in ways only the bench can adjudicate.
|
||||||
|
Until every row below is resolved, **both files stay in the repo unchanged**:
|
||||||
|
`genesis_laser_gui.py` is the reference implementation, `genesis_core.py`
|
||||||
|
(+ `tools/genesis_laser_control.py`) is the intended successor.
|
||||||
|
|
||||||
|
Run these with the Genesis laser connected, interlock chain accessible, and
|
||||||
|
a front-panel/manual reference for current and temperature readouts.
|
||||||
|
|
||||||
|
| # | Divergence | Bench test | Resolution |
|
||||||
|
|---|---|---|---|
|
||||||
|
| 1 | **ADS7828 command byte.** Reference passes raw command bytes (`0x84`, `0xe4`, `0x94` — `genesis_laser_gui.py:73-77`); core synthesizes `0x80 \| (ch<<4) \| 0x0c` → `0x8C` for ch0 (`genesis_core.py:413`), different PD1/PD0 power-down bits. | Read the same ADC channel through both implementations; compare against the front-panel current readout. Also check for settling differences right after power-up. | Keep whichever matches the panel; fix the other. |
|
||||||
|
| 2 | **LDD enable polarity.** Reference `get_ldd_enable()` returns `not bool(value & 0x01)` ("Inverted logic", `genesis_laser_gui.py:602-612`); core returns the un-inverted bit (`genesis_core.py:585-599`). Same register, opposite answers. | With emission verifiably OFF (keyswitch off), read LDD status via both. Exactly one will say "disabled". | Adopt the polarity that matches reality; document the register semantics inline. |
|
||||||
|
| 3 | **Shutter: manual or bit-controlled?** Reference docs say "this laser has a MANUAL shutter" and `emergency_stop()` deliberately leaves it alone; core `set_shutter()` toggles a PCA9555 bit and `emergency_stop()`/`enter_safe_state()` rely on it. | Toggle `set_shutter()` from core with the beam blocked; observe whether anything physical actuates. | If the bit is inert, remove `set_shutter` and fix the safe-state functions; if real, correct the `tools/` docs. |
|
||||||
|
| 4 | **ADC filtering dropped.** Reference reads 3× and takes median (`i2c_read_discard_high_low`, `genesis_laser_gui.py:341-364`) or retries until two reads agree; core does single unfiltered reads. | Log ~100 consecutive current readings through core; if the spread is more than display noise, filtering was load-bearing. | Port the median-of-3 helper into `genesis_core.I2CProtocol`. |
|
||||||
|
| 5 | **Scaling dropped.** Reference converts to Amps/Watts (`AMPS_FULLSCALE * ADC_TO_VOLTS`); core returns raw 0–4095 counts. | Compare a scaled reading against the front panel. | Port the scaling constants + conversion into core. |
|
||||||
|
| 6 | **Temperatures + power monitoring dropped.** `get_main_temp` / `get_etalon_temp` / `get_shg_temp` / `get_power_actual` exist only in the reference. | Confirm each channel's reading is sane vs. front panel. | Port the four getters into core. |
|
||||||
|
| 7 | **`pre_flight_check()` dropped.** Reference validates remote-enable + keyswitch + interlock before emission. | n/a — code review + one interlock-open test. | Port into core; call it from `genesis_laser_control.py` before enabling. |
|
||||||
|
|
||||||
|
When all rows are resolved: port the verified behavior into
|
||||||
|
`genesis_core.py`, update `tools/genesis_laser_control.py`, delete
|
||||||
|
`tools/genesis_laser_gui.py`, and remove this checklist plus the warning
|
||||||
|
header in `genesis_core.py`.
|
||||||
@@ -2,6 +2,16 @@
|
|||||||
Genesis SLM MX 532 Laser Core Hardware Control Module
|
Genesis SLM MX 532 Laser Core Hardware Control Module
|
||||||
======================================================
|
======================================================
|
||||||
|
|
||||||
|
.. warning::
|
||||||
|
QUARANTINED — do not modify semantics or dedupe against
|
||||||
|
``tools/genesis_laser_gui.py`` until the bench checklist in
|
||||||
|
``docs/genesis_verification.md`` has been run. This module was
|
||||||
|
extracted from that GUI but diverges from it in ways only hardware can
|
||||||
|
adjudicate: ADS7828 command byte (0x84 vs 0x8C), LDD enable polarity
|
||||||
|
(inverted vs not), shutter semantics (manual vs bit-controlled),
|
||||||
|
dropped median-of-3 ADC filtering, dropped Amps/Watts scaling, dropped
|
||||||
|
temperature reads and pre-flight check.
|
||||||
|
|
||||||
This module provides low-level hardware control for the Genesis SLM MX 532 laser
|
This module provides low-level hardware control for the Genesis SLM MX 532 laser
|
||||||
using NXP I2C-over-serial protocol. It contains reusable classes for serial
|
using NXP I2C-over-serial protocol. It contains reusable classes for serial
|
||||||
communication, I2C protocol handling, device control, and laser operations.
|
communication, I2C protocol handling, device control, and laser operations.
|
||||||
@@ -34,7 +44,6 @@ from typing import Optional, List
|
|||||||
from enum import IntEnum
|
from enum import IntEnum
|
||||||
|
|
||||||
import serial
|
import serial
|
||||||
from serial.tools import list_ports
|
|
||||||
|
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
"""pybbd202 - Thorlabs BBD202 servo stage driver (pyserial-based)"""
|
"""pybbd202 - Thorlabs BBD202 servo stage driver (pyserial-based)"""
|
||||||
|
|
||||||
from .bbd20x import ThorlabsServoDriver
|
from .bbd20x import ThorlabsServoDriver
|
||||||
from .apt_constants import TriggerBitsServo, StatusBits
|
|
||||||
|
|
||||||
# Axis address constants
|
# Axis address constants
|
||||||
AXIS_X = 0x21
|
AXIS_X = 0x21
|
||||||
AXIS_Y = 0x22
|
AXIS_Y = 0x22
|
||||||
CONTROLLER = 0x11
|
|
||||||
|
|||||||
@@ -32,16 +32,6 @@ class StatusBits(IntFlag):
|
|||||||
MOT_SB_COMMUTATIONERROR | MOT_SB_OVERLOAD |
|
MOT_SB_COMMUTATIONERROR | MOT_SB_OVERLOAD |
|
||||||
MOT_SB_ERROR | MOT_SB_INSTRERROR)
|
MOT_SB_ERROR | MOT_SB_INSTRERROR)
|
||||||
|
|
||||||
class TriggerBitsStepper(IntFlag):
|
|
||||||
TRIGIN_ENABLE = 0x01,
|
|
||||||
TRIGOUT_ENABLE = 0x02,
|
|
||||||
TRIGOUT_MODEFOLLOW = 0x04,
|
|
||||||
TRIGOUT_MODEMOVEEND = 0x08,
|
|
||||||
TRIG_RELMOVE = 0x10,
|
|
||||||
TRIG_ABSMOVE = 0x20,
|
|
||||||
TRIG_HOMEMOVE = 0x40,
|
|
||||||
TRIGOUT_NOTRIGIN = 0x80
|
|
||||||
|
|
||||||
class TriggerBitsServo(IntFlag):
|
class TriggerBitsServo(IntFlag):
|
||||||
TRIGIN_HIGH = 0x01
|
TRIGIN_HIGH = 0x01
|
||||||
TRIGIN_RELMOVE = 0x02
|
TRIGIN_RELMOVE = 0x02
|
||||||
|
|||||||
@@ -4,7 +4,6 @@
|
|||||||
Version 1
|
Version 1
|
||||||
'''
|
'''
|
||||||
import struct
|
import struct
|
||||||
from .apt_constants import StatusBits as sb
|
|
||||||
|
|
||||||
class APTProtocol():
|
class APTProtocol():
|
||||||
ADDRESSES = { 'HOST_PC': 0x01, 'CONTROLLER': 0x11,
|
ADDRESSES = { 'HOST_PC': 0x01, 'CONTROLLER': 0x11,
|
||||||
|
|||||||
@@ -288,25 +288,6 @@ class ThorlabsServoDriver():
|
|||||||
self.am_moving[ch] = False
|
self.am_moving[ch] = False
|
||||||
return
|
return
|
||||||
|
|
||||||
def _update0x0212(self, msg):
|
|
||||||
'''
|
|
||||||
_update0x0212 - internal function that listens for CHANENABLESTATE
|
|
||||||
messages.
|
|
||||||
'''
|
|
||||||
if msg['source'] == 0x21:
|
|
||||||
ch = 0
|
|
||||||
elif msg['source'] == 0x22:
|
|
||||||
ch = 1
|
|
||||||
else:
|
|
||||||
raise ValueError("Wherever this message came from, it's WRONG!")
|
|
||||||
|
|
||||||
if msg['enable_state'] == 0x01:
|
|
||||||
self.am_enabled[ch] = True # enabled
|
|
||||||
elif msg['enable_state'] == 0x02:
|
|
||||||
self.am_enabled[ch] = False # disabled
|
|
||||||
else:
|
|
||||||
raise ValueError("Am I a joke to you? WTF did this even come from?!")
|
|
||||||
|
|
||||||
# ── Axis control ─────────────────────────────────────────────
|
# ── Axis control ─────────────────────────────────────────────
|
||||||
|
|
||||||
def enable_axis(self, axis):
|
def enable_axis(self, axis):
|
||||||
@@ -473,38 +454,6 @@ class ThorlabsServoDriver():
|
|||||||
destination=axis, source=0x01)
|
destination=axis, source=0x01)
|
||||||
return TriggerBitsServo(result['mode'])
|
return TriggerBitsServo(result['mode'])
|
||||||
|
|
||||||
def set_trigger_trigin_high(self, axis):
|
|
||||||
'''Set trigger input to logic high.'''
|
|
||||||
self.set_trigger(axis, TriggerBitsServo.TRIGIN_HIGH)
|
|
||||||
|
|
||||||
def set_trigger_trigin_relmove(self, axis):
|
|
||||||
'''Set trigger input to initiate a relative move.'''
|
|
||||||
self.set_trigger(axis, TriggerBitsServo.TRIGIN_RELMOVE)
|
|
||||||
|
|
||||||
def set_trigger_trigin_absmove(self, axis):
|
|
||||||
'''Set trigger input to initiate an absolute move.'''
|
|
||||||
self.set_trigger(axis, TriggerBitsServo.TRIGIN_ABSMOVE)
|
|
||||||
|
|
||||||
def set_trigger_trigin_homemove(self, axis):
|
|
||||||
'''Set trigger input to initiate a home move.'''
|
|
||||||
self.set_trigger(axis, TriggerBitsServo.TRIGIN_HOMEMOVE)
|
|
||||||
|
|
||||||
def set_trigger_trigout_high(self, axis):
|
|
||||||
'''Set trigger output to logic high.'''
|
|
||||||
self.set_trigger(axis, TriggerBitsServo.TRIGOUT_HIGH)
|
|
||||||
|
|
||||||
def set_trigger_trigout_inmotion(self, axis):
|
|
||||||
'''Set trigger output high while axis is in motion.'''
|
|
||||||
self.set_trigger(axis, TriggerBitsServo.TRIGOUT_INMOTION)
|
|
||||||
|
|
||||||
def set_trigger_trigout_motioncomplete(self, axis):
|
|
||||||
'''Set trigger output to pulse when motion completes.'''
|
|
||||||
self.set_trigger(axis, TriggerBitsServo.TRIGOUT_MOTIONCOMPLETE)
|
|
||||||
|
|
||||||
def set_trigger_trigout_maxvelocity(self, axis):
|
|
||||||
'''Set trigger output to pulse at max velocity.'''
|
|
||||||
self.set_trigger(axis, TriggerBitsServo.TRIGOUT_MAXVELOCITY)
|
|
||||||
|
|
||||||
def set_trigger_trigout_maxv(self, axis):
|
def set_trigger_trigout_maxv(self, axis):
|
||||||
'''Set trigger output high + pulse at max velocity (TRIGOUT_MAXV).'''
|
'''Set trigger output high + pulse at max velocity (TRIGOUT_MAXV).'''
|
||||||
self.set_trigger(axis, TriggerBitsServo.TRIGOUT_MAXV)
|
self.set_trigger(axis, TriggerBitsServo.TRIGOUT_MAXV)
|
||||||
|
|||||||
@@ -193,14 +193,6 @@ def set_position(ch: int, position: int) -> bytes:
|
|||||||
return build_frame(CMD_SET_POSITION, struct.pack("<Bi", ch, position))
|
return build_frame(CMD_SET_POSITION, struct.pack("<Bi", ch, position))
|
||||||
|
|
||||||
|
|
||||||
def read_reg(ch: int, reg: int) -> bytes:
|
|
||||||
return build_frame(CMD_READ_REG, struct.pack("<BB", ch, reg))
|
|
||||||
|
|
||||||
|
|
||||||
def write_reg(ch: int, reg: int, value: int) -> bytes:
|
|
||||||
return build_frame(CMD_WRITE_REG, struct.pack("<BBI", ch, reg, value))
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Response / event decoders. Each returns a dataclass (or None on bad length).
|
# Response / event decoders. Each returns a dataclass (or None on bad length).
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
@@ -261,13 +253,6 @@ class Position:
|
|||||||
position: int
|
position: int
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class Reg:
|
|
||||||
ch: int
|
|
||||||
reg: int
|
|
||||||
value: int
|
|
||||||
|
|
||||||
|
|
||||||
def decode_pong(p: bytes):
|
def decode_pong(p: bytes):
|
||||||
if len(p) < 4:
|
if len(p) < 4:
|
||||||
return None
|
return None
|
||||||
@@ -304,13 +289,6 @@ def decode_position(p: bytes):
|
|||||||
return Position(ch, pos)
|
return Position(ch, pos)
|
||||||
|
|
||||||
|
|
||||||
def decode_reg(p: bytes):
|
|
||||||
if len(p) < 6:
|
|
||||||
return None
|
|
||||||
ch, reg, value = struct.unpack_from("<BBI", p, 0)
|
|
||||||
return Reg(ch, reg, value)
|
|
||||||
|
|
||||||
|
|
||||||
def decode_event_position(p: bytes):
|
def decode_event_position(p: bytes):
|
||||||
"""MOTION_DONE / STOPPED share the (ch, position) layout."""
|
"""MOTION_DONE / STOPPED share the (ch, position) layout."""
|
||||||
return decode_position(p)
|
return decode_position(p)
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ from PyQt6.QtCore import QThread, pyqtSignal, QObject
|
|||||||
from PyQt6.QtGui import QImage
|
from PyQt6.QtGui import QImage
|
||||||
import logging
|
import logging
|
||||||
import threading
|
import threading
|
||||||
from contextlib import contextmanager
|
|
||||||
from typing import List, Optional, Tuple
|
from typing import List, Optional, Tuple
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -331,29 +330,6 @@ class UC480Camera(QObject):
|
|||||||
logger.info("Video capture stopped")
|
logger.info("Video capture stopped")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@contextmanager
|
|
||||||
def _capture_paused(self):
|
|
||||||
"""
|
|
||||||
Context manager that temporarily stops live video while a camera
|
|
||||||
parameter is being changed, then restarts it. Many IDS cameras
|
|
||||||
return IS_CANT_COMMUNICATE_WITH_DRIVER (17) or IS_NO_SUCCESS (-1)
|
|
||||||
when gain/exposure commands are issued during active capture.
|
|
||||||
"""
|
|
||||||
with self._settings_lock:
|
|
||||||
was_capturing = self.is_capturing
|
|
||||||
if was_capturing:
|
|
||||||
ueye.is_StopLiveVideo(self.h_cam, ueye.IS_WAIT)
|
|
||||||
self.is_capturing = False
|
|
||||||
try:
|
|
||||||
yield
|
|
||||||
finally:
|
|
||||||
if was_capturing:
|
|
||||||
ret = ueye.is_CaptureVideo(self.h_cam, ueye.IS_DONT_WAIT)
|
|
||||||
if ret == ueye.IS_SUCCESS:
|
|
||||||
self.is_capturing = True
|
|
||||||
else:
|
|
||||||
logger.error(f"Failed to restart capture after settings change: {ret}")
|
|
||||||
|
|
||||||
def get_frame(self) -> Optional[QImage]:
|
def get_frame(self) -> Optional[QImage]:
|
||||||
"""
|
"""
|
||||||
Capture a single frame from the camera.
|
Capture a single frame from the camera.
|
||||||
@@ -535,24 +511,6 @@ class UC480Camera(QObject):
|
|||||||
logger.error(f"Failed to set framerate: {ret}")
|
logger.error(f"Failed to set framerate: {ret}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def get_framerate(self) -> Optional[float]:
|
|
||||||
"""
|
|
||||||
Get current framerate.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Framerate in fps, or None if failed
|
|
||||||
"""
|
|
||||||
if not self.is_initialized:
|
|
||||||
return None
|
|
||||||
|
|
||||||
fps = ueye.c_double()
|
|
||||||
ret = ueye.is_GetFramesPerSecond(self.h_cam, fps)
|
|
||||||
|
|
||||||
if ret == ueye.IS_SUCCESS:
|
|
||||||
return fps.value
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
def set_gain(self, master_gain: int) -> bool:
|
def set_gain(self, master_gain: int) -> bool:
|
||||||
"""
|
"""
|
||||||
Set camera master gain.
|
Set camera master gain.
|
||||||
|
|||||||
+2
-4
@@ -6,15 +6,13 @@ Simple PyQt6 GUI for testing and controlling the Helios laser.
|
|||||||
|
|
||||||
import sys
|
import sys
|
||||||
import logging
|
import logging
|
||||||
from typing import Optional
|
|
||||||
from enum import Enum
|
|
||||||
|
|
||||||
from PyQt6.QtWidgets import (
|
from PyQt6.QtWidgets import (
|
||||||
QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
|
QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
|
||||||
QGroupBox, QLabel, QLineEdit, QPushButton, QComboBox, QSpinBox,
|
QGroupBox, QLabel, QLineEdit, QPushButton, QComboBox, QSpinBox,
|
||||||
QStatusBar, QMessageBox, QTabWidget, QTextEdit
|
QMessageBox, QTabWidget, QTextEdit
|
||||||
)
|
)
|
||||||
from PyQt6.QtCore import Qt, QThread, pyqtSignal, QObject
|
from PyQt6.QtCore import QThread, pyqtSignal, QObject
|
||||||
from PyQt6.QtGui import QFont
|
from PyQt6.QtGui import QFont
|
||||||
|
|
||||||
from hardware.helios_laser import HeliosLaser, PulseMode
|
from hardware.helios_laser import HeliosLaser, PulseMode
|
||||||
|
|||||||
+1
-3
@@ -16,10 +16,9 @@ import threading
|
|||||||
from threading import Thread
|
from threading import Thread
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import serial
|
|
||||||
from PyQt6 import uic
|
from PyQt6 import uic
|
||||||
from PyQt6.QtCore import QObject, QThread, QTimer, Qt, pyqtSignal, pyqtSlot
|
from PyQt6.QtCore import QObject, QThread, QTimer, Qt, pyqtSignal, pyqtSlot
|
||||||
from PyQt6.QtGui import QImage, QPixmap, QPainter, QColor, QPen
|
from PyQt6.QtGui import QImage, QPixmap
|
||||||
from PyQt6.QtWidgets import (
|
from PyQt6.QtWidgets import (
|
||||||
QApplication, QDialog, QDialogButtonBox, QFileDialog, QHBoxLayout, QLabel,
|
QApplication, QDialog, QDialogButtonBox, QFileDialog, QHBoxLayout, QLabel,
|
||||||
QListWidget, QListWidgetItem, QMainWindow, QMessageBox, QPushButton,
|
QListWidget, QListWidgetItem, QMainWindow, QMessageBox, QPushButton,
|
||||||
@@ -93,7 +92,6 @@ SCAN_RAMP_MM = SCAN_VELOCITY_MM_S**2 / (2.0 * SCAN_ACCEL_MM_S2)
|
|||||||
SCAN_RAMP_BUFFER_MM = 1.0
|
SCAN_RAMP_BUFFER_MM = 1.0
|
||||||
SCOPE_SAMPLE_RATE = 6.25e9 # 6.25 GS/s → 160 ps/sample
|
SCOPE_SAMPLE_RATE = 6.25e9 # 6.25 GS/s → 160 ps/sample
|
||||||
SCOPE_TRIG_LEVEL_V = 0.500
|
SCOPE_TRIG_LEVEL_V = 0.500
|
||||||
T3R_BAUD = 115200
|
|
||||||
|
|
||||||
# GR-axis rotation defaults (used by ScanWorker between angles)
|
# GR-axis rotation defaults (used by ScanWorker between angles)
|
||||||
GR_MICROSTEPS = 8 # microsteps/full-step on GR axis (ch3)
|
GR_MICROSTEPS = 8 # microsteps/full-step on GR axis (ch3)
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ Only format version 6 is supported.
|
|||||||
import argparse
|
import argparse
|
||||||
import struct
|
import struct
|
||||||
import sys
|
import sys
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ Usage::
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from PyQt6.QtCore import Qt, QTimer
|
from PyQt6.QtCore import Qt
|
||||||
from PyQt6.QtGui import QFont
|
from PyQt6.QtGui import QFont
|
||||||
from PyQt6.QtWidgets import (
|
from PyQt6.QtWidgets import (
|
||||||
QCheckBox, QComboBox, QDialog, QDoubleSpinBox, QFrame, QGridLayout,
|
QCheckBox, QComboBox, QDialog, QDoubleSpinBox, QFrame, QGridLayout,
|
||||||
|
|||||||
@@ -31,26 +31,19 @@ Date: 2026-01-24
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
import time
|
|
||||||
import struct
|
|
||||||
from datetime import datetime
|
|
||||||
from typing import Optional, List, Tuple
|
|
||||||
from enum import IntEnum
|
|
||||||
|
|
||||||
import serial
|
|
||||||
from serial.tools import list_ports
|
from serial.tools import list_ports
|
||||||
from PyQt6.QtWidgets import (
|
from PyQt6.QtWidgets import (
|
||||||
QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
|
QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
|
||||||
QTabWidget, QLabel, QSlider, QPushButton, QSpinBox, QCheckBox,
|
QTabWidget, QLabel, QSlider, QPushButton, QSpinBox, QCheckBox,
|
||||||
QComboBox, QTextEdit, QLineEdit, QGroupBox, QGridLayout,
|
QComboBox, QTextEdit, QLineEdit, QGroupBox, QGridLayout,
|
||||||
QMessageBox, QStatusBar, QProgressBar
|
QMessageBox, QStatusBar
|
||||||
)
|
)
|
||||||
from PyQt6.QtCore import Qt, QTimer, pyqtSignal, QSettings
|
from PyQt6.QtCore import Qt, QTimer, QSettings
|
||||||
from PyQt6.QtGui import QFont, QPalette, QColor
|
from PyQt6.QtGui import QFont
|
||||||
|
|
||||||
# Import core hardware control classes
|
# Import core hardware control classes
|
||||||
from hardware.genesis_core import (
|
from hardware.genesis_core import (
|
||||||
I2CAddress, PCA9555Register, ControlBitmask,
|
|
||||||
SerialComm, I2CProtocol, I2CDevices, LaserControl
|
SerialComm, I2CProtocol, I2CDevices, LaserControl
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -39,7 +39,6 @@ from PyQt6.QtWidgets import (
|
|||||||
QLineEdit, QTextEdit, QCheckBox, QMessageBox, QGroupBox, QGridLayout
|
QLineEdit, QTextEdit, QCheckBox, QMessageBox, QGroupBox, QGridLayout
|
||||||
)
|
)
|
||||||
from PyQt6.QtCore import QObject, pyqtSignal, QTimer, Qt
|
from PyQt6.QtCore import QObject, pyqtSignal, QTimer, Qt
|
||||||
from PyQt6.QtGui import QPalette, QColor
|
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# CONSTANTS
|
# CONSTANTS
|
||||||
|
|||||||
Reference in New Issue
Block a user