From 88ef718f316b4860accee051b778413d49c019ce Mon Sep 17 00:00:00 2001 From: awe Date: Mon, 24 Nov 2025 22:21:30 +0300 Subject: [PATCH] =?UTF-8?q?=D0=B2=D1=80=D0=BE=D0=B4=D0=B5=20=D0=B2=D0=BE?= =?UTF-8?q?=D1=80=D0=BA=D0=B0=D0=B5=D1=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- vna_system/api/endpoints/laser.py | 88 ++-- vna_system/api/models/laser.py | 17 + vna_system/core/laser/laser_controller.py | 397 +++++++++++++++--- vna_system/core/singletons.py | 6 +- vna_system/main.py | 19 + .../web_ui/static/js/modules/constants.js | 1 + .../js/modules/settings/laser-manager.js | 66 ++- 7 files changed, 491 insertions(+), 103 deletions(-) diff --git a/vna_system/api/endpoints/laser.py b/vna_system/api/endpoints/laser.py index 49b48ef..6dae658 100644 --- a/vna_system/api/endpoints/laser.py +++ b/vna_system/api/endpoints/laser.py @@ -6,24 +6,21 @@ from ...api.models.laser import ( LaserParameters, LaserStatus, LaserStartResponse, - LaserStopResponse + LaserStopResponse, + ManualLaserParameters, + ManualLaserStartResponse ) -from ...core.laser import LaserController +from ...core.laser.laser_controller import LaserController +from ...core import singletons logger = logging.getLogger(__name__) router = APIRouter(prefix="/api/v1/laser", tags=["laser"]) -# Singleton laser controller instance -_laser_controller: LaserController = None - -def get_laser_controller() -> LaserController: - """Dependency to get laser controller instance""" - global _laser_controller - if _laser_controller is None: - _laser_controller = LaserController() - return _laser_controller +def get_laser_controller(): + """Dependency to get laser controller instance from singletons""" + return singletons.laser_controller_instance @router.post("/start", response_model=LaserStartResponse) @@ -40,19 +37,8 @@ async def start_laser_cycle( try: logger.info("Received request to start laser cycle") - # Validate that at least one control mode is enabled - if not any([ - parameters.enable_t1, - parameters.enable_t2, - parameters.enable_c1, - parameters.enable_c2 - ]): - raise HTTPException( - status_code=400, - detail="Необходимо включить хотя бы один режим управления (температура или ток)" - ) - - # Validate that only one mode is enabled at a time + # Validate that only one scan mode is enabled at a time + # (Manual mode = all disabled, Scan mode = one enabled) enabled_modes = sum([ parameters.enable_t1, parameters.enable_t2, @@ -62,7 +48,7 @@ async def start_laser_cycle( if enabled_modes > 1: raise HTTPException( status_code=400, - detail="Можно включить только один режим управления одновременно" + detail="Можно включить только один режим сканирования одновременно" ) # Start the cycle @@ -84,6 +70,51 @@ async def start_laser_cycle( raise HTTPException(status_code=500, detail=f"Внутренняя ошибка сервера: {str(e)}") +@router.post("/start-manual", response_model=ManualLaserStartResponse) +async def start_manual_laser_control( + parameters: ManualLaserParameters, + controller: LaserController = Depends(get_laser_controller) +) -> ManualLaserStartResponse: + """ + Start manual laser control with simplified parameters (t1, t2, i1, i2). + + This endpoint is designed for direct manual control without scan modes. + It accepts only 4 parameters and immediately applies them to the device. + + Args: + parameters: Manual control parameters with t1, t2, i1, i2 + + Returns: + ManualLaserStartResponse with success status and message + """ + try: + logger.info("Received request to start manual laser control") + logger.info(f"Parameters: T1={parameters.t1}°C, T2={parameters.t2}°C, I1={parameters.i1}mA, I2={parameters.i2}mA") + + # Call the simplified manual control method + result = controller.start_manual_direct( + t1=parameters.t1, + t2=parameters.t2, + i1=parameters.i1, + i2=parameters.i2 + ) + + if not result["success"]: + raise HTTPException(status_code=500, detail=result["message"]) + + return ManualLaserStartResponse( + success=True, + message=result["message"], + parameters=parameters + ) + + except HTTPException: + raise + except Exception as e: + logger.error(f"Unexpected error starting manual laser control: {e}", exc_info=True) + raise HTTPException(status_code=500, detail=f"Внутренняя ошибка сервера: {str(e)}") + + @router.post("/stop", response_model=LaserStopResponse) async def stop_laser_cycle( controller: LaserController = Depends(get_laser_controller) @@ -139,7 +170,12 @@ async def connect_laser( port: Serial port (e.g., '/dev/ttyUSB0'). If not specified, auto-detect. """ try: - logger.info(f"Received request to connect to laser hardware on port: {port}") + logger.info(f"Received request to connect to laser hardware on port: {port or 'auto-detect'}") + + # If already connected, disconnect first + if controller.is_connected: + logger.info("Already connected, disconnecting first...") + controller.disconnect() result = controller.connect(port) diff --git a/vna_system/api/models/laser.py b/vna_system/api/models/laser.py index 5a1c835..17167e5 100644 --- a/vna_system/api/models/laser.py +++ b/vna_system/api/models/laser.py @@ -101,3 +101,20 @@ class LaserStopResponse(BaseModel): success: bool = Field(..., description="Успешность операции") message: str = Field(..., description="Сообщение о результате") + + +class ManualLaserParameters(BaseModel): + """Model for manual laser control with simple parameters""" + + t1: float = Field(..., ge=-1, le=45, description="Температура лазера 1 (°C)") + t2: float = Field(..., ge=-1, le=45, description="Температура лазера 2 (°C)") + i1: float = Field(..., ge=15, le=70, description="Ток лазера 1 (мА)") + i2: float = Field(..., ge=15, le=60, description="Ток лазера 2 (мА)") + + +class ManualLaserStartResponse(BaseModel): + """Response model for manual laser start endpoint""" + + success: bool = Field(..., description="Успешность операции") + message: str = Field(..., description="Сообщение о результате") + parameters: Optional[ManualLaserParameters] = Field(None, description="Примененные параметры") diff --git a/vna_system/core/laser/laser_controller.py b/vna_system/core/laser/laser_controller.py index 3f85ef3..2385ede 100644 --- a/vna_system/core/laser/laser_controller.py +++ b/vna_system/core/laser/laser_controller.py @@ -1,8 +1,11 @@ import logging +import time from typing import Optional, Dict, Any from datetime import datetime from ...api.models.laser import LaserParameters, LaserStatus +from .RadioPhotonic_PCB_PC_software import device_interaction as dev +from .RadioPhotonic_PCB_PC_software import device_commands as cmd logger = logging.getLogger(__name__) @@ -11,22 +14,35 @@ class LaserController: """ Controller for laser control system. - This is a stub implementation that logs all actions. - Future integration with actual hardware would use serial communication - similar to the RadioPhotonic_PCB_PC_software project. + Communicates with RadioPhotonic board via serial port (115200 baud). + Supports both manual control and automated scanning modes. """ def __init__(self): + self.prt = None # Serial port object self.is_connected = False self.is_running = False self.current_parameters: Optional[LaserParameters] = None self.current_status = LaserStatus() + self.last_data: Optional[Dict[str, Any]] = None + + # Default PI coefficients (multiplied by 256 as per device protocol) + self.proportional_coeff_1 = int(10 * 256) + self.proportional_coeff_2 = int(10 * 256) + self.integral_coeff_1 = int(0.5 * 256) + self.integral_coeff_2 = int(0.5 * 256) + self.message_id = "00FF" + logger.info("LaserController initialized") def start_cycle(self, parameters: LaserParameters) -> Dict[str, Any]: """ Start laser control cycle with given parameters. + Determines mode (manual vs scan) based on enable flags: + - Manual mode: all enable_* flags are False + - Scan mode: one enable_* flag is True + Args: parameters: LaserParameters object with control settings @@ -34,54 +50,40 @@ class LaserController: Dictionary with success status and message """ try: + if not self.is_connected or self.prt is None: + return { + "success": False, + "message": "Устройство не подключено. Сначала выполните подключение.", + "parameters": None + } + logger.info("=" * 60) logger.info("LASER CONTROL: START CYCLE") logger.info(f"Timestamp: {datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')}") logger.info("-" * 60) - # Log all parameters - logger.info("Laser 1 Temperature Parameters:") - logger.info(f" Min Temperature: {parameters.min_temp_1}°C") - logger.info(f" Max Temperature: {parameters.max_temp_1}°C") - logger.info(f" Delta Temperature: {parameters.delta_temp_1}°C") + # Determine operation mode + is_manual = not any([ + parameters.enable_t1, + parameters.enable_t2, + parameters.enable_c1, + parameters.enable_c2 + ]) - logger.info("Laser 1 Current Parameters:") - logger.info(f" Min Current: {parameters.min_current_1} mA") - logger.info(f" Max Current: {parameters.max_current_1} mA") - logger.info(f" Delta Current: {parameters.delta_current_1} mA") + if is_manual: + logger.info("Mode: MANUAL CONTROL") + result = self._start_manual_mode(parameters) + else: + logger.info("Mode: AUTOMATED SCAN") + result = self._start_scan_mode(parameters) - logger.info("Laser 2 Temperature Parameters:") - logger.info(f" Min Temperature: {parameters.min_temp_2}°C") - logger.info(f" Max Temperature: {parameters.max_temp_2}°C") - logger.info(f" Delta Temperature: {parameters.delta_temp_2}°C") - - logger.info("Laser 2 Current Parameters:") - logger.info(f" Min Current: {parameters.min_current_2} mA") - logger.info(f" Max Current: {parameters.max_current_2} mA") - logger.info(f" Delta Current: {parameters.delta_current_2} mA") - - logger.info("Time Parameters:") - logger.info(f" Delta Time: {parameters.delta_time} μs") - logger.info(f" Tau (Delay): {parameters.tau} ms") - - logger.info("Control Mode Flags:") - logger.info(f" Enable Temperature Control Laser 1: {parameters.enable_t1}") - logger.info(f" Enable Temperature Control Laser 2: {parameters.enable_t2}") - logger.info(f" Enable Current Control Laser 1: {parameters.enable_c1}") - logger.info(f" Enable Current Control Laser 2: {parameters.enable_c2}") + if result["success"]: + self.current_parameters = parameters + self.is_running = True + self.current_status.is_running = True logger.info("=" * 60) - - # Store current parameters - self.current_parameters = parameters - self.is_running = True - self.current_status.is_running = True - - return { - "success": True, - "message": "Цикл управления лазером успешно запущен (stub mode - без реального устройства)", - "parameters": parameters.model_dump() - } + return result except Exception as e: logger.error(f"Error starting laser cycle: {e}", exc_info=True) @@ -91,9 +93,124 @@ class LaserController: "parameters": None } + def _start_manual_mode(self, parameters: LaserParameters) -> Dict[str, Any]: + """ + Start manual control mode with fixed T1, T2, I1, I2 values. + Uses DECODE_ENABLE (0x1111) command. + """ + try: + # Prepare control parameters + params = { + 'Temp_1': parameters.min_temp_1, + 'Temp_2': parameters.min_temp_2, + 'Iset_1': parameters.min_current_1, + 'Iset_2': parameters.min_current_2, + 'ProportionalCoeff_1': self.proportional_coeff_1, + 'ProportionalCoeff_2': self.proportional_coeff_2, + 'IntegralCoeff_1': self.integral_coeff_1, + 'IntegralCoeff_2': self.integral_coeff_2, + 'Message_ID': self.message_id + } + + logger.info(f"Sending manual control parameters:") + logger.info(f" T1: {params['Temp_1']}°C, T2: {params['Temp_2']}°C") + logger.info(f" I1: {params['Iset_1']} mA, I2: {params['Iset_2']} mA") + + # Send control parameters to device + dev.send_control_parameters(self.prt, params) + + return { + "success": True, + "message": "Ручное управление запущено", + "parameters": parameters.model_dump() + } + + except Exception as e: + logger.error(f"Error in manual mode: {e}", exc_info=True) + raise + + def _start_scan_mode(self, parameters: LaserParameters) -> Dict[str, Any]: + """ + Start automated scan mode. + Uses TASK_ENABLE (0x7777) command with TaskType. + """ + try: + # Determine which parameter to scan + if parameters.enable_c1: + task_type = cmd.TaskType.ChangeCurrentLD1.value + scan_param = "Current Laser 1" + logger.info(f"Scanning Current Laser 1: {parameters.min_current_1} to {parameters.max_current_1} mA") + elif parameters.enable_c2: + task_type = cmd.TaskType.ChangeCurrentLD2.value + scan_param = "Current Laser 2" + logger.info(f"Scanning Current Laser 2: {parameters.min_current_2} to {parameters.max_current_2} mA") + elif parameters.enable_t1: + return { + "success": False, + "message": "Сканирование температуры Laser 1 не поддерживается устройством", + "parameters": None + } + elif parameters.enable_t2: + return { + "success": False, + "message": "Сканирование температуры Laser 2 не поддерживается устройством", + "parameters": None + } + else: + return { + "success": False, + "message": "Не выбран параметр для сканирования", + "parameters": None + } + + # Build task parameters based on task type + sending_param = { + 'TaskType': task_type, + 'Dt': parameters.delta_time / 1000.0, # Convert μs to ms + 'Tau': parameters.tau, + 'ProportionalCoeff_1': self.proportional_coeff_1, + 'ProportionalCoeff_2': self.proportional_coeff_2, + 'IntegralCoeff_1': self.integral_coeff_1, + 'IntegralCoeff_2': self.integral_coeff_2 + } + + # Add scan-specific parameters + if task_type == cmd.TaskType.ChangeCurrentLD1.value: + sending_param.update({ + 'MinC1': parameters.min_current_1, + 'MaxC1': parameters.max_current_1, + 'DeltaC1': parameters.delta_current_1, + 'T1': parameters.min_temp_1, # Fixed + 'I2': parameters.min_current_2, # Fixed + 'T2': parameters.min_temp_2 # Fixed + }) + elif task_type == cmd.TaskType.ChangeCurrentLD2.value: + sending_param.update({ + 'MinC2': parameters.min_current_2, + 'MaxC2': parameters.max_current_2, + 'DeltaC2': parameters.delta_current_2, + 'T2': parameters.min_temp_2, # Fixed + 'I1': parameters.min_current_1, # Fixed + 'T1': parameters.min_temp_1 # Fixed + }) + + # Send task command to device + dev.send_task_command(self.prt, sending_param) + + return { + "success": True, + "message": f"Сканирование запущено: {scan_param}", + "parameters": parameters.model_dump() + } + + except Exception as e: + logger.error(f"Error in scan mode: {e}", exc_info=True) + raise + def stop_cycle(self) -> Dict[str, Any]: """ Stop current laser control cycle. + Sends reset command to device. Returns: Dictionary with success status and message @@ -104,6 +221,14 @@ class LaserController: logger.info(f"Timestamp: {datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')}") logger.info("=" * 60) + if self.is_connected and self.prt is not None: + # Send reset command to device + try: + dev.reset_port_settings(self.prt) + logger.info("Device reset command sent") + except Exception as e: + logger.warning(f"Failed to send reset command: {e}") + self.is_running = False self.current_status.is_running = False self.current_parameters = None @@ -120,39 +245,131 @@ class LaserController: "message": f"Ошибка при остановке цикла: {str(e)}" } + def start_manual_direct(self, t1: float, t2: float, i1: float, i2: float) -> Dict[str, Any]: + """ + Start manual control mode with direct T1, T2, I1, I2 parameters. + Simplified interface for manual control. + + Args: + t1: Temperature for Laser 1 in Celsius + t2: Temperature for Laser 2 in Celsius + i1: Current for Laser 1 in mA + i2: Current for Laser 2 in mA + + Returns: + Dictionary with success status and message + """ + try: + # Check connection status + if not self.is_connected: + logger.error("Device not connected") + return { + "success": False, + "message": "Устройство не подключено. Нажмите кнопку 'Подключить' в настройках.", + } + + if self.prt is None: + logger.error("Serial port is None") + return { + "success": False, + "message": "Порт не инициализирован. Переподключите устройство.", + } + + # Check if port is actually open + if not self.prt.is_open: + logger.error("Serial port is not open") + return { + "success": False, + "message": "Порт не открыт. Переподключите устройство.", + } + + logger.info("=" * 60) + logger.info("LASER CONTROL: START MANUAL MODE (Direct)") + logger.info(f"Timestamp: {datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')}") + logger.info(f"Port: {self.prt.port}, Open: {self.prt.is_open}") + logger.info(f" T1: {t1}°C, T2: {t2}°C") + logger.info(f" I1: {i1} mA, I2: {i2} mA") + logger.info("=" * 60) + + # Prepare control parameters + params = { + 'Temp_1': t1, + 'Temp_2': t2, + 'Iset_1': i1, + 'Iset_2': i2, + 'ProportionalCoeff_1': self.proportional_coeff_1, + 'ProportionalCoeff_2': self.proportional_coeff_2, + 'IntegralCoeff_1': self.integral_coeff_1, + 'IntegralCoeff_2': self.integral_coeff_2, + 'Message_ID': self.message_id + } + + # Send control parameters to device + logger.info("Sending control parameters to device...") + dev.send_control_parameters(self.prt, params) + logger.info("Control parameters sent successfully") + + self.is_running = True + self.current_status.is_running = True + + return { + "success": True, + "message": "Ручное управление запущено", + } + + except Exception as e: + logger.error(f"Error starting manual control: {e}", exc_info=True) + # Reset connection status if error + self.is_connected = False + return { + "success": False, + "message": f"Ошибка при запуске: {str(e)}. Попробуйте переподключить устройство.", + } + def get_status(self) -> LaserStatus: """ - Get current laser status. + Get current laser status by requesting data from device. + Uses TRANS_ENABLE (0x4444) command. Returns: LaserStatus object with current state """ - # In real implementation, this would query the hardware - # For now, return stub data self.current_status.connected = self.is_connected self.current_status.is_running = self.is_running - # Stub values (would come from actual hardware) - if self.is_connected: - self.current_status.temp_1 = 28.0 - self.current_status.temp_2 = 28.9 - self.current_status.current_1 = -0.02 - self.current_status.current_2 = -0.02 - self.current_status.temp_ext_1 = 30.95 - self.current_status.temp_ext_2 = 29.58 - self.current_status.voltage_3v3 = 3.30 - self.current_status.voltage_5v1 = 4.92 - self.current_status.voltage_5v2 = 4.96 - self.current_status.voltage_7v0 = 7.57 + if self.is_connected and self.prt is not None: + try: + # Request data from device + data = dev.request_data(self.prt) + + if data and isinstance(data, dict): + # Update status from device data + self.last_data = data + self.current_status.temp_1 = data.get('Temp_1', 0.0) + self.current_status.temp_2 = data.get('Temp_2', 0.0) + self.current_status.current_1 = data.get('I1', 0.0) + self.current_status.current_2 = data.get('I2', 0.0) + self.current_status.temp_ext_1 = data.get('Temp_Ext_1', 0.0) + self.current_status.temp_ext_2 = data.get('Temp_Ext_2', 0.0) + self.current_status.voltage_3v3 = data.get('MON_3V3', 0.0) + self.current_status.voltage_5v1 = data.get('MON_5V1', 0.0) + self.current_status.voltage_5v2 = data.get('MON_5V2', 0.0) + self.current_status.voltage_7v0 = data.get('MON_7V0', 0.0) + + except Exception as e: + logger.warning(f"Error requesting status from device: {e}") + # Keep previous status values on error return self.current_status def connect(self, port: Optional[str] = None) -> Dict[str, Any]: """ - Connect to laser control hardware. + Connect to laser control hardware via serial port. + Auto-detects USB serial ports if port not specified. Args: port: Serial port to connect to (e.g., '/dev/ttyUSB0') + If None, will auto-detect USB ports Returns: Dictionary with success status and message @@ -160,21 +377,63 @@ class LaserController: try: logger.info(f"Attempting to connect to laser hardware on port: {port or 'auto-detect'}") - # In real implementation, would use serial communication here - # For now, just simulate connection + if self.is_connected: + logger.warning("Already connected to device") + return { + "success": True, + "message": "Уже подключено к устройству", + "port": str(self.prt.port) if self.prt else "unknown" + } + + # Create port connection (auto-detect if port not specified) + if port: + # Manual port specification + try: + self.prt = cmd.setup_port_connection(port=port, baudrate=115200, timeout_sec=1) + cmd.open_port(self.prt) + dev.reset_port_settings(self.prt) + except Exception as e: + logger.error(f"Failed to connect to specified port {port}: {e}") + return { + "success": False, + "message": f"Не удалось подключиться к порту {port}: {str(e)}", + "port": port + } + else: + # Auto-detect USB ports + self.prt = dev.create_port_connection() + + if self.prt is None: + logger.error("Failed to create port connection") + return { + "success": False, + "message": "Не удалось найти устройство. Проверьте подключение USB.", + "port": None + } + self.is_connected = True self.current_status.connected = True - logger.info("Successfully connected to laser hardware (stub mode)") + port_name = self.prt.port if hasattr(self.prt, 'port') else "unknown" + logger.info(f"Successfully connected to laser hardware on {port_name}") + + # Request initial status + try: + time.sleep(0.2) # Give device time to initialize + self.get_status() + except Exception as e: + logger.warning(f"Failed to get initial status: {e}") return { "success": True, - "message": f"Подключено к устройству (stub mode)", - "port": port or "auto-detected" + "message": f"Подключено к устройству на порту {port_name}", + "port": port_name } except Exception as e: logger.error(f"Error connecting to laser hardware: {e}", exc_info=True) + self.is_connected = False + self.prt = None return { "success": False, "message": f"Ошибка подключения: {str(e)}", @@ -184,6 +443,7 @@ class LaserController: def disconnect(self) -> Dict[str, Any]: """ Disconnect from laser control hardware. + Stops any running cycle and closes serial port. Returns: Dictionary with success status and message @@ -195,8 +455,19 @@ class LaserController: if self.is_running: self.stop_cycle() + # Close serial port + if self.prt is not None: + try: + cmd.close_port(self.prt) + logger.info("Serial port closed") + except Exception as e: + logger.warning(f"Error closing serial port: {e}") + + self.prt = None + self.is_connected = False self.current_status.connected = False + self.last_data = None logger.info("Successfully disconnected from laser hardware") diff --git a/vna_system/core/singletons.py b/vna_system/core/singletons.py index c712912..7e4c475 100644 --- a/vna_system/core/singletons.py +++ b/vna_system/core/singletons.py @@ -11,6 +11,7 @@ from vna_system.core.processors.storage.data_storage import DataStorage from vna_system.core.settings.settings_manager import VNASettingsManager from vna_system.core.processors.manager import ProcessorManager from vna_system.core.processors.websocket_handler import ProcessorWebSocketHandler +from vna_system.core.laser.laser_controller import LaserController from vna_system.core.config import PROCESSORS_CONFIG_DIR_PATH # Global singleton instances @@ -22,4 +23,7 @@ processor_manager: ProcessorManager = ProcessorManager(vna_data_acquisition_inst data_storage = DataStorage() processor_websocket_handler: ProcessorWebSocketHandler = ProcessorWebSocketHandler( processor_manager, data_storage -) \ No newline at end of file +) + +# Laser control system +laser_controller_instance: LaserController = LaserController() \ No newline at end of file diff --git a/vna_system/main.py b/vna_system/main.py index fedc157..6e75d78 100644 --- a/vna_system/main.py +++ b/vna_system/main.py @@ -40,6 +40,17 @@ async def lifespan(app: FastAPI): processors=singletons.processor_manager.list_processors(), ) + # Try to connect to laser controller (optional, non-blocking) + logger.info("Attempting to connect to laser control hardware") + try: + result = singletons.laser_controller_instance.connect() + if result["success"]: + logger.info("Laser controller connected", port=result.get("port")) + else: + logger.warning("Laser controller connection failed (will retry on demand)", message=result.get("message")) + except Exception as e: + logger.warning("Failed to connect to laser controller on startup (will retry on demand)", error=str(e)) + logger.info("VNA API Server started successfully") yield except Exception as exc: @@ -47,6 +58,14 @@ async def lifespan(app: FastAPI): raise logger.info("Shutting down VNA API Server") + # Disconnect laser controller + if singletons.laser_controller_instance and singletons.laser_controller_instance.is_connected: + try: + singletons.laser_controller_instance.disconnect() + logger.info("Laser controller disconnected") + except Exception as e: + logger.warning("Error disconnecting laser controller", error=str(e)) + if singletons.processor_manager: singletons.processor_manager.stop_processing() logger.info("Processor system stopped") diff --git a/vna_system/web_ui/static/js/modules/constants.js b/vna_system/web_ui/static/js/modules/constants.js index 6817ef9..c298cf2 100644 --- a/vna_system/web_ui/static/js/modules/constants.js +++ b/vna_system/web_ui/static/js/modules/constants.js @@ -93,6 +93,7 @@ export const API = { LASER: { START: `${API_BASE}/laser/start`, + START_MANUAL: `${API_BASE}/laser/start-manual`, STOP: `${API_BASE}/laser/stop`, STATUS: `${API_BASE}/laser/status`, CONNECT: `${API_BASE}/laser/connect`, diff --git a/vna_system/web_ui/static/js/modules/settings/laser-manager.js b/vna_system/web_ui/static/js/modules/settings/laser-manager.js index 3210f13..d381850 100644 --- a/vna_system/web_ui/static/js/modules/settings/laser-manager.js +++ b/vna_system/web_ui/static/js/modules/settings/laser-manager.js @@ -3,7 +3,6 @@ * Handles laser control interface with two modes: manual and scan */ -import { ButtonState } from '../utils.js'; import { apiPost } from '../api-client.js'; import { API, NOTIFICATION_TYPES } from '../constants.js'; @@ -111,26 +110,30 @@ export class LaserManager { } try { - ButtonState.disable(this.elements.startBtn); + // Disable start button during request + this.elements.startBtn.disabled = true; let parameters; + let endpoint; if (this.isManualMode) { - // Manual mode - set fixed values - parameters = this.collectManualParameters(); + // Manual mode - use simplified endpoint with only t1, t2, i1, i2 + parameters = this.collectManualParametersSimple(); + endpoint = API.LASER.START_MANUAL; } else { - // Scan mode - set scan parameters + // Scan mode - use full endpoint with scan parameters parameters = this.collectScanParameters(); + endpoint = API.LASER.START; } // Validate parameters if (!this.validateParameters(parameters)) { - ButtonState.enable(this.elements.startBtn); + this.elements.startBtn.disabled = false; return; } - // Send start request - const response = await apiPost(API.LASER.START, parameters); + // Send start request to appropriate endpoint + const response = await apiPost(endpoint, parameters); if (response.success) { this.isRunning = true; @@ -146,13 +149,13 @@ export class LaserManager { console.log('Laser cycle started:', parameters); } else { this.notify(ERROR, 'Ошибка', response.message); - ButtonState.enable(this.elements.startBtn); + this.elements.startBtn.disabled = false; } } catch (error) { console.error('Failed to start laser cycle:', error); this.notify(ERROR, 'Ошибка', `Не удалось запустить цикл: ${error.message}`); - ButtonState.enable(this.elements.startBtn); + this.elements.startBtn.disabled = false; } } @@ -163,7 +166,8 @@ export class LaserManager { } try { - ButtonState.disable(this.elements.stopBtn); + // Disable stop button during request + this.elements.stopBtn.disabled = true; const response = await apiPost(API.LASER.STOP, {}); @@ -178,13 +182,13 @@ export class LaserManager { console.log('Laser cycle stopped'); } else { this.notify(ERROR, 'Ошибка', response.message); - ButtonState.enable(this.elements.stopBtn); + this.elements.stopBtn.disabled = false; } } catch (error) { console.error('Failed to stop laser cycle:', error); this.notify(ERROR, 'Ошибка', `Не удалось остановить цикл: ${error.message}`); - ButtonState.enable(this.elements.stopBtn); + this.elements.stopBtn.disabled = false; } } @@ -225,6 +229,16 @@ export class LaserManager { }; } + collectManualParametersSimple() { + // Simplified manual mode: only 4 parameters (t1, t2, i1, i2) + return { + t1: parseFloat(this.elements.temp1.value), + t2: parseFloat(this.elements.temp2.value), + i1: parseFloat(this.elements.current1.value), + i2: parseFloat(this.elements.current2.value) + }; + } + collectScanParameters() { // Scan mode: scan current 1 while keeping other parameters fixed return { @@ -260,6 +274,32 @@ export class LaserManager { } validateParameters(params) { + // Check if simplified format (t1, t2, i1, i2) + if ('t1' in params && 't2' in params && 'i1' in params && 'i2' in params) { + // Simplified format validation + const values = [params.t1, params.t2, params.i1, params.i2]; + + if (values.some(v => isNaN(v))) { + this.notify(ERROR, 'Ошибка валидации', 'Все поля должны быть заполнены корректными числами'); + return false; + } + + // Check temperature ranges + if (params.t1 < -1 || params.t1 > 45 || params.t2 < -1 || params.t2 > 45) { + this.notify(ERROR, 'Ошибка валидации', 'Температура должна быть от -1 до 45°C'); + return false; + } + + // Check current ranges + if (params.i1 < 15 || params.i1 > 70 || params.i2 < 15 || params.i2 > 60) { + this.notify(ERROR, 'Ошибка валидации', 'Ток лазера 1: 15-70мА, лазера 2: 15-60мА'); + return false; + } + + return true; + } + + // Full format validation (original) // Check for NaN values const values = [ params.min_temp_1, params.max_temp_1,