frontend first
This commit is contained in:
179
vna_system/api/endpoints/laser.py
Normal file
179
vna_system/api/endpoints/laser.py
Normal file
@ -0,0 +1,179 @@
|
|||||||
|
from fastapi import APIRouter, HTTPException, Depends
|
||||||
|
from typing import Dict, Any
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from ...api.models.laser import (
|
||||||
|
LaserParameters,
|
||||||
|
LaserStatus,
|
||||||
|
LaserStartResponse,
|
||||||
|
LaserStopResponse
|
||||||
|
)
|
||||||
|
from ...core.laser import LaserController
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/start", response_model=LaserStartResponse)
|
||||||
|
async def start_laser_cycle(
|
||||||
|
parameters: LaserParameters,
|
||||||
|
controller: LaserController = Depends(get_laser_controller)
|
||||||
|
) -> LaserStartResponse:
|
||||||
|
"""
|
||||||
|
Start laser control cycle with specified parameters.
|
||||||
|
|
||||||
|
This endpoint accepts laser control parameters and starts the measurement cycle.
|
||||||
|
All parameters are validated and logged.
|
||||||
|
"""
|
||||||
|
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
|
||||||
|
enabled_modes = sum([
|
||||||
|
parameters.enable_t1,
|
||||||
|
parameters.enable_t2,
|
||||||
|
parameters.enable_c1,
|
||||||
|
parameters.enable_c2
|
||||||
|
])
|
||||||
|
if enabled_modes > 1:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=400,
|
||||||
|
detail="Можно включить только один режим управления одновременно"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Start the cycle
|
||||||
|
result = controller.start_cycle(parameters)
|
||||||
|
|
||||||
|
if not result["success"]:
|
||||||
|
raise HTTPException(status_code=500, detail=result["message"])
|
||||||
|
|
||||||
|
return LaserStartResponse(
|
||||||
|
success=True,
|
||||||
|
message=result["message"],
|
||||||
|
parameters=parameters
|
||||||
|
)
|
||||||
|
|
||||||
|
except HTTPException:
|
||||||
|
raise
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Unexpected error starting laser cycle: {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)
|
||||||
|
) -> LaserStopResponse:
|
||||||
|
"""
|
||||||
|
Stop the current laser control cycle.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
logger.info("Received request to stop laser cycle")
|
||||||
|
|
||||||
|
result = controller.stop_cycle()
|
||||||
|
|
||||||
|
if not result["success"]:
|
||||||
|
raise HTTPException(status_code=500, detail=result["message"])
|
||||||
|
|
||||||
|
return LaserStopResponse(
|
||||||
|
success=True,
|
||||||
|
message=result["message"]
|
||||||
|
)
|
||||||
|
|
||||||
|
except HTTPException:
|
||||||
|
raise
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Unexpected error stopping laser cycle: {e}", exc_info=True)
|
||||||
|
raise HTTPException(status_code=500, detail=f"Внутренняя ошибка сервера: {str(e)}")
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/status", response_model=LaserStatus)
|
||||||
|
async def get_laser_status(
|
||||||
|
controller: LaserController = Depends(get_laser_controller)
|
||||||
|
) -> LaserStatus:
|
||||||
|
"""
|
||||||
|
Get current laser status including temperature, current, and voltage readings.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
status = controller.get_status()
|
||||||
|
return status
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error getting laser status: {e}", exc_info=True)
|
||||||
|
raise HTTPException(status_code=500, detail=f"Ошибка получения статуса: {str(e)}")
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/connect")
|
||||||
|
async def connect_laser(
|
||||||
|
port: str = None,
|
||||||
|
controller: LaserController = Depends(get_laser_controller)
|
||||||
|
) -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
Connect to laser control hardware on specified port.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
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}")
|
||||||
|
|
||||||
|
result = controller.connect(port)
|
||||||
|
|
||||||
|
if not result["success"]:
|
||||||
|
raise HTTPException(status_code=500, detail=result["message"])
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
except HTTPException:
|
||||||
|
raise
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error connecting to laser: {e}", exc_info=True)
|
||||||
|
raise HTTPException(status_code=500, detail=f"Ошибка подключения: {str(e)}")
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/disconnect")
|
||||||
|
async def disconnect_laser(
|
||||||
|
controller: LaserController = Depends(get_laser_controller)
|
||||||
|
) -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
Disconnect from laser control hardware.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
logger.info("Received request to disconnect from laser hardware")
|
||||||
|
|
||||||
|
result = controller.disconnect()
|
||||||
|
|
||||||
|
if not result["success"]:
|
||||||
|
raise HTTPException(status_code=500, detail=result["message"])
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
except HTTPException:
|
||||||
|
raise
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error disconnecting from laser: {e}", exc_info=True)
|
||||||
|
raise HTTPException(status_code=500, detail=f"Ошибка отключения: {str(e)}")
|
||||||
103
vna_system/api/models/laser.py
Normal file
103
vna_system/api/models/laser.py
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
from pydantic import BaseModel, Field, field_validator
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
|
||||||
|
class LaserParameters(BaseModel):
|
||||||
|
"""Model for laser control parameters"""
|
||||||
|
|
||||||
|
# Laser 1 Temperature parameters
|
||||||
|
min_temp_1: float = Field(..., ge=-1, le=45, description="Минимальная температура лазера 1 (C)")
|
||||||
|
max_temp_1: float = Field(..., ge=-1, le=45, description="Максимальная температура лазера 1 (C)")
|
||||||
|
delta_temp_1: float = Field(..., ge=0.05, le=1.0, description="Шаг дискретизации температуры лазера 1 (C)")
|
||||||
|
|
||||||
|
# Laser 1 Current parameters
|
||||||
|
min_current_1: float = Field(..., ge=15, le=70, description="Минимальный ток лазера 1 (мА)")
|
||||||
|
max_current_1: float = Field(..., ge=15, le=70, description="Максимальный ток лазера 1 (мА)")
|
||||||
|
delta_current_1: float = Field(..., ge=0.002, le=0.5, description="Шаг дискретизации тока лазера 1 (мА)")
|
||||||
|
|
||||||
|
# Laser 2 Temperature parameters
|
||||||
|
min_temp_2: float = Field(..., ge=-1, le=45, description="Минимальная температура лазера 2 (C)")
|
||||||
|
max_temp_2: float = Field(..., ge=-1, le=45, description="Максимальная температура лазера 2 (C)")
|
||||||
|
delta_temp_2: float = Field(..., ge=0.05, le=1.0, description="Шаг дискретизации температуры лазера 2 (C)")
|
||||||
|
|
||||||
|
# Laser 2 Current parameters
|
||||||
|
min_current_2: float = Field(..., ge=15, le=60, description="Минимальный ток лазера 2 (мА)")
|
||||||
|
max_current_2: float = Field(..., ge=15, le=60, description="Максимальный ток лазера 2 (мА)")
|
||||||
|
delta_current_2: float = Field(..., ge=0.002, le=0.5, description="Шаг дискретизации тока лазера 2 (мА)")
|
||||||
|
|
||||||
|
# Time parameters
|
||||||
|
delta_time: int = Field(..., ge=20, le=100, description="Шаг дискретизации времени (мкс), шаг 10")
|
||||||
|
tau: int = Field(..., ge=3, le=10, description="Время задержки (мс)")
|
||||||
|
|
||||||
|
# Enable flags for different control modes
|
||||||
|
enable_t1: bool = Field(False, description="Включить изменение температуры лазера 1")
|
||||||
|
enable_t2: bool = Field(False, description="Включить изменение температуры лазера 2")
|
||||||
|
enable_c1: bool = Field(False, description="Включить изменение тока лазера 1")
|
||||||
|
enable_c2: bool = Field(False, description="Включить изменение тока лазера 2")
|
||||||
|
|
||||||
|
@field_validator('delta_time')
|
||||||
|
@classmethod
|
||||||
|
def validate_delta_time(cls, v):
|
||||||
|
if v % 10 != 0:
|
||||||
|
raise ValueError('delta_time должен быть кратен 10 мкс')
|
||||||
|
return v
|
||||||
|
|
||||||
|
@field_validator('max_temp_1')
|
||||||
|
@classmethod
|
||||||
|
def validate_temp_1_range(cls, v, info):
|
||||||
|
if 'min_temp_1' in info.data and v < info.data['min_temp_1']:
|
||||||
|
raise ValueError('max_temp_1 должен быть больше min_temp_1')
|
||||||
|
return v
|
||||||
|
|
||||||
|
@field_validator('max_temp_2')
|
||||||
|
@classmethod
|
||||||
|
def validate_temp_2_range(cls, v, info):
|
||||||
|
if 'min_temp_2' in info.data and v < info.data['min_temp_2']:
|
||||||
|
raise ValueError('max_temp_2 должен быть больше min_temp_2')
|
||||||
|
return v
|
||||||
|
|
||||||
|
@field_validator('max_current_1')
|
||||||
|
@classmethod
|
||||||
|
def validate_current_1_range(cls, v, info):
|
||||||
|
if 'min_current_1' in info.data and v < info.data['min_current_1']:
|
||||||
|
raise ValueError('max_current_1 должен быть больше min_current_1')
|
||||||
|
return v
|
||||||
|
|
||||||
|
@field_validator('max_current_2')
|
||||||
|
@classmethod
|
||||||
|
def validate_current_2_range(cls, v, info):
|
||||||
|
if 'min_current_2' in info.data and v < info.data['min_current_2']:
|
||||||
|
raise ValueError('max_current_2 должен быть больше min_current_2')
|
||||||
|
return v
|
||||||
|
|
||||||
|
|
||||||
|
class LaserStatus(BaseModel):
|
||||||
|
"""Model for laser status response"""
|
||||||
|
|
||||||
|
temp_1: Optional[float] = Field(None, description="Текущая температура лазера 1 (C)")
|
||||||
|
temp_2: Optional[float] = Field(None, description="Текущая температура лазера 2 (C)")
|
||||||
|
current_1: Optional[float] = Field(None, description="Текущий ток фотодиода 1 (мА)")
|
||||||
|
current_2: Optional[float] = Field(None, description="Текущий ток фотодиода 2 (мА)")
|
||||||
|
temp_ext_1: Optional[float] = Field(None, description="Температура внешнего термистора 1 (C)")
|
||||||
|
temp_ext_2: Optional[float] = Field(None, description="Температура внешнего термистора 2 (C)")
|
||||||
|
voltage_3v3: Optional[float] = Field(None, description="Напряжение 3V3 (В)")
|
||||||
|
voltage_5v1: Optional[float] = Field(None, description="Напряжение 5V1 (В)")
|
||||||
|
voltage_5v2: Optional[float] = Field(None, description="Напряжение 5V2 (В)")
|
||||||
|
voltage_7v0: Optional[float] = Field(None, description="Напряжение 7V0 (В)")
|
||||||
|
is_running: bool = Field(False, description="Запущен ли цикл измерений")
|
||||||
|
connected: bool = Field(False, description="Подключено ли устройство")
|
||||||
|
|
||||||
|
|
||||||
|
class LaserStartResponse(BaseModel):
|
||||||
|
"""Response model for laser start endpoint"""
|
||||||
|
|
||||||
|
success: bool = Field(..., description="Успешность операции")
|
||||||
|
message: str = Field(..., description="Сообщение о результате")
|
||||||
|
parameters: Optional[LaserParameters] = Field(None, description="Примененные параметры")
|
||||||
|
|
||||||
|
|
||||||
|
class LaserStopResponse(BaseModel):
|
||||||
|
"""Response model for laser stop endpoint"""
|
||||||
|
|
||||||
|
success: bool = Field(..., description="Успешность операции")
|
||||||
|
message: str = Field(..., description="Сообщение о результате")
|
||||||
3
vna_system/core/laser/__init__.py
Normal file
3
vna_system/core/laser/__init__.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
from .laser_controller import LaserController
|
||||||
|
|
||||||
|
__all__ = ['LaserController']
|
||||||
213
vna_system/core/laser/laser_controller.py
Normal file
213
vna_system/core/laser/laser_controller.py
Normal file
@ -0,0 +1,213 @@
|
|||||||
|
import logging
|
||||||
|
from typing import Optional, Dict, Any
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
from ...api.models.laser import LaserParameters, LaserStatus
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
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.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.is_connected = False
|
||||||
|
self.is_running = False
|
||||||
|
self.current_parameters: Optional[LaserParameters] = None
|
||||||
|
self.current_status = LaserStatus()
|
||||||
|
logger.info("LaserController initialized")
|
||||||
|
|
||||||
|
def start_cycle(self, parameters: LaserParameters) -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
Start laser control cycle with given parameters.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
parameters: LaserParameters object with control settings
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dictionary with success status and message
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
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")
|
||||||
|
|
||||||
|
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")
|
||||||
|
|
||||||
|
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}")
|
||||||
|
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error starting laser cycle: {e}", exc_info=True)
|
||||||
|
return {
|
||||||
|
"success": False,
|
||||||
|
"message": f"Ошибка при запуске цикла: {str(e)}",
|
||||||
|
"parameters": None
|
||||||
|
}
|
||||||
|
|
||||||
|
def stop_cycle(self) -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
Stop current laser control cycle.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dictionary with success status and message
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
logger.info("=" * 60)
|
||||||
|
logger.info("LASER CONTROL: STOP CYCLE")
|
||||||
|
logger.info(f"Timestamp: {datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')}")
|
||||||
|
logger.info("=" * 60)
|
||||||
|
|
||||||
|
self.is_running = False
|
||||||
|
self.current_status.is_running = False
|
||||||
|
self.current_parameters = None
|
||||||
|
|
||||||
|
return {
|
||||||
|
"success": True,
|
||||||
|
"message": "Цикл управления лазером остановлен"
|
||||||
|
}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error stopping laser cycle: {e}", exc_info=True)
|
||||||
|
return {
|
||||||
|
"success": False,
|
||||||
|
"message": f"Ошибка при остановке цикла: {str(e)}"
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_status(self) -> LaserStatus:
|
||||||
|
"""
|
||||||
|
Get current laser status.
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
return self.current_status
|
||||||
|
|
||||||
|
def connect(self, port: Optional[str] = None) -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
Connect to laser control hardware.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
port: Serial port to connect to (e.g., '/dev/ttyUSB0')
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dictionary with success status and message
|
||||||
|
"""
|
||||||
|
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
|
||||||
|
self.is_connected = True
|
||||||
|
self.current_status.connected = True
|
||||||
|
|
||||||
|
logger.info("Successfully connected to laser hardware (stub mode)")
|
||||||
|
|
||||||
|
return {
|
||||||
|
"success": True,
|
||||||
|
"message": f"Подключено к устройству (stub mode)",
|
||||||
|
"port": port or "auto-detected"
|
||||||
|
}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error connecting to laser hardware: {e}", exc_info=True)
|
||||||
|
return {
|
||||||
|
"success": False,
|
||||||
|
"message": f"Ошибка подключения: {str(e)}",
|
||||||
|
"port": port
|
||||||
|
}
|
||||||
|
|
||||||
|
def disconnect(self) -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
Disconnect from laser control hardware.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dictionary with success status and message
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
logger.info("Disconnecting from laser hardware")
|
||||||
|
|
||||||
|
# Stop any running cycle first
|
||||||
|
if self.is_running:
|
||||||
|
self.stop_cycle()
|
||||||
|
|
||||||
|
self.is_connected = False
|
||||||
|
self.current_status.connected = False
|
||||||
|
|
||||||
|
logger.info("Successfully disconnected from laser hardware")
|
||||||
|
|
||||||
|
return {
|
||||||
|
"success": True,
|
||||||
|
"message": "Отключено от устройства"
|
||||||
|
}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error disconnecting from laser hardware: {e}", exc_info=True)
|
||||||
|
return {
|
||||||
|
"success": False,
|
||||||
|
"message": f"Ошибка отключения: {str(e)}"
|
||||||
|
}
|
||||||
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"open_air": true,
|
"open_air": false,
|
||||||
"axis": "abs",
|
"axis": "abs",
|
||||||
"cut": 0.23,
|
"cut": 0.23,
|
||||||
"max": 1.5,
|
"max": 1.5,
|
||||||
@ -7,8 +7,8 @@
|
|||||||
"start_freq": 470.0,
|
"start_freq": 470.0,
|
||||||
"stop_freq": 8800.0,
|
"stop_freq": 8800.0,
|
||||||
"clear_history": false,
|
"clear_history": false,
|
||||||
"sigma": 3.42,
|
"sigma": 1.44,
|
||||||
"border_border_m": 0.26,
|
"border_border_m": 0.41,
|
||||||
"if_normalize": false,
|
"if_normalize": false,
|
||||||
"if_draw_level": false,
|
"if_draw_level": false,
|
||||||
"detection_level": 3.0,
|
"detection_level": 3.0,
|
||||||
|
|||||||
@ -8,7 +8,7 @@ from fastapi import FastAPI
|
|||||||
from fastapi.staticfiles import StaticFiles
|
from fastapi.staticfiles import StaticFiles
|
||||||
|
|
||||||
import vna_system.core.singletons as singletons
|
import vna_system.core.singletons as singletons
|
||||||
from vna_system.api.endpoints import acquisition, health, settings, web_ui
|
from vna_system.api.endpoints import acquisition, health, settings, web_ui, laser
|
||||||
from vna_system.api.websockets import processing as ws_processing
|
from vna_system.api.websockets import processing as ws_processing
|
||||||
from vna_system.core.config import API_HOST, API_PORT
|
from vna_system.core.config import API_HOST, API_PORT
|
||||||
from vna_system.core.logging.logger import get_component_logger, setup_logging
|
from vna_system.core.logging.logger import get_component_logger, setup_logging
|
||||||
@ -77,6 +77,7 @@ app.include_router(web_ui.router)
|
|||||||
app.include_router(health.router)
|
app.include_router(health.router)
|
||||||
app.include_router(acquisition.router)
|
app.include_router(acquisition.router)
|
||||||
app.include_router(settings.router)
|
app.include_router(settings.router)
|
||||||
|
app.include_router(laser.router)
|
||||||
app.include_router(ws_processing.router)
|
app.include_router(ws_processing.router)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -574,3 +574,67 @@
|
|||||||
min-width: unset;
|
min-width: unset;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ========================================
|
||||||
|
Laser Parameters Table Styles
|
||||||
|
======================================== */
|
||||||
|
|
||||||
|
.laser-params-table {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(4, 1fr);
|
||||||
|
gap: var(--space-3);
|
||||||
|
background: var(--bg-tertiary);
|
||||||
|
border: 1px solid var(--border-primary);
|
||||||
|
border-radius: var(--radius-lg);
|
||||||
|
padding: var(--space-4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.laser-param-cell {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.laser-param-header {
|
||||||
|
color: var(--text-secondary);
|
||||||
|
font-size: var(--font-size-sm);
|
||||||
|
font-weight: var(--font-weight-semibold);
|
||||||
|
text-align: center;
|
||||||
|
padding: var(--space-3);
|
||||||
|
background: var(--bg-surface);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
border: 1px solid var(--border-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.laser-param-input {
|
||||||
|
display: flex;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.laser-param-input .settings-input {
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
font-weight: var(--font-weight-medium);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive layout for smaller screens */
|
||||||
|
@media (max-width: 1024px) {
|
||||||
|
.laser-params-table {
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 640px) {
|
||||||
|
.laser-params-table {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.laser-param-header {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.laser-param-input .settings-input {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -89,6 +89,14 @@ export const API = {
|
|||||||
REFERENCE_CURRENT: `${API_BASE}/settings/reference/current`,
|
REFERENCE_CURRENT: `${API_BASE}/settings/reference/current`,
|
||||||
REFERENCE_ITEM: (name) => `${API_BASE}/settings/reference/${encodeURIComponent(name)}`,
|
REFERENCE_ITEM: (name) => `${API_BASE}/settings/reference/${encodeURIComponent(name)}`,
|
||||||
REFERENCE_PLOT: (name) => `${API_BASE}/settings/reference/${encodeURIComponent(name)}/plot`
|
REFERENCE_PLOT: (name) => `${API_BASE}/settings/reference/${encodeURIComponent(name)}/plot`
|
||||||
|
},
|
||||||
|
|
||||||
|
LASER: {
|
||||||
|
START: `${API_BASE}/laser/start`,
|
||||||
|
STOP: `${API_BASE}/laser/stop`,
|
||||||
|
STATUS: `${API_BASE}/laser/status`,
|
||||||
|
CONNECT: `${API_BASE}/laser/connect`,
|
||||||
|
DISCONNECT: `${API_BASE}/laser/disconnect`
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -6,6 +6,7 @@
|
|||||||
import { PresetManager } from './settings/preset-manager.js';
|
import { PresetManager } from './settings/preset-manager.js';
|
||||||
import { CalibrationManager } from './settings/calibration-manager.js';
|
import { CalibrationManager } from './settings/calibration-manager.js';
|
||||||
import { ReferenceManager } from './settings/reference-manager.js';
|
import { ReferenceManager } from './settings/reference-manager.js';
|
||||||
|
import { LaserManager } from './settings/laser-manager.js';
|
||||||
import { Debouncer, ButtonState, downloadJSON } from './utils.js';
|
import { Debouncer, ButtonState, downloadJSON } from './utils.js';
|
||||||
import { renderIcons } from './icons.js';
|
import { renderIcons } from './icons.js';
|
||||||
import {
|
import {
|
||||||
@ -34,6 +35,7 @@ export class SettingsManager {
|
|||||||
this.presetManager = new PresetManager(notifications);
|
this.presetManager = new PresetManager(notifications);
|
||||||
this.calibrationManager = new CalibrationManager(notifications);
|
this.calibrationManager = new CalibrationManager(notifications);
|
||||||
this.referenceManager = new ReferenceManager(notifications);
|
this.referenceManager = new ReferenceManager(notifications);
|
||||||
|
this.laserManager = new LaserManager(notifications);
|
||||||
|
|
||||||
// Plots modal state
|
// Plots modal state
|
||||||
this.currentPlotsData = null;
|
this.currentPlotsData = null;
|
||||||
@ -100,6 +102,23 @@ export class SettingsManager {
|
|||||||
currentReferenceDescription: document.getElementById('currentReferenceDescription'),
|
currentReferenceDescription: document.getElementById('currentReferenceDescription'),
|
||||||
currentReferenceCalibration: document.getElementById('currentReferenceCalibration'),
|
currentReferenceCalibration: document.getElementById('currentReferenceCalibration'),
|
||||||
|
|
||||||
|
// Laser controls
|
||||||
|
laserManualMode: document.getElementById('laserManualMode'),
|
||||||
|
laserTemp1: document.getElementById('laserTemp1'),
|
||||||
|
laserTemp2: document.getElementById('laserTemp2'),
|
||||||
|
laserCurrent1: document.getElementById('laserCurrent1'),
|
||||||
|
laserCurrent2: document.getElementById('laserCurrent2'),
|
||||||
|
laserMinCurrent1: document.getElementById('laserMinCurrent1'),
|
||||||
|
laserMaxCurrent1: document.getElementById('laserMaxCurrent1'),
|
||||||
|
laserDeltaCurrent1: document.getElementById('laserDeltaCurrent1'),
|
||||||
|
laserScanTemp1: document.getElementById('laserScanTemp1'),
|
||||||
|
laserScanTemp2: document.getElementById('laserScanTemp2'),
|
||||||
|
laserScanCurrent2: document.getElementById('laserScanCurrent2'),
|
||||||
|
laserDeltaTime: document.getElementById('laserDeltaTime'),
|
||||||
|
laserTau: document.getElementById('laserTau'),
|
||||||
|
laserStartBtn: document.getElementById('laserStartBtn'),
|
||||||
|
laserStopBtn: document.getElementById('laserStopBtn'),
|
||||||
|
|
||||||
// Status
|
// Status
|
||||||
presetCount: document.getElementById('presetCount'),
|
presetCount: document.getElementById('presetCount'),
|
||||||
calibrationCount: document.getElementById('calibrationCount'),
|
calibrationCount: document.getElementById('calibrationCount'),
|
||||||
@ -118,6 +137,23 @@ export class SettingsManager {
|
|||||||
this.presetManager.init(this.elements);
|
this.presetManager.init(this.elements);
|
||||||
this.calibrationManager.init(this.elements);
|
this.calibrationManager.init(this.elements);
|
||||||
this.referenceManager.init(this.elements);
|
this.referenceManager.init(this.elements);
|
||||||
|
this.laserManager.init({
|
||||||
|
manualMode: this.elements.laserManualMode,
|
||||||
|
temp1: this.elements.laserTemp1,
|
||||||
|
temp2: this.elements.laserTemp2,
|
||||||
|
current1: this.elements.laserCurrent1,
|
||||||
|
current2: this.elements.laserCurrent2,
|
||||||
|
minCurrent1: this.elements.laserMinCurrent1,
|
||||||
|
maxCurrent1: this.elements.laserMaxCurrent1,
|
||||||
|
deltaCurrent1: this.elements.laserDeltaCurrent1,
|
||||||
|
scanTemp1: this.elements.laserScanTemp1,
|
||||||
|
scanTemp2: this.elements.laserScanTemp2,
|
||||||
|
scanCurrent2: this.elements.laserScanCurrent2,
|
||||||
|
deltaTime: this.elements.laserDeltaTime,
|
||||||
|
tau: this.elements.laserTau,
|
||||||
|
startBtn: this.elements.laserStartBtn,
|
||||||
|
stopBtn: this.elements.laserStopBtn
|
||||||
|
});
|
||||||
|
|
||||||
// Setup callbacks
|
// Setup callbacks
|
||||||
this.presetManager.onPresetChanged = async () => {
|
this.presetManager.onPresetChanged = async () => {
|
||||||
|
|||||||
366
vna_system/web_ui/static/js/modules/settings/laser-manager.js
Normal file
366
vna_system/web_ui/static/js/modules/settings/laser-manager.js
Normal file
@ -0,0 +1,366 @@
|
|||||||
|
/**
|
||||||
|
* Laser Manager
|
||||||
|
* 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';
|
||||||
|
|
||||||
|
const { SUCCESS, ERROR } = NOTIFICATION_TYPES;
|
||||||
|
|
||||||
|
export class LaserManager {
|
||||||
|
constructor(notifications) {
|
||||||
|
this.notifications = notifications;
|
||||||
|
this.elements = {};
|
||||||
|
this.isRunning = false;
|
||||||
|
this.isManualMode = false;
|
||||||
|
|
||||||
|
// Bind methods
|
||||||
|
this.handleModeChange = this.handleModeChange.bind(this);
|
||||||
|
this.handleStartClick = this.handleStartClick.bind(this);
|
||||||
|
this.handleStopClick = this.handleStopClick.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
init(elements) {
|
||||||
|
this.elements = elements;
|
||||||
|
|
||||||
|
// Add event listeners
|
||||||
|
this.elements.manualMode?.addEventListener('change', this.handleModeChange);
|
||||||
|
this.elements.startBtn?.addEventListener('click', this.handleStartClick);
|
||||||
|
this.elements.stopBtn?.addEventListener('click', this.handleStopClick);
|
||||||
|
|
||||||
|
// Initialize UI state
|
||||||
|
this.updateUIState();
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy() {
|
||||||
|
this.elements.manualMode?.removeEventListener('change', this.handleModeChange);
|
||||||
|
this.elements.startBtn?.removeEventListener('click', this.handleStartClick);
|
||||||
|
this.elements.stopBtn?.removeEventListener('click', this.handleStopClick);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleModeChange() {
|
||||||
|
this.isManualMode = this.elements.manualMode?.checked || false;
|
||||||
|
this.updateUIState();
|
||||||
|
}
|
||||||
|
|
||||||
|
updateUIState() {
|
||||||
|
const manualSection = document.getElementById('laserManualSection');
|
||||||
|
const scanSection = document.getElementById('laserScanSection');
|
||||||
|
|
||||||
|
if (this.isManualMode) {
|
||||||
|
// Manual mode: show manual controls, hide scan controls
|
||||||
|
if (manualSection) manualSection.style.display = 'block';
|
||||||
|
if (scanSection) scanSection.style.display = 'none';
|
||||||
|
|
||||||
|
// Enable manual mode inputs only if not running
|
||||||
|
if (!this.isRunning) {
|
||||||
|
this.elements.temp1.disabled = false;
|
||||||
|
this.elements.temp2.disabled = false;
|
||||||
|
this.elements.current1.disabled = false;
|
||||||
|
this.elements.current2.disabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disable scan inputs
|
||||||
|
this.elements.minCurrent1.disabled = true;
|
||||||
|
this.elements.maxCurrent1.disabled = true;
|
||||||
|
this.elements.deltaCurrent1.disabled = true;
|
||||||
|
this.elements.scanTemp1.disabled = true;
|
||||||
|
this.elements.scanTemp2.disabled = true;
|
||||||
|
this.elements.scanCurrent2.disabled = true;
|
||||||
|
this.elements.deltaTime.disabled = true;
|
||||||
|
this.elements.tau.disabled = true;
|
||||||
|
} else {
|
||||||
|
// Scan mode: hide manual controls, show scan controls
|
||||||
|
if (manualSection) manualSection.style.display = 'none';
|
||||||
|
if (scanSection) scanSection.style.display = 'block';
|
||||||
|
|
||||||
|
// Disable manual mode inputs
|
||||||
|
this.elements.temp1.disabled = true;
|
||||||
|
this.elements.temp2.disabled = true;
|
||||||
|
this.elements.current1.disabled = true;
|
||||||
|
this.elements.current2.disabled = true;
|
||||||
|
|
||||||
|
// Enable scan inputs only if not running
|
||||||
|
if (!this.isRunning) {
|
||||||
|
this.elements.minCurrent1.disabled = false;
|
||||||
|
this.elements.maxCurrent1.disabled = false;
|
||||||
|
this.elements.deltaCurrent1.disabled = false;
|
||||||
|
this.elements.scanTemp1.disabled = false;
|
||||||
|
this.elements.scanTemp2.disabled = false;
|
||||||
|
this.elements.scanCurrent2.disabled = false;
|
||||||
|
this.elements.deltaTime.disabled = false;
|
||||||
|
this.elements.tau.disabled = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enable/disable start button based on running state
|
||||||
|
if (this.elements.startBtn) {
|
||||||
|
this.elements.startBtn.disabled = this.isRunning;
|
||||||
|
}
|
||||||
|
if (this.elements.stopBtn) {
|
||||||
|
this.elements.stopBtn.disabled = !this.isRunning;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async handleStartClick() {
|
||||||
|
if (this.isRunning) {
|
||||||
|
this.notify(ERROR, 'Ошибка', 'Цикл уже запущен');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
ButtonState.disable(this.elements.startBtn);
|
||||||
|
|
||||||
|
let parameters;
|
||||||
|
|
||||||
|
if (this.isManualMode) {
|
||||||
|
// Manual mode - set fixed values
|
||||||
|
parameters = this.collectManualParameters();
|
||||||
|
} else {
|
||||||
|
// Scan mode - set scan parameters
|
||||||
|
parameters = this.collectScanParameters();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate parameters
|
||||||
|
if (!this.validateParameters(parameters)) {
|
||||||
|
ButtonState.enable(this.elements.startBtn);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send start request
|
||||||
|
const response = await apiPost(API.LASER.START, parameters);
|
||||||
|
|
||||||
|
if (response.success) {
|
||||||
|
this.isRunning = true;
|
||||||
|
this.notify(SUCCESS, 'Успешно', response.message);
|
||||||
|
|
||||||
|
// Disable all controls during operation
|
||||||
|
this.disableAllControls();
|
||||||
|
|
||||||
|
// Update button states
|
||||||
|
this.elements.startBtn.disabled = true;
|
||||||
|
this.elements.stopBtn.disabled = false;
|
||||||
|
|
||||||
|
console.log('Laser cycle started:', parameters);
|
||||||
|
} else {
|
||||||
|
this.notify(ERROR, 'Ошибка', response.message);
|
||||||
|
ButtonState.enable(this.elements.startBtn);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to start laser cycle:', error);
|
||||||
|
this.notify(ERROR, 'Ошибка', `Не удалось запустить цикл: ${error.message}`);
|
||||||
|
ButtonState.enable(this.elements.startBtn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async handleStopClick() {
|
||||||
|
if (!this.isRunning) {
|
||||||
|
this.notify(ERROR, 'Ошибка', 'Цикл не запущен');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
ButtonState.disable(this.elements.stopBtn);
|
||||||
|
|
||||||
|
const response = await apiPost(API.LASER.STOP, {});
|
||||||
|
|
||||||
|
if (response.success) {
|
||||||
|
this.isRunning = false;
|
||||||
|
this.notify(SUCCESS, 'Успешно', response.message);
|
||||||
|
|
||||||
|
// Re-enable controls
|
||||||
|
this.enableAllControls();
|
||||||
|
this.updateUIState();
|
||||||
|
|
||||||
|
console.log('Laser cycle stopped');
|
||||||
|
} else {
|
||||||
|
this.notify(ERROR, 'Ошибка', response.message);
|
||||||
|
ButtonState.enable(this.elements.stopBtn);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to stop laser cycle:', error);
|
||||||
|
this.notify(ERROR, 'Ошибка', `Не удалось остановить цикл: ${error.message}`);
|
||||||
|
ButtonState.enable(this.elements.stopBtn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
collectManualParameters() {
|
||||||
|
// Manual mode: use fixed temperature and current values
|
||||||
|
// Set min=max to indicate manual mode
|
||||||
|
const temp1 = parseFloat(this.elements.temp1.value);
|
||||||
|
const temp2 = parseFloat(this.elements.temp2.value);
|
||||||
|
const current1 = parseFloat(this.elements.current1.value);
|
||||||
|
const current2 = parseFloat(this.elements.current2.value);
|
||||||
|
|
||||||
|
return {
|
||||||
|
// Manual mode indicated by setting enable_c1 = false and all ranges equal
|
||||||
|
enable_c1: false,
|
||||||
|
enable_t1: false,
|
||||||
|
enable_t2: false,
|
||||||
|
enable_c2: false,
|
||||||
|
|
||||||
|
// Temperature values (same for min/max in manual mode)
|
||||||
|
min_temp_1: temp1,
|
||||||
|
max_temp_1: temp1,
|
||||||
|
delta_temp_1: 0.05,
|
||||||
|
min_temp_2: temp2,
|
||||||
|
max_temp_2: temp2,
|
||||||
|
delta_temp_2: 0.05,
|
||||||
|
|
||||||
|
// Current values (same for min/max in manual mode)
|
||||||
|
min_current_1: current1,
|
||||||
|
max_current_1: current1,
|
||||||
|
delta_current_1: 0.05,
|
||||||
|
min_current_2: current2,
|
||||||
|
max_current_2: current2,
|
||||||
|
delta_current_2: 0.05,
|
||||||
|
|
||||||
|
// Time parameters (not used in manual mode but required)
|
||||||
|
delta_time: 50,
|
||||||
|
tau: 10
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
collectScanParameters() {
|
||||||
|
// Scan mode: scan current 1 while keeping other parameters fixed
|
||||||
|
return {
|
||||||
|
enable_c1: true, // Scanning current 1
|
||||||
|
enable_t1: false,
|
||||||
|
enable_t2: false,
|
||||||
|
enable_c2: false,
|
||||||
|
|
||||||
|
// Temperature 1 (fixed)
|
||||||
|
min_temp_1: parseFloat(this.elements.scanTemp1.value),
|
||||||
|
max_temp_1: parseFloat(this.elements.scanTemp1.value),
|
||||||
|
delta_temp_1: 0.05,
|
||||||
|
|
||||||
|
// Temperature 2 (fixed)
|
||||||
|
min_temp_2: parseFloat(this.elements.scanTemp2.value),
|
||||||
|
max_temp_2: parseFloat(this.elements.scanTemp2.value),
|
||||||
|
delta_temp_2: 0.05,
|
||||||
|
|
||||||
|
// Current 1 (scanning range)
|
||||||
|
min_current_1: parseFloat(this.elements.minCurrent1.value),
|
||||||
|
max_current_1: parseFloat(this.elements.maxCurrent1.value),
|
||||||
|
delta_current_1: parseFloat(this.elements.deltaCurrent1.value),
|
||||||
|
|
||||||
|
// Current 2 (fixed)
|
||||||
|
min_current_2: parseFloat(this.elements.scanCurrent2.value),
|
||||||
|
max_current_2: parseFloat(this.elements.scanCurrent2.value),
|
||||||
|
delta_current_2: 0.05,
|
||||||
|
|
||||||
|
// Time parameters
|
||||||
|
delta_time: parseInt(this.elements.deltaTime.value),
|
||||||
|
tau: parseInt(this.elements.tau.value)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
validateParameters(params) {
|
||||||
|
// Check for NaN values
|
||||||
|
const values = [
|
||||||
|
params.min_temp_1, params.max_temp_1,
|
||||||
|
params.min_temp_2, params.max_temp_2,
|
||||||
|
params.min_current_1, params.max_current_1,
|
||||||
|
params.min_current_2, params.max_current_2,
|
||||||
|
params.delta_time, params.tau
|
||||||
|
];
|
||||||
|
|
||||||
|
if (values.some(v => isNaN(v))) {
|
||||||
|
this.notify(ERROR, 'Ошибка валидации', 'Все поля должны быть заполнены корректными числами');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check temperature ranges
|
||||||
|
if (params.min_temp_1 < -1 || params.min_temp_1 > 45 ||
|
||||||
|
params.max_temp_1 < -1 || params.max_temp_1 > 45 ||
|
||||||
|
params.min_temp_2 < -1 || params.min_temp_2 > 45 ||
|
||||||
|
params.max_temp_2 < -1 || params.max_temp_2 > 45) {
|
||||||
|
this.notify(ERROR, 'Ошибка валидации', 'Температура должна быть от -1 до 45°C');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check current ranges
|
||||||
|
if (params.min_current_1 < 15 || params.max_current_1 > 70 ||
|
||||||
|
params.min_current_2 < 15 || params.max_current_2 > 60) {
|
||||||
|
this.notify(ERROR, 'Ошибка валидации', 'Ток должен быть в допустимых пределах');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// In scan mode, check min < max for current 1
|
||||||
|
if (!this.isManualMode) {
|
||||||
|
if (params.min_current_1 >= params.max_current_1) {
|
||||||
|
this.notify(ERROR, 'Ошибка валидации',
|
||||||
|
'Минимальный ток лазера 1 должен быть меньше максимального');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check delta_time is multiple of 10
|
||||||
|
if (params.delta_time % 10 !== 0) {
|
||||||
|
this.notify(ERROR, 'Ошибка валидации',
|
||||||
|
'Шаг дискретизации времени должен быть кратен 10 мкс');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check time parameters
|
||||||
|
if (params.delta_time < 20 || params.delta_time > 100) {
|
||||||
|
this.notify(ERROR, 'Ошибка валидации',
|
||||||
|
'Шаг дискретизации времени должен быть от 20 до 100 мкс');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (params.tau < 3 || params.tau > 10) {
|
||||||
|
this.notify(ERROR, 'Ошибка валидации',
|
||||||
|
'Время задержки должно быть от 3 до 10 мс');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
disableAllControls() {
|
||||||
|
// Disable mode checkbox
|
||||||
|
if (this.elements.manualMode) {
|
||||||
|
this.elements.manualMode.disabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disable all input fields
|
||||||
|
const allInputs = [
|
||||||
|
this.elements.temp1,
|
||||||
|
this.elements.temp2,
|
||||||
|
this.elements.current1,
|
||||||
|
this.elements.current2,
|
||||||
|
this.elements.minCurrent1,
|
||||||
|
this.elements.maxCurrent1,
|
||||||
|
this.elements.deltaCurrent1,
|
||||||
|
this.elements.scanTemp1,
|
||||||
|
this.elements.scanTemp2,
|
||||||
|
this.elements.scanCurrent2,
|
||||||
|
this.elements.deltaTime,
|
||||||
|
this.elements.tau
|
||||||
|
];
|
||||||
|
|
||||||
|
allInputs.forEach(input => {
|
||||||
|
if (input) input.disabled = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
enableAllControls() {
|
||||||
|
// Enable mode checkbox
|
||||||
|
if (this.elements.manualMode) {
|
||||||
|
this.elements.manualMode.disabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// UI state will be updated by updateUIState() call after this
|
||||||
|
}
|
||||||
|
|
||||||
|
notify(type, title, message) {
|
||||||
|
if (this.notifications) {
|
||||||
|
this.notifications.show(type, title, message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -327,6 +327,130 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Laser Control Section -->
|
||||||
|
<div class="settings-card">
|
||||||
|
<h3 class="settings-card-title">Управление лазером</h3>
|
||||||
|
<p class="settings-card-description">Контроль параметров лазерной схемы оптического смесителя</p>
|
||||||
|
|
||||||
|
<div class="laser-controls">
|
||||||
|
<!-- Mode Selection -->
|
||||||
|
<div class="control-section">
|
||||||
|
<h4 class="control-section-title">Режим работы</h4>
|
||||||
|
<div class="control-group">
|
||||||
|
<label class="control-label control-label--checkbox">
|
||||||
|
<input type="checkbox" id="laserManualMode">
|
||||||
|
<span>Ручной режим ввода</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Manual Mode Controls -->
|
||||||
|
<div class="control-section" id="laserManualSection">
|
||||||
|
<h4 class="control-section-title">Параметры ручного режима</h4>
|
||||||
|
<div class="laser-params-table">
|
||||||
|
<div class="laser-param-cell laser-param-header">Температура лазера 1 (°C)</div>
|
||||||
|
<div class="laser-param-cell laser-param-header">Температура лазера 2 (°C)</div>
|
||||||
|
<div class="laser-param-cell laser-param-header">Управляющий ток лазера 1 (15-60 мА)</div>
|
||||||
|
<div class="laser-param-cell laser-param-header">Управляющий ток лазера 2 (15-60 мА)</div>
|
||||||
|
|
||||||
|
<div class="laser-param-cell laser-param-input">
|
||||||
|
<input type="number" class="settings-input" id="laserTemp1"
|
||||||
|
min="-1" max="45" step="0.1" value="28">
|
||||||
|
</div>
|
||||||
|
<div class="laser-param-cell laser-param-input">
|
||||||
|
<input type="number" class="settings-input" id="laserTemp2"
|
||||||
|
min="-1" max="45" step="0.1" value="28.9">
|
||||||
|
</div>
|
||||||
|
<div class="laser-param-cell laser-param-input">
|
||||||
|
<input type="number" class="settings-input" id="laserCurrent1"
|
||||||
|
min="15" max="60" step="0.1" value="33">
|
||||||
|
</div>
|
||||||
|
<div class="laser-param-cell laser-param-input">
|
||||||
|
<input type="number" class="settings-input" id="laserCurrent2"
|
||||||
|
min="15" max="60" step="0.1" value="35">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Scan Mode Controls -->
|
||||||
|
<div class="control-section" id="laserScanSection" style="display: none;">
|
||||||
|
<h4 class="control-section-title">Режим сканирования тока лазера 1</h4>
|
||||||
|
|
||||||
|
<div class="control-subsection">
|
||||||
|
<h5 class="control-subsection-title">Параметры сканирования</h5>
|
||||||
|
<div class="control-grid">
|
||||||
|
<div class="control-group">
|
||||||
|
<label class="control-label">Минимальный ток лазера 1 (мА):</label>
|
||||||
|
<input type="number" class="settings-input" id="laserMinCurrent1"
|
||||||
|
min="15" max="70" step="0.1" value="33" disabled>
|
||||||
|
</div>
|
||||||
|
<div class="control-group">
|
||||||
|
<label class="control-label">Максимальный ток лазера 1 (мА):</label>
|
||||||
|
<input type="number" class="settings-input" id="laserMaxCurrent1"
|
||||||
|
min="15" max="70" step="0.1" value="70" disabled>
|
||||||
|
</div>
|
||||||
|
<div class="control-group">
|
||||||
|
<label class="control-label">Шаг дискретизации тока лазера 1 (0.002-0.5 мА):</label>
|
||||||
|
<input type="number" class="settings-input" id="laserDeltaCurrent1"
|
||||||
|
min="0.002" max="0.5" step="0.001" value="0.05" disabled>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="control-subsection">
|
||||||
|
<h5 class="control-subsection-title">Фиксированные параметры</h5>
|
||||||
|
<div class="control-grid">
|
||||||
|
<div class="control-group">
|
||||||
|
<label class="control-label">Температура лазера 1 (°C):</label>
|
||||||
|
<input type="number" class="settings-input" id="laserScanTemp1"
|
||||||
|
min="-1" max="45" step="0.1" value="28" disabled>
|
||||||
|
</div>
|
||||||
|
<div class="control-group">
|
||||||
|
<label class="control-label">Температура лазера 2 (°C):</label>
|
||||||
|
<input type="number" class="settings-input" id="laserScanTemp2"
|
||||||
|
min="-1" max="45" step="0.1" value="28.9" disabled>
|
||||||
|
</div>
|
||||||
|
<div class="control-group">
|
||||||
|
<label class="control-label">Управляющий ток лазера 2 (мА):</label>
|
||||||
|
<input type="number" class="settings-input" id="laserScanCurrent2"
|
||||||
|
min="15" max="60" step="0.1" value="35" disabled>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="control-subsection">
|
||||||
|
<h5 class="control-subsection-title">Параметры времени</h5>
|
||||||
|
<div class="control-grid">
|
||||||
|
<div class="control-group">
|
||||||
|
<label class="control-label">Шаг дискретизации времени (20-100 мкс, шаг 10):</label>
|
||||||
|
<input type="number" class="settings-input" id="laserDeltaTime"
|
||||||
|
min="20" max="100" step="10" value="50" disabled>
|
||||||
|
</div>
|
||||||
|
<div class="control-group">
|
||||||
|
<label class="control-label">Время задержки (3-10 мс):</label>
|
||||||
|
<input type="number" class="settings-input" id="laserTau"
|
||||||
|
min="3" max="10" step="1" value="10" disabled>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Control Buttons -->
|
||||||
|
<div class="control-section">
|
||||||
|
<div class="control-group control-group--buttons">
|
||||||
|
<button class="btn btn--primary" id="laserStartBtn">
|
||||||
|
<span data-icon="play"></span>
|
||||||
|
Пуск
|
||||||
|
</button>
|
||||||
|
<button class="btn btn--secondary" id="laserStopBtn" disabled>
|
||||||
|
<span data-icon="square"></span>
|
||||||
|
Стоп
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- System Summary -->
|
<!-- System Summary -->
|
||||||
<div class="settings-card">
|
<div class="settings-card">
|
||||||
<h3 class="settings-card-title">Сводка системы</h3>
|
<h3 class="settings-card-title">Сводка системы</h3>
|
||||||
|
|||||||
Reference in New Issue
Block a user