when'd i last commit this pos?

This commit is contained in:
Thomas Ales [M S E]
2026-02-09 14:40:34 -06:00
parent fc43fbe4b0
commit 23f6331ba2
94 changed files with 14427 additions and 12178 deletions
+828
View File
@@ -0,0 +1,828 @@
#!/usr/bin/env python3
"""
Genesis SLM MX 532 Laser Control Application
=============================================
This application provides comprehensive control of a Genesis SLM MX 532 laser
over serial port using NXP I2C-over-serial protocol.
Protocol Overview:
-----------------
The laser uses NXP I2C-over-serial protocol with packet format:
[0x53] [I2C_ADDR] [LENGTH] [COMMAND_BYTES] [DATA_BYTES] [0x50]
For reads:
[0x53] [ADDR_WRITE] [CMD_LEN] [CMD] [0x53] [ADDR_READ] [DATA_LEN] [0x50]
I2C Devices:
-----------
- X9119 digital potentiometer at 0x152 - laser current control
- PCA9555 I/O expander at 0x14a - digital I/O
- AD5254 digital potentiometer at 0x158 - limits control
- ADS7828 ADC at 0x190 - sensor readings
- M24C64 EEPROM at 0x2a4 - configuration storage
Usage:
------
python genesis_laser_control.py
Author: Claude
Date: 2026-01-24
"""
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 PyQt6.QtWidgets import (
QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
QTabWidget, QLabel, QSlider, QPushButton, QSpinBox, QCheckBox,
QComboBox, QTextEdit, QLineEdit, QGroupBox, QGridLayout,
QMessageBox, QStatusBar, QProgressBar
)
from PyQt6.QtCore import Qt, QTimer, pyqtSignal, QSettings
from PyQt6.QtGui import QFont, QPalette, QColor
# Import core hardware control classes
from hardware.genesis_core import (
I2CAddress, PCA9555Register, ControlBitmask,
SerialComm, I2CProtocol, I2CDevices, LaserControl
)
# ============================================================================
# Main Window GUI
# ============================================================================
class MainWindow(QMainWindow):
"""Main application window with tabbed interface"""
def __init__(self):
super().__init__()
# Initialize backend
self.serial_comm = SerialComm()
self.i2c_protocol = I2CProtocol(self.serial_comm)
self.i2c_devices = I2CDevices(self.i2c_protocol)
self.laser_control = LaserControl(self.i2c_devices)
# Settings for persistence
self.settings = QSettings("Genesis", "LaserControl")
# Monitoring timer
self.monitor_timer = QTimer()
self.monitor_timer.timeout.connect(self.update_monitoring)
self.monitor_enabled = False
# Initialize UI
self.init_ui()
self.apply_stylesheet()
# Restore settings
self.restore_settings()
# Initial state: all controls disabled
self.update_control_states()
def init_ui(self):
"""Initialize user interface"""
self.setWindowTitle("Genesis SLM MX 532 Laser Control")
self.setMinimumSize(800, 600)
# Central widget with tabs
central_widget = QWidget()
self.setCentralWidget(central_widget)
layout = QVBoxLayout(central_widget)
# Tab widget
self.tabs = QTabWidget()
layout.addWidget(self.tabs)
# Create tabs
self.tab_basic = self.create_basic_controls_tab()
self.tab_monitoring = self.create_monitoring_tab()
self.tab_advanced = self.create_advanced_tab()
self.tab_config = self.create_configuration_tab()
self.tabs.addTab(self.tab_basic, "Basic Controls")
self.tabs.addTab(self.tab_monitoring, "Monitoring")
self.tabs.addTab(self.tab_advanced, "Advanced")
self.tabs.addTab(self.tab_config, "Configuration")
# Status bar
self.status_bar = QStatusBar()
self.setStatusBar(self.status_bar)
self.status_bar.showMessage("Disconnected")
def create_basic_controls_tab(self) -> QWidget:
"""Create basic controls tab"""
tab = QWidget()
layout = QVBoxLayout(tab)
# Current control group
current_group = QGroupBox("Current Control")
current_layout = QGridLayout()
current_layout.addWidget(QLabel("Current Command (0-1023):"), 0, 0)
self.current_slider = QSlider(Qt.Orientation.Horizontal)
self.current_slider.setMinimum(0)
self.current_slider.setMaximum(1023)
self.current_slider.setValue(0)
self.current_slider.setToolTip("Set laser current (0-1023)")
self.current_slider.valueChanged.connect(self.on_current_changed)
current_layout.addWidget(self.current_slider, 0, 1)
self.current_spinbox = QSpinBox()
self.current_spinbox.setMinimum(0)
self.current_spinbox.setMaximum(1023)
self.current_spinbox.setValue(0)
self.current_spinbox.valueChanged.connect(self.current_slider.setValue)
current_layout.addWidget(self.current_spinbox, 0, 2)
self.current_percent_label = QLabel("0.0%")
current_layout.addWidget(self.current_percent_label, 0, 3)
current_group.setLayout(current_layout)
layout.addWidget(current_group)
# Power control group
power_group = QGroupBox("Power Control")
power_layout = QGridLayout()
power_layout.addWidget(QLabel("Power Command (0-1023):"), 0, 0)
self.power_slider = QSlider(Qt.Orientation.Horizontal)
self.power_slider.setMinimum(0)
self.power_slider.setMaximum(1023)
self.power_slider.setValue(0)
self.power_slider.setToolTip("Set laser power command (0-1023)")
self.power_slider.valueChanged.connect(self.on_power_changed)
power_layout.addWidget(self.power_slider, 0, 1)
self.power_spinbox = QSpinBox()
self.power_spinbox.setMinimum(0)
self.power_spinbox.setMaximum(1023)
self.power_spinbox.setValue(0)
self.power_spinbox.valueChanged.connect(self.power_slider.setValue)
power_layout.addWidget(self.power_spinbox, 0, 2)
power_group.setLayout(power_layout)
layout.addWidget(power_group)
# Toggle controls group
toggle_group = QGroupBox("Digital Controls")
toggle_layout = QGridLayout()
self.shutter_btn = QPushButton("Shutter: CLOSED")
self.shutter_btn.setCheckable(True)
self.shutter_btn.setToolTip("Open/close laser shutter")
self.shutter_btn.clicked.connect(self.on_shutter_toggled)
toggle_layout.addWidget(self.shutter_btn, 0, 0)
self.keyswitch_btn = QPushButton("Keyswitch: OFF")
self.keyswitch_btn.setCheckable(True)
self.keyswitch_btn.setToolTip("Enable/disable keyswitch")
self.keyswitch_btn.clicked.connect(self.on_keyswitch_toggled)
toggle_layout.addWidget(self.keyswitch_btn, 0, 1)
self.remote_enable_btn = QPushButton("Remote: DISABLED")
self.remote_enable_btn.setCheckable(True)
self.remote_enable_btn.setToolTip("Enable/disable remote control")
self.remote_enable_btn.clicked.connect(self.on_remote_enable_toggled)
toggle_layout.addWidget(self.remote_enable_btn, 1, 0)
self.analog_enable_btn = QPushButton("Analog Input: DISABLED")
self.analog_enable_btn.setCheckable(True)
self.analog_enable_btn.setToolTip("Enable/disable analog input")
self.analog_enable_btn.clicked.connect(self.on_analog_enable_toggled)
toggle_layout.addWidget(self.analog_enable_btn, 1, 1)
self.current_mode_btn = QPushButton("Current Mode: OFF")
self.current_mode_btn.setCheckable(True)
self.current_mode_btn.setToolTip("Enable/disable current mode")
self.current_mode_btn.clicked.connect(self.on_current_mode_toggled)
toggle_layout.addWidget(self.current_mode_btn, 2, 0)
toggle_group.setLayout(toggle_layout)
layout.addWidget(toggle_group)
# Emergency stop
self.emergency_stop_btn = QPushButton("EMERGENCY STOP")
self.emergency_stop_btn.setStyleSheet("background-color: #ff0000; color: white; font-weight: bold; font-size: 14pt;")
self.emergency_stop_btn.clicked.connect(self.on_emergency_stop)
self.emergency_stop_btn.setEnabled(True) # Always enabled
layout.addWidget(self.emergency_stop_btn)
layout.addStretch()
return tab
def create_monitoring_tab(self) -> QWidget:
"""Create monitoring tab"""
tab = QWidget()
layout = QVBoxLayout(tab)
# Auto-refresh control
refresh_layout = QHBoxLayout()
self.auto_refresh_check = QCheckBox("Auto-refresh")
self.auto_refresh_check.stateChanged.connect(self.on_auto_refresh_toggled)
refresh_layout.addWidget(self.auto_refresh_check)
refresh_layout.addWidget(QLabel("Interval (ms):"))
self.refresh_interval_spin = QSpinBox()
self.refresh_interval_spin.setMinimum(100)
self.refresh_interval_spin.setMaximum(5000)
self.refresh_interval_spin.setValue(500)
self.refresh_interval_spin.setSingleStep(100)
refresh_layout.addWidget(self.refresh_interval_spin)
self.refresh_now_btn = QPushButton("Refresh Now")
self.refresh_now_btn.clicked.connect(self.update_monitoring)
refresh_layout.addWidget(self.refresh_now_btn)
refresh_layout.addStretch()
layout.addLayout(refresh_layout)
# Readings group
readings_group = QGroupBox("Sensor Readings")
readings_layout = QGridLayout()
readings_layout.addWidget(QLabel("Actual Current:"), 0, 0)
self.current_actual_label = QLabel("---")
readings_layout.addWidget(self.current_actual_label, 0, 1)
readings_layout.addWidget(QLabel("Interlock Status:"), 1, 0)
self.interlock_label = QLabel("---")
readings_layout.addWidget(self.interlock_label, 1, 1)
readings_layout.addWidget(QLabel("LDD Enable:"), 2, 0)
self.ldd_enable_label = QLabel("---")
readings_layout.addWidget(self.ldd_enable_label, 2, 1)
readings_layout.addWidget(QLabel("PS Glue In:"), 3, 0)
self.psglue_in_label = QLabel("---")
readings_layout.addWidget(self.psglue_in_label, 3, 1)
readings_layout.addWidget(QLabel("PS Glue Out:"), 4, 0)
self.psglue_out_label = QLabel("---")
readings_layout.addWidget(self.psglue_out_label, 4, 1)
readings_layout.addWidget(QLabel("Head DIO:"), 5, 0)
self.head_dio_label = QLabel("---")
readings_layout.addWidget(self.head_dio_label, 5, 1)
readings_group.setLayout(readings_layout)
layout.addWidget(readings_group)
# Laser info group
info_group = QGroupBox("Laser Information")
info_layout = QGridLayout()
info_layout.addWidget(QLabel("Model:"), 0, 0)
self.laser_model_label = QLabel("Genesis SLM MX 532")
info_layout.addWidget(self.laser_model_label, 0, 1)
info_layout.addWidget(QLabel("Wavelength:"), 1, 0)
self.wavelength_label = QLabel("532 nm")
info_layout.addWidget(self.wavelength_label, 1, 1)
info_group.setLayout(info_layout)
layout.addWidget(info_group)
layout.addStretch()
return tab
def create_advanced_tab(self) -> QWidget:
"""Create advanced tab"""
tab = QWidget()
layout = QVBoxLayout(tab)
# Raw I2C packet sender
packet_group = QGroupBox("Raw I2C Packet Sender")
packet_layout = QGridLayout()
packet_layout.addWidget(QLabel("I2C Address (hex):"), 0, 0)
self.i2c_addr_edit = QLineEdit("52")
self.i2c_addr_edit.setMaximumWidth(100)
packet_layout.addWidget(self.i2c_addr_edit, 0, 1)
packet_layout.addWidget(QLabel("Command (hex):"), 1, 0)
self.i2c_cmd_edit = QLineEdit("a0")
self.i2c_cmd_edit.setMaximumWidth(200)
packet_layout.addWidget(self.i2c_cmd_edit, 1, 1)
packet_layout.addWidget(QLabel("Data (hex):"), 2, 0)
self.i2c_data_edit = QLineEdit("00 00")
packet_layout.addWidget(self.i2c_data_edit, 2, 1)
self.send_packet_btn = QPushButton("Send Packet")
self.send_packet_btn.clicked.connect(self.on_send_raw_packet)
packet_layout.addWidget(self.send_packet_btn, 3, 0, 1, 2)
self.packet_response_label = QLabel("Response: ---")
packet_layout.addWidget(self.packet_response_label, 4, 0, 1, 2)
packet_group.setLayout(packet_layout)
layout.addWidget(packet_group)
# Packet log
log_group = QGroupBox("Packet Capture Log")
log_layout = QVBoxLayout()
log_controls = QHBoxLayout()
self.clear_log_btn = QPushButton("Clear Log")
self.clear_log_btn.clicked.connect(self.on_clear_log)
log_controls.addWidget(self.clear_log_btn)
log_controls.addStretch()
log_layout.addLayout(log_controls)
self.packet_log_text = QTextEdit()
self.packet_log_text.setReadOnly(True)
self.packet_log_text.setFont(QFont("Courier", 9))
log_layout.addWidget(self.packet_log_text)
log_group.setLayout(log_layout)
layout.addWidget(log_group)
# Update log periodically
self.log_timer = QTimer()
self.log_timer.timeout.connect(self.update_packet_log)
self.log_timer.start(1000) # Update every second
return tab
def create_configuration_tab(self) -> QWidget:
"""Create configuration tab"""
tab = QWidget()
layout = QVBoxLayout(tab)
# Serial port configuration
serial_group = QGroupBox("Serial Port Configuration")
serial_layout = QGridLayout()
serial_layout.addWidget(QLabel("Port:"), 0, 0)
self.port_combo = QComboBox()
self.refresh_ports()
serial_layout.addWidget(self.port_combo, 0, 1)
self.refresh_ports_btn = QPushButton("Refresh Ports")
self.refresh_ports_btn.clicked.connect(self.refresh_ports)
serial_layout.addWidget(self.refresh_ports_btn, 0, 2)
serial_layout.addWidget(QLabel("Baud Rate:"), 1, 0)
self.baudrate_combo = QComboBox()
self.baudrate_combo.addItems(["9600", "19200", "38400", "57600", "115200"])
self.baudrate_combo.setCurrentText("9600")
serial_layout.addWidget(self.baudrate_combo, 1, 1)
self.connect_btn = QPushButton("Connect")
self.connect_btn.clicked.connect(self.on_connect_clicked)
serial_layout.addWidget(self.connect_btn, 2, 0, 1, 3)
serial_group.setLayout(serial_layout)
layout.addWidget(serial_group)
# DTR/RTS control
control_group = QGroupBox("Serial Control Lines")
control_layout = QVBoxLayout()
self.dtr_check = QCheckBox("DTR (Data Terminal Ready)")
self.dtr_check.stateChanged.connect(self.on_dtr_changed)
control_layout.addWidget(self.dtr_check)
self.rts_check = QCheckBox("RTS (Request To Send)")
self.rts_check.stateChanged.connect(self.on_rts_changed)
control_layout.addWidget(self.rts_check)
control_group.setLayout(control_layout)
layout.addWidget(control_group)
# About section
about_group = QGroupBox("About")
about_layout = QVBoxLayout()
about_text = QLabel(
"Genesis SLM MX 532 Laser Control\n\n"
"Protocol: NXP I2C-over-serial\n"
"Baud Rate: 9600 (default)\n\n"
"This application provides comprehensive control of the Genesis laser\n"
"using I2C devices over serial communication."
)
about_layout.addWidget(about_text)
about_group.setLayout(about_layout)
layout.addWidget(about_group)
layout.addStretch()
return tab
# ------------------------------------------------------------------------
# Event Handlers - Basic Controls
# ------------------------------------------------------------------------
def on_current_changed(self, value: int):
"""Handle current slider change"""
self.current_spinbox.setValue(value)
percent = (value / 1023.0) * 100.0
self.current_percent_label.setText(f"{percent:.1f}%")
if self.serial_comm.is_connected():
if self.laser_control.set_current(value):
self.status_bar.showMessage(f"Current set to {value} ({percent:.1f}%)", 2000)
def on_power_changed(self, value: int):
"""Handle power slider change"""
self.power_spinbox.setValue(value)
if self.serial_comm.is_connected():
if self.laser_control.set_power_cmd(value):
self.status_bar.showMessage(f"Power command set to {value}", 2000)
def on_shutter_toggled(self, checked: bool):
"""Handle shutter toggle"""
if checked and self.current_slider.value() > 0:
reply = QMessageBox.question(
self,
"Confirm Shutter Open",
"Current is set above 0. Are you sure you want to open the shutter?",
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No
)
if reply == QMessageBox.StandardButton.No:
self.shutter_btn.setChecked(False)
return
if self.laser_control.set_shutter(checked):
self.shutter_btn.setText("Shutter: OPEN" if checked else "Shutter: CLOSED")
self.status_bar.showMessage(f"Shutter {'opened' if checked else 'closed'}", 2000)
else:
self.shutter_btn.setChecked(not checked)
def on_keyswitch_toggled(self, checked: bool):
"""Handle keyswitch toggle"""
if self.laser_control.set_keyswitch(checked):
self.keyswitch_btn.setText("Keyswitch: ON" if checked else "Keyswitch: OFF")
self.status_bar.showMessage(f"Keyswitch {'enabled' if checked else 'disabled'}", 2000)
else:
self.keyswitch_btn.setChecked(not checked)
def on_remote_enable_toggled(self, checked: bool):
"""Handle remote enable toggle"""
if self.laser_control.set_remote_enable(checked):
self.remote_enable_btn.setText("Remote: ENABLED" if checked else "Remote: DISABLED")
self.status_bar.showMessage(f"Remote control {'enabled' if checked else 'disabled'}", 2000)
else:
self.remote_enable_btn.setChecked(not checked)
def on_analog_enable_toggled(self, checked: bool):
"""Handle analog enable toggle"""
if self.laser_control.set_analog_enable(checked):
self.analog_enable_btn.setText("Analog Input: ENABLED" if checked else "Analog Input: DISABLED")
self.status_bar.showMessage(f"Analog input {'enabled' if checked else 'disabled'}", 2000)
else:
self.analog_enable_btn.setChecked(not checked)
def on_current_mode_toggled(self, checked: bool):
"""Handle current mode toggle"""
if self.laser_control.set_current_mode(checked):
self.current_mode_btn.setText("Current Mode: ON" if checked else "Current Mode: OFF")
self.status_bar.showMessage(f"Current mode {'enabled' if checked else 'disabled'}", 2000)
else:
self.current_mode_btn.setChecked(not checked)
def on_emergency_stop(self):
"""Handle emergency stop button"""
if self.laser_control.emergency_stop():
# Reset UI to safe state
self.current_slider.setValue(0)
self.power_slider.setValue(0)
self.shutter_btn.setChecked(False)
self.shutter_btn.setText("Shutter: CLOSED")
self.keyswitch_btn.setChecked(False)
self.keyswitch_btn.setText("Keyswitch: OFF")
self.status_bar.showMessage("EMERGENCY STOP ACTIVATED", 5000)
QMessageBox.warning(self, "Emergency Stop", "Emergency stop activated!\nLaser is now in safe state.")
# ------------------------------------------------------------------------
# Event Handlers - Monitoring
# ------------------------------------------------------------------------
def on_auto_refresh_toggled(self, state):
"""Handle auto-refresh toggle"""
if state:
interval = self.refresh_interval_spin.value()
self.monitor_timer.start(interval)
self.monitor_enabled = True
else:
self.monitor_timer.stop()
self.monitor_enabled = False
def update_monitoring(self):
"""Update monitoring readings"""
if not self.serial_comm.is_connected():
return
# Read actual current
current = self.laser_control.get_current_actual()
if current is not None:
self.current_actual_label.setText(f"{current} counts")
else:
self.current_actual_label.setText("Error reading")
# Read interlock status
interlock = self.laser_control.get_interlock_status()
if interlock is not None:
if interlock:
self.interlock_label.setText("OK")
self.interlock_label.setStyleSheet("color: green; font-weight: bold;")
else:
self.interlock_label.setText("FAULT")
self.interlock_label.setStyleSheet("color: red; font-weight: bold;")
else:
self.interlock_label.setText("Error reading")
self.interlock_label.setStyleSheet("")
# Read LDD enable
ldd = self.laser_control.get_ldd_enable_status()
if ldd is not None:
self.ldd_enable_label.setText("Enabled" if ldd else "Disabled")
else:
self.ldd_enable_label.setText("Error reading")
# Read PS glue status
psglue_in = self.laser_control.get_psglue_in_status()
if psglue_in is not None:
self.psglue_in_label.setText(f"0x{psglue_in:02x}")
else:
self.psglue_in_label.setText("Error reading")
psglue_out = self.laser_control.get_psglue_out_status()
if psglue_out is not None:
self.psglue_out_label.setText(f"0x{psglue_out:02x}")
else:
self.psglue_out_label.setText("Error reading")
# Read head DIO
head_dio = self.laser_control.get_head_dio_status()
if head_dio is not None:
self.head_dio_label.setText(f"0x{head_dio:02x}")
else:
self.head_dio_label.setText("Error reading")
# ------------------------------------------------------------------------
# Event Handlers - Advanced
# ------------------------------------------------------------------------
def on_send_raw_packet(self):
"""Handle raw packet send"""
if not self.serial_comm.is_connected():
QMessageBox.warning(self, "Not Connected", "Please connect to serial port first.")
return
try:
# Parse inputs
addr = int(self.i2c_addr_edit.text(), 16)
cmd_hex = self.i2c_cmd_edit.text().replace(" ", "")
cmd = bytes.fromhex(cmd_hex)
data_hex = self.i2c_data_edit.text().replace(" ", "")
data = bytes.fromhex(data_hex) if data_hex else b''
# Send packet
if self.i2c_protocol.write(addr, cmd, data):
self.packet_response_label.setText("Response: Packet sent successfully")
self.status_bar.showMessage("Raw packet sent", 2000)
else:
self.packet_response_label.setText("Response: Send failed")
except ValueError as e:
QMessageBox.warning(self, "Invalid Input", f"Invalid hex value: {e}")
def on_clear_log(self):
"""Handle clear log button"""
self.serial_comm.clear_packet_log()
self.packet_log_text.clear()
def update_packet_log(self):
"""Update packet log display"""
if not self.serial_comm.is_connected():
return
log_entries = self.serial_comm.get_packet_log(100)
if log_entries:
self.packet_log_text.setPlainText("\n".join(log_entries))
# Scroll to bottom
scrollbar = self.packet_log_text.verticalScrollBar()
scrollbar.setValue(scrollbar.maximum())
# ------------------------------------------------------------------------
# Event Handlers - Configuration
# ------------------------------------------------------------------------
def on_connect_clicked(self):
"""Handle connect/disconnect button"""
if self.serial_comm.is_connected():
# Disconnect
self.laser_control.enter_safe_state()
self.serial_comm.disconnect()
self.connect_btn.setText("Connect")
self.status_bar.showMessage("Disconnected")
self.update_control_states()
else:
# Connect
port = self.port_combo.currentText()
baudrate = int(self.baudrate_combo.currentText())
if self.serial_comm.connect(port, baudrate):
self.connect_btn.setText("Disconnect")
self.status_bar.showMessage(f"Connected to {port} at {baudrate} baud")
self.update_control_states()
else:
QMessageBox.critical(self, "Connection Error", f"Failed to connect to {port}")
def on_dtr_changed(self, state):
"""Handle DTR checkbox change"""
if self.serial_comm.is_connected():
self.serial_comm.port.dtr = bool(state)
def on_rts_changed(self, state):
"""Handle RTS checkbox change"""
if self.serial_comm.is_connected():
self.serial_comm.port.rts = bool(state)
def refresh_ports(self):
"""Refresh available serial ports"""
self.port_combo.clear()
ports = list_ports.comports()
for port in ports:
self.port_combo.addItem(port.device)
# Add default if no ports found
if self.port_combo.count() == 0:
self.port_combo.addItem("/dev/ttyUSB0")
# ------------------------------------------------------------------------
# UI Update Functions
# ------------------------------------------------------------------------
def update_control_states(self):
"""Enable/disable controls based on connection state"""
connected = self.serial_comm.is_connected()
# Basic controls
self.current_slider.setEnabled(connected)
self.current_spinbox.setEnabled(connected)
self.power_slider.setEnabled(connected)
self.power_spinbox.setEnabled(connected)
self.shutter_btn.setEnabled(connected)
self.keyswitch_btn.setEnabled(connected)
self.remote_enable_btn.setEnabled(connected)
self.analog_enable_btn.setEnabled(connected)
self.current_mode_btn.setEnabled(connected)
# Monitoring controls
self.auto_refresh_check.setEnabled(connected)
self.refresh_now_btn.setEnabled(connected)
# Advanced controls
self.send_packet_btn.setEnabled(connected)
# Configuration controls
self.port_combo.setEnabled(not connected)
self.baudrate_combo.setEnabled(not connected)
self.dtr_check.setEnabled(connected)
self.rts_check.setEnabled(connected)
def apply_stylesheet(self):
"""Apply custom stylesheet for professional look"""
self.setStyleSheet("""
QGroupBox {
font-weight: bold;
border: 2px solid #cccccc;
border-radius: 5px;
margin-top: 10px;
padding-top: 10px;
}
QGroupBox::title {
subcontrol-origin: margin;
left: 10px;
padding: 0 5px 0 5px;
}
QPushButton {
padding: 5px 10px;
border-radius: 3px;
background-color: #e0e0e0;
}
QPushButton:hover {
background-color: #d0d0d0;
}
QPushButton:pressed {
background-color: #c0c0c0;
}
QPushButton:disabled {
background-color: #f0f0f0;
color: #a0a0a0;
}
QPushButton:checked {
background-color: #4CAF50;
color: white;
}
QSlider::groove:horizontal {
border: 1px solid #999999;
height: 8px;
background: #e0e0e0;
margin: 2px 0;
border-radius: 4px;
}
QSlider::handle:horizontal {
background: #4CAF50;
border: 1px solid #5c5c5c;
width: 18px;
margin: -5px 0;
border-radius: 9px;
}
""")
# ------------------------------------------------------------------------
# Settings Persistence
# ------------------------------------------------------------------------
def save_settings(self):
"""Save window geometry and settings"""
self.settings.setValue("geometry", self.saveGeometry())
self.settings.setValue("port", self.port_combo.currentText())
self.settings.setValue("baudrate", self.baudrate_combo.currentText())
def restore_settings(self):
"""Restore window geometry and settings"""
geometry = self.settings.value("geometry")
if geometry:
self.restoreGeometry(geometry)
port = self.settings.value("port")
if port:
index = self.port_combo.findText(port)
if index >= 0:
self.port_combo.setCurrentIndex(index)
baudrate = self.settings.value("baudrate")
if baudrate:
index = self.baudrate_combo.findText(baudrate)
if index >= 0:
self.baudrate_combo.setCurrentIndex(index)
def closeEvent(self, event):
"""Handle window close event"""
if self.serial_comm.is_connected():
reply = QMessageBox.question(
self,
"Confirm Exit",
"Laser is connected. Enter safe state and disconnect?",
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No
)
if reply == QMessageBox.StandardButton.Yes:
self.laser_control.enter_safe_state()
self.serial_comm.disconnect()
else:
event.ignore()
return
self.save_settings()
event.accept()
# ============================================================================
# Main Entry Point
# ============================================================================
def main():
"""Main application entry point"""
app = QApplication(sys.argv)
app.setApplicationName("Genesis Laser Control")
app.setOrganizationName("Genesis")
window = MainWindow()
window.show()
sys.exit(app.exec())
if __name__ == "__main__":
main()
+1445
View File
File diff suppressed because it is too large Load Diff