some russian translations and S button functios as single sweep now
This commit is contained in:
@ -26,7 +26,7 @@ async def get_acquisition_status() -> dict[str, Any]:
|
|||||||
acquisition = singletons.vna_data_acquisition_instance
|
acquisition = singletons.vna_data_acquisition_instance
|
||||||
if acquisition is None:
|
if acquisition is None:
|
||||||
logger.error("Acquisition singleton is not initialized")
|
logger.error("Acquisition singleton is not initialized")
|
||||||
raise HTTPException(status_code=500, detail="Acquisition not initialized")
|
raise HTTPException(status_code=500, detail="Сбор данных не инициализирован")
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"running": acquisition.is_running,
|
"running": acquisition.is_running,
|
||||||
@ -45,14 +45,14 @@ async def start_acquisition() -> dict[str, Any]:
|
|||||||
acquisition = singletons.vna_data_acquisition_instance
|
acquisition = singletons.vna_data_acquisition_instance
|
||||||
if acquisition is None:
|
if acquisition is None:
|
||||||
logger.error("Acquisition singleton is not initialized")
|
logger.error("Acquisition singleton is not initialized")
|
||||||
raise HTTPException(status_code=500, detail="Acquisition not initialized")
|
raise HTTPException(status_code=500, detail="Сбор данных не инициализирован")
|
||||||
|
|
||||||
if not acquisition.is_running:
|
if not acquisition.is_running:
|
||||||
acquisition.start()
|
acquisition.start()
|
||||||
logger.info("Acquisition thread started via API")
|
logger.info("Acquisition thread started via API")
|
||||||
|
|
||||||
acquisition.set_continuous_mode(True)
|
acquisition.set_continuous_mode(True)
|
||||||
return {"success": True, "message": "Acquisition started"}
|
return {"success": True, "message": "Сбор данных запущен"}
|
||||||
except HTTPException:
|
except HTTPException:
|
||||||
raise
|
raise
|
||||||
except Exception as exc: # noqa: BLE001
|
except Exception as exc: # noqa: BLE001
|
||||||
@ -69,14 +69,14 @@ async def stop_acquisition() -> dict[str, Any]:
|
|||||||
acquisition = singletons.vna_data_acquisition_instance
|
acquisition = singletons.vna_data_acquisition_instance
|
||||||
if acquisition is None:
|
if acquisition is None:
|
||||||
logger.error("Acquisition singleton is not initialized")
|
logger.error("Acquisition singleton is not initialized")
|
||||||
raise HTTPException(status_code=500, detail="Acquisition not initialized")
|
raise HTTPException(status_code=500, detail="Сбор данных не инициализирован")
|
||||||
|
|
||||||
if not acquisition.is_running:
|
if not acquisition.is_running:
|
||||||
return {"success": True, "message": "Acquisition already stopped"}
|
return {"success": True, "message": "Сбор данных уже остановлен"}
|
||||||
|
|
||||||
acquisition.pause()
|
acquisition.pause()
|
||||||
logger.info("Acquisition paused via API")
|
logger.info("Acquisition paused via API")
|
||||||
return {"success": True, "message": "Acquisition paused"}
|
return {"success": True, "message": "Сбор данных приостановлен"}
|
||||||
except HTTPException:
|
except HTTPException:
|
||||||
raise
|
raise
|
||||||
except Exception as exc: # noqa: BLE001
|
except Exception as exc: # noqa: BLE001
|
||||||
@ -94,14 +94,14 @@ async def trigger_single_sweep() -> dict[str, Any]:
|
|||||||
acquisition = singletons.vna_data_acquisition_instance
|
acquisition = singletons.vna_data_acquisition_instance
|
||||||
if acquisition is None:
|
if acquisition is None:
|
||||||
logger.error("Acquisition singleton is not initialized")
|
logger.error("Acquisition singleton is not initialized")
|
||||||
raise HTTPException(status_code=500, detail="Acquisition not initialized")
|
raise HTTPException(status_code=500, detail="Сбор данных не инициализирован")
|
||||||
|
|
||||||
if not acquisition.is_running:
|
if not acquisition.is_running:
|
||||||
acquisition.start()
|
acquisition.start()
|
||||||
logger.info("Acquisition thread started (single-sweep request)")
|
logger.info("Acquisition thread started (single-sweep request)")
|
||||||
|
|
||||||
acquisition.trigger_single_sweep()
|
acquisition.trigger_single_sweep()
|
||||||
return {"success": True, "message": "Single sweep triggered"}
|
return {"success": True, "message": "Одиночный свип запущен"}
|
||||||
except HTTPException:
|
except HTTPException:
|
||||||
raise
|
raise
|
||||||
except Exception as exc: # noqa: BLE001
|
except Exception as exc: # noqa: BLE001
|
||||||
@ -111,7 +111,7 @@ async def trigger_single_sweep() -> dict[str, Any]:
|
|||||||
|
|
||||||
@router.get("/acquisition/latest-sweep")
|
@router.get("/acquisition/latest-sweep")
|
||||||
async def get_latest_sweep(
|
async def get_latest_sweep(
|
||||||
limit: int = Query(10, ge=1, le=1000, description="Max number of points to include in response"),
|
limit: int = Query(10, ge=1, le=1000, description="Максимальное число точек в ответе"),
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Return the latest sweep metadata and a limited subset of points.
|
Return the latest sweep metadata and a limited subset of points.
|
||||||
@ -125,11 +125,11 @@ async def get_latest_sweep(
|
|||||||
acquisition = singletons.vna_data_acquisition_instance
|
acquisition = singletons.vna_data_acquisition_instance
|
||||||
if acquisition is None:
|
if acquisition is None:
|
||||||
logger.error("Acquisition singleton is not initialized")
|
logger.error("Acquisition singleton is not initialized")
|
||||||
raise HTTPException(status_code=500, detail="Acquisition not initialized")
|
raise HTTPException(status_code=500, detail="Сбор данных не инициализирован")
|
||||||
|
|
||||||
latest_sweep = acquisition.sweep_buffer.get_latest_sweep()
|
latest_sweep = acquisition.sweep_buffer.get_latest_sweep()
|
||||||
if not latest_sweep:
|
if not latest_sweep:
|
||||||
return {"sweep": None, "message": "No sweep data available"}
|
return {"sweep": None, "message": "Данные свипа недоступны"}
|
||||||
|
|
||||||
points = latest_sweep.points[:limit]
|
points = latest_sweep.points[:limit]
|
||||||
return {
|
return {
|
||||||
@ -139,7 +139,7 @@ async def get_latest_sweep(
|
|||||||
"total_points": latest_sweep.total_points,
|
"total_points": latest_sweep.total_points,
|
||||||
"points": points,
|
"points": points,
|
||||||
},
|
},
|
||||||
"message": f"Latest sweep #{latest_sweep.sweep_number} with {latest_sweep.total_points} points",
|
"message": f"Последний свип №{latest_sweep.sweep_number}, точек: {latest_sweep.total_points}",
|
||||||
}
|
}
|
||||||
except HTTPException:
|
except HTTPException:
|
||||||
raise
|
raise
|
||||||
|
|||||||
@ -14,4 +14,4 @@ router = APIRouter(prefix="/api/v1", tags=["health"])
|
|||||||
@router.get("/")
|
@router.get("/")
|
||||||
async def root():
|
async def root():
|
||||||
"""Root endpoint."""
|
"""Root endpoint."""
|
||||||
return {"message": "VNA System API", "version": "1.0.0"}
|
return {"message": "API системы VNA", "version": "1.0.0"}
|
||||||
|
|||||||
@ -67,14 +67,14 @@ async def set_preset(request: SetPresetRequest) -> dict[str, Any]:
|
|||||||
presets = singletons.settings_manager.get_available_presets()
|
presets = singletons.settings_manager.get_available_presets()
|
||||||
preset = next((p for p in presets if p.filename == request.filename), None)
|
preset = next((p for p in presets if p.filename == request.filename), None)
|
||||||
if preset is None:
|
if preset is None:
|
||||||
raise HTTPException(status_code=404, detail=f"Preset not found: {request.filename}")
|
raise HTTPException(status_code=404, detail=f"Пресет не найден: {request.filename}")
|
||||||
|
|
||||||
# Changing preset invalidates active calibration selection.
|
# Changing preset invalidates active calibration selection.
|
||||||
singletons.settings_manager.calibration_manager.clear_current_calibration()
|
singletons.settings_manager.calibration_manager.clear_current_calibration()
|
||||||
singletons.settings_manager.set_current_preset(preset)
|
singletons.settings_manager.set_current_preset(preset)
|
||||||
|
|
||||||
logger.info("Preset selected via API", filename=preset.filename, mode=preset.mode.value)
|
logger.info("Preset selected via API", filename=preset.filename, mode=preset.mode.value)
|
||||||
return {"success": True, "message": f"Preset set to {request.filename}"}
|
return {"success": True, "message": f"Пресет установлен: {request.filename}"}
|
||||||
except HTTPException:
|
except HTTPException:
|
||||||
raise
|
raise
|
||||||
except Exception as exc: # noqa: BLE001
|
except Exception as exc: # noqa: BLE001
|
||||||
@ -112,7 +112,7 @@ async def get_calibrations(preset_filename: str | None = None) -> list[Calibrati
|
|||||||
presets = singletons.settings_manager.get_available_presets()
|
presets = singletons.settings_manager.get_available_presets()
|
||||||
preset = next((p for p in presets if p.filename == preset_filename), None)
|
preset = next((p for p in presets if p.filename == preset_filename), None)
|
||||||
if preset is None:
|
if preset is None:
|
||||||
raise HTTPException(status_code=404, detail=f"Preset not found: {preset_filename}")
|
raise HTTPException(status_code=404, detail=f"Пресет не найден: {preset_filename}")
|
||||||
|
|
||||||
calibrations = singletons.settings_manager.get_available_calibrations(preset)
|
calibrations = singletons.settings_manager.get_available_calibrations(preset)
|
||||||
details: list[CalibrationModel] = []
|
details: list[CalibrationModel] = []
|
||||||
@ -153,14 +153,14 @@ async def start_calibration(request: StartCalibrationRequest) -> dict[str, Any]:
|
|||||||
presets = singletons.settings_manager.get_available_presets()
|
presets = singletons.settings_manager.get_available_presets()
|
||||||
preset = next((p for p in presets if p.filename == request.preset_filename), None)
|
preset = next((p for p in presets if p.filename == request.preset_filename), None)
|
||||||
if preset is None:
|
if preset is None:
|
||||||
raise HTTPException(status_code=404, detail=f"Preset not found: {request.preset_filename}")
|
raise HTTPException(status_code=404, detail=f"Пресет не найден: {request.preset_filename}")
|
||||||
|
|
||||||
calib = singletons.settings_manager.start_new_calibration(preset)
|
calib = singletons.settings_manager.start_new_calibration(preset)
|
||||||
required = singletons.settings_manager.get_required_standards(calib.preset.mode)
|
required = singletons.settings_manager.get_required_standards(calib.preset.mode)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"success": True,
|
"success": True,
|
||||||
"message": "Calibration started",
|
"message": "Калибровка запущена",
|
||||||
"preset": calib.preset.filename,
|
"preset": calib.preset.filename,
|
||||||
"required_standards": [s.value for s in required],
|
"required_standards": [s.value for s in required],
|
||||||
}
|
}
|
||||||
@ -178,7 +178,7 @@ async def add_calibration_standard(request: CalibrateStandardRequest) -> dict[st
|
|||||||
try:
|
try:
|
||||||
standard = CalibrationStandard(request.standard)
|
standard = CalibrationStandard(request.standard)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
raise HTTPException(status_code=400, detail=f"Invalid calibration standard: {request.standard}")
|
raise HTTPException(status_code=400, detail=f"Недопустимый стандарт калибровки: {request.standard}")
|
||||||
|
|
||||||
sweep_no = singletons.settings_manager.capture_calibration_standard_from_acquisition(
|
sweep_no = singletons.settings_manager.capture_calibration_standard_from_acquisition(
|
||||||
standard, singletons.vna_data_acquisition_instance
|
standard, singletons.vna_data_acquisition_instance
|
||||||
@ -189,7 +189,7 @@ async def add_calibration_standard(request: CalibrateStandardRequest) -> dict[st
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
"success": True,
|
"success": True,
|
||||||
"message": f"Added {standard.value} standard from sweep {sweep_no}",
|
"message": f"Добавлен стандарт {standard.value} из свипа {sweep_no}",
|
||||||
"sweep_number": sweep_no,
|
"sweep_number": sweep_no,
|
||||||
"progress": f"{progress[0]}/{progress[1]}",
|
"progress": f"{progress[0]}/{progress[1]}",
|
||||||
"is_complete": working.is_complete() if working else False,
|
"is_complete": working.is_complete() if working else False,
|
||||||
@ -208,7 +208,7 @@ async def save_calibration(request: SaveCalibrationRequest) -> dict[str, Any]:
|
|||||||
saved = singletons.settings_manager.save_calibration_set(request.name)
|
saved = singletons.settings_manager.save_calibration_set(request.name)
|
||||||
return {
|
return {
|
||||||
"success": True,
|
"success": True,
|
||||||
"message": f"Calibration '{request.name}' saved successfully",
|
"message": f"Калибровка \"{request.name}\" успешно сохранена",
|
||||||
"preset": saved.preset.filename,
|
"preset": saved.preset.filename,
|
||||||
"standards": [s.value for s in saved.standards.keys()],
|
"standards": [s.value for s in saved.standards.keys()],
|
||||||
}
|
}
|
||||||
@ -226,10 +226,10 @@ async def set_calibration(request: SetCalibrationRequest) -> dict[str, Any]:
|
|||||||
presets = singletons.settings_manager.get_available_presets()
|
presets = singletons.settings_manager.get_available_presets()
|
||||||
preset = next((p for p in presets if p.filename == request.preset_filename), None)
|
preset = next((p for p in presets if p.filename == request.preset_filename), None)
|
||||||
if preset is None:
|
if preset is None:
|
||||||
raise HTTPException(status_code=404, detail=f"Preset not found: {request.preset_filename}")
|
raise HTTPException(status_code=404, detail=f"Пресет не найден: {request.preset_filename}")
|
||||||
|
|
||||||
singletons.settings_manager.set_current_calibration(request.name, preset)
|
singletons.settings_manager.set_current_calibration(request.name, preset)
|
||||||
return {"success": True, "message": f"Calibration set to '{request.name}'"}
|
return {"success": True, "message": f"Калибровка установлена: \"{request.name}\""}
|
||||||
except HTTPException:
|
except HTTPException:
|
||||||
raise
|
raise
|
||||||
except Exception as exc: # noqa: BLE001
|
except Exception as exc: # noqa: BLE001
|
||||||
@ -266,7 +266,7 @@ async def remove_calibration_standard(request: RemoveStandardRequest) -> dict[st
|
|||||||
try:
|
try:
|
||||||
standard = CalibrationStandard(request.standard)
|
standard = CalibrationStandard(request.standard)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
raise HTTPException(status_code=400, detail=f"Invalid calibration standard: {request.standard}")
|
raise HTTPException(status_code=400, detail=f"Недопустимый стандарт калибровки: {request.standard}")
|
||||||
|
|
||||||
singletons.settings_manager.remove_calibration_standard(standard)
|
singletons.settings_manager.remove_calibration_standard(standard)
|
||||||
|
|
||||||
@ -275,7 +275,7 @@ async def remove_calibration_standard(request: RemoveStandardRequest) -> dict[st
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
"success": True,
|
"success": True,
|
||||||
"message": f"Removed {standard.value} standard",
|
"message": f"Стандарт {standard.value} удалён",
|
||||||
"progress": f"{progress[0]}/{progress[1]}",
|
"progress": f"{progress[0]}/{progress[1]}",
|
||||||
"is_complete": working.is_complete() if working else False,
|
"is_complete": working.is_complete() if working else False,
|
||||||
}
|
}
|
||||||
@ -319,18 +319,18 @@ async def get_calibration_standards_plots(
|
|||||||
presets = singletons.settings_manager.get_available_presets()
|
presets = singletons.settings_manager.get_available_presets()
|
||||||
preset = next((p for p in presets if p.filename == preset_filename), None)
|
preset = next((p for p in presets if p.filename == preset_filename), None)
|
||||||
if preset is None:
|
if preset is None:
|
||||||
raise HTTPException(status_code=404, detail=f"Preset not found: {preset_filename}")
|
raise HTTPException(status_code=404, detail=f"Пресет не найден: {preset_filename}")
|
||||||
else:
|
else:
|
||||||
preset = singletons.settings_manager.get_current_preset()
|
preset = singletons.settings_manager.get_current_preset()
|
||||||
if preset is None:
|
if preset is None:
|
||||||
raise HTTPException(status_code=400, detail="No current preset selected")
|
raise HTTPException(status_code=400, detail="Текущий пресет не выбран")
|
||||||
|
|
||||||
# Resolve calibration directory (uses manager's internal layout)
|
# Resolve calibration directory (uses manager's internal layout)
|
||||||
calibration_manager = singletons.settings_manager.calibration_manager
|
calibration_manager = singletons.settings_manager.calibration_manager
|
||||||
calibration_dir = calibration_manager._get_preset_calibration_dir(preset) / calibration_name # noqa: SLF001
|
calibration_dir = calibration_manager._get_preset_calibration_dir(preset) / calibration_name # noqa: SLF001
|
||||||
|
|
||||||
if not calibration_dir.exists():
|
if not calibration_dir.exists():
|
||||||
raise HTTPException(status_code=404, detail=f"Calibration not found: {calibration_name}")
|
raise HTTPException(status_code=404, detail=f"Калибровка не найдена: {calibration_name}")
|
||||||
|
|
||||||
individual_plots = generate_standards_magnitude_plots(calibration_dir, preset)
|
individual_plots = generate_standards_magnitude_plots(calibration_dir, preset)
|
||||||
|
|
||||||
@ -352,9 +352,9 @@ async def get_working_calibration_standards_plots() -> dict[str, Any]:
|
|||||||
try:
|
try:
|
||||||
working = singletons.settings_manager.get_current_working_calibration()
|
working = singletons.settings_manager.get_current_working_calibration()
|
||||||
if not working:
|
if not working:
|
||||||
raise HTTPException(status_code=404, detail="No working calibration active")
|
raise HTTPException(status_code=404, detail="Рабочая калибровка не активна")
|
||||||
if not working.standards:
|
if not working.standards:
|
||||||
raise HTTPException(status_code=404, detail="No standards captured in working calibration")
|
raise HTTPException(status_code=404, detail="В рабочей калибровке нет сохранённых стандартов")
|
||||||
|
|
||||||
from vna_system.core.visualization.magnitude_chart import generate_magnitude_plot_from_sweep_data
|
from vna_system.core.visualization.magnitude_chart import generate_magnitude_plot_from_sweep_data
|
||||||
|
|
||||||
@ -371,8 +371,8 @@ async def get_working_calibration_standards_plots() -> dict[str, Any]:
|
|||||||
fig = generate_magnitude_plot_from_sweep_data(sweep, working.preset)
|
fig = generate_magnitude_plot_from_sweep_data(sweep, working.preset)
|
||||||
if "error" not in fig and fig.get("data"):
|
if "error" not in fig and fig.get("data"):
|
||||||
fig["data"][0]["line"]["color"] = standard_colors.get(standard.value, "#1f77b4")
|
fig["data"][0]["line"]["color"] = standard_colors.get(standard.value, "#1f77b4")
|
||||||
fig["data"][0]["name"] = f"{standard.value.upper()} Standard"
|
fig["data"][0]["name"] = f"Стандарт {standard.value.upper()}"
|
||||||
fig["layout"]["title"] = f"{standard.value.upper()} Standard Magnitude (Working)"
|
fig["layout"]["title"] = f"Амплитуда стандарта {standard.value.upper()} (рабочая)"
|
||||||
|
|
||||||
fig["raw_sweep_data"] = {
|
fig["raw_sweep_data"] = {
|
||||||
"sweep_number": sweep.sweep_number,
|
"sweep_number": sweep.sweep_number,
|
||||||
@ -390,13 +390,13 @@ async def get_working_calibration_standards_plots() -> dict[str, Any]:
|
|||||||
}
|
}
|
||||||
individual[standard.value] = fig
|
individual[standard.value] = fig
|
||||||
except Exception as exc: # noqa: BLE001
|
except Exception as exc: # noqa: BLE001
|
||||||
individual[standard.value] = {"error": f"Failed to generate plot for {standard.value}: {exc}"}
|
individual[standard.value] = {"error": f"Не удалось построить график для {standard.value}: {exc}"}
|
||||||
|
|
||||||
if not individual:
|
if not individual:
|
||||||
raise HTTPException(status_code=404, detail="No valid plots generated for working calibration")
|
raise HTTPException(status_code=404, detail="Не удалось создать графики для рабочей калибровки")
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"calibration_name": "Working Calibration",
|
"calibration_name": "Рабочая калибровка",
|
||||||
"preset": {"filename": working.preset.filename, "mode": working.preset.mode.value},
|
"preset": {"filename": working.preset.filename, "mode": working.preset.mode.value},
|
||||||
"individual_plots": individual,
|
"individual_plots": individual,
|
||||||
"is_working": True,
|
"is_working": True,
|
||||||
@ -425,11 +425,11 @@ async def get_references(preset_filename: str | None = None) -> List[dict[str, A
|
|||||||
None
|
None
|
||||||
)
|
)
|
||||||
if not preset:
|
if not preset:
|
||||||
raise HTTPException(status_code=404, detail=f"Preset '{preset_filename}' not found")
|
raise HTTPException(status_code=404, detail=f"Пресет \"{preset_filename}\" не найден")
|
||||||
else:
|
else:
|
||||||
preset = settings_manager.get_current_preset()
|
preset = settings_manager.get_current_preset()
|
||||||
if not preset:
|
if not preset:
|
||||||
raise HTTPException(status_code=400, detail="No current preset set and no preset_filename provided")
|
raise HTTPException(status_code=400, detail="Текущий пресет не выбран и параметр preset_filename не указан")
|
||||||
|
|
||||||
references = settings_manager.get_available_references(preset)
|
references = settings_manager.get_available_references(preset)
|
||||||
|
|
||||||
@ -462,11 +462,11 @@ async def get_current_reference(preset_filename: str | None = None) -> dict[str,
|
|||||||
None
|
None
|
||||||
)
|
)
|
||||||
if not preset:
|
if not preset:
|
||||||
raise HTTPException(status_code=404, detail=f"Preset '{preset_filename}' not found")
|
raise HTTPException(status_code=404, detail=f"Пресет \"{preset_filename}\" не найден")
|
||||||
else:
|
else:
|
||||||
preset = settings_manager.get_current_preset()
|
preset = settings_manager.get_current_preset()
|
||||||
if not preset:
|
if not preset:
|
||||||
raise HTTPException(status_code=400, detail="No current preset set and no preset_filename provided")
|
raise HTTPException(status_code=400, detail="Текущий пресет не выбран и параметр preset_filename не указан")
|
||||||
|
|
||||||
current_ref = settings_manager.get_current_reference(preset)
|
current_ref = settings_manager.get_current_reference(preset)
|
||||||
|
|
||||||
@ -500,11 +500,11 @@ async def create_reference(request: CreateReferenceRequest) -> dict[str, Any]:
|
|||||||
None
|
None
|
||||||
)
|
)
|
||||||
if not preset:
|
if not preset:
|
||||||
raise HTTPException(status_code=404, detail=f"Preset '{request.preset_filename}' not found")
|
raise HTTPException(status_code=404, detail=f"Пресет \"{request.preset_filename}\" не найден")
|
||||||
else:
|
else:
|
||||||
preset = settings_manager.get_current_preset()
|
preset = settings_manager.get_current_preset()
|
||||||
if not preset:
|
if not preset:
|
||||||
raise HTTPException(status_code=400, detail="No current preset set and no preset_filename provided")
|
raise HTTPException(status_code=400, detail="Текущий пресет не выбран и параметр preset_filename не указан")
|
||||||
|
|
||||||
# Create reference using the new capture method
|
# Create reference using the new capture method
|
||||||
reference_info = settings_manager.capture_reference_from_acquisition(
|
reference_info = settings_manager.capture_reference_from_acquisition(
|
||||||
@ -517,7 +517,7 @@ async def create_reference(request: CreateReferenceRequest) -> dict[str, Any]:
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
"success": True,
|
"success": True,
|
||||||
"message": f"Reference '{request.name}' created successfully",
|
"message": f"Эталон \"{request.name}\" успешно создан",
|
||||||
"reference": {
|
"reference": {
|
||||||
"name": reference_info.name,
|
"name": reference_info.name,
|
||||||
"timestamp": reference_info.timestamp.isoformat(),
|
"timestamp": reference_info.timestamp.isoformat(),
|
||||||
@ -545,20 +545,20 @@ async def set_current_reference(request: SetReferenceRequest) -> dict[str, Any]:
|
|||||||
None
|
None
|
||||||
)
|
)
|
||||||
if not preset:
|
if not preset:
|
||||||
raise HTTPException(status_code=404, detail=f"Preset '{request.preset_filename}' not found")
|
raise HTTPException(status_code=404, detail=f"Пресет \"{request.preset_filename}\" не найден")
|
||||||
else:
|
else:
|
||||||
preset = settings_manager.get_current_preset()
|
preset = settings_manager.get_current_preset()
|
||||||
if not preset:
|
if not preset:
|
||||||
raise HTTPException(status_code=400, detail="No current preset set and no preset_filename provided")
|
raise HTTPException(status_code=400, detail="Текущий пресет не выбран и параметр preset_filename не указан")
|
||||||
|
|
||||||
success = settings_manager.set_current_reference(request.name, preset)
|
success = settings_manager.set_current_reference(request.name, preset)
|
||||||
|
|
||||||
if not success:
|
if not success:
|
||||||
raise HTTPException(status_code=404, detail=f"Reference '{request.name}' not found")
|
raise HTTPException(status_code=404, detail=f"Эталон \"{request.name}\" не найден")
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"success": True,
|
"success": True,
|
||||||
"message": f"Reference '{request.name}' set as current"
|
"message": f"Эталон \"{request.name}\" выбран текущим"
|
||||||
}
|
}
|
||||||
except HTTPException:
|
except HTTPException:
|
||||||
raise
|
raise
|
||||||
@ -575,11 +575,11 @@ async def clear_current_reference() -> dict[str, Any]:
|
|||||||
success = settings_manager.clear_current_reference()
|
success = settings_manager.clear_current_reference()
|
||||||
|
|
||||||
if not success:
|
if not success:
|
||||||
raise HTTPException(status_code=500, detail="Failed to clear current reference")
|
raise HTTPException(status_code=500, detail="Не удалось сбросить текущий эталон")
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"success": True,
|
"success": True,
|
||||||
"message": "Current reference cleared"
|
"message": "Текущий эталон сброшен"
|
||||||
}
|
}
|
||||||
except HTTPException:
|
except HTTPException:
|
||||||
raise
|
raise
|
||||||
@ -600,15 +600,15 @@ async def get_reference(reference_name: str, preset_filename: str | None = None)
|
|||||||
None
|
None
|
||||||
)
|
)
|
||||||
if not preset:
|
if not preset:
|
||||||
raise HTTPException(status_code=404, detail=f"Preset '{preset_filename}' not found")
|
raise HTTPException(status_code=404, detail=f"Пресет \"{preset_filename}\" не найден")
|
||||||
else:
|
else:
|
||||||
preset = settings_manager.get_current_preset()
|
preset = settings_manager.get_current_preset()
|
||||||
if not preset:
|
if not preset:
|
||||||
raise HTTPException(status_code=400, detail="No current preset set and no preset_filename provided")
|
raise HTTPException(status_code=400, detail="Текущий пресет не выбран и параметр preset_filename не указан")
|
||||||
|
|
||||||
info = settings_manager.get_reference_info(reference_name, preset)
|
info = settings_manager.get_reference_info(reference_name, preset)
|
||||||
if not info:
|
if not info:
|
||||||
raise HTTPException(status_code=404, detail=f"Reference '{reference_name}' not found")
|
raise HTTPException(status_code=404, detail=f"Эталон \"{reference_name}\" не найден")
|
||||||
|
|
||||||
return ReferenceModel(
|
return ReferenceModel(
|
||||||
name=info.name,
|
name=info.name,
|
||||||
@ -636,19 +636,19 @@ async def get_reference_plot(reference_name: str, preset_filename: str | None =
|
|||||||
None
|
None
|
||||||
)
|
)
|
||||||
if not preset:
|
if not preset:
|
||||||
raise HTTPException(status_code=404, detail=f"Preset '{preset_filename}' not found")
|
raise HTTPException(status_code=404, detail=f"Пресет \"{preset_filename}\" не найден")
|
||||||
else:
|
else:
|
||||||
preset = settings_manager.get_current_preset()
|
preset = settings_manager.get_current_preset()
|
||||||
if not preset:
|
if not preset:
|
||||||
raise HTTPException(status_code=400, detail="No current preset set and no preset_filename provided")
|
raise HTTPException(status_code=400, detail="Текущий пресет не выбран и параметр preset_filename не указан")
|
||||||
|
|
||||||
reference_info = settings_manager.get_reference_info(reference_name, preset)
|
reference_info = settings_manager.get_reference_info(reference_name, preset)
|
||||||
if not reference_info:
|
if not reference_info:
|
||||||
raise HTTPException(status_code=404, detail=f"Reference '{reference_name}' not found")
|
raise HTTPException(status_code=404, detail=f"Эталон \"{reference_name}\" не найден")
|
||||||
|
|
||||||
sweep_data = settings_manager.reference_manager.get_reference_sweep(reference_name, preset)
|
sweep_data = settings_manager.reference_manager.get_reference_sweep(reference_name, preset)
|
||||||
if not sweep_data:
|
if not sweep_data:
|
||||||
raise HTTPException(status_code=404, detail=f"Reference data not found for '{reference_name}'")
|
raise HTTPException(status_code=404, detail=f"Данные эталона \"{reference_name}\" не найдены")
|
||||||
|
|
||||||
from vna_system.core.visualization.magnitude_chart import generate_magnitude_plot_from_sweep_data
|
from vna_system.core.visualization.magnitude_chart import generate_magnitude_plot_from_sweep_data
|
||||||
|
|
||||||
@ -682,20 +682,20 @@ async def delete_reference(reference_name: str, preset_filename: str | None = No
|
|||||||
None
|
None
|
||||||
)
|
)
|
||||||
if not preset:
|
if not preset:
|
||||||
raise HTTPException(status_code=404, detail=f"Preset '{preset_filename}' not found")
|
raise HTTPException(status_code=404, detail=f"Пресет \"{preset_filename}\" не найден")
|
||||||
else:
|
else:
|
||||||
preset = settings_manager.get_current_preset()
|
preset = settings_manager.get_current_preset()
|
||||||
if not preset:
|
if not preset:
|
||||||
raise HTTPException(status_code=400, detail="No current preset set and no preset_filename provided")
|
raise HTTPException(status_code=400, detail="Текущий пресет не выбран и параметр preset_filename не указан")
|
||||||
|
|
||||||
success = settings_manager.delete_reference(reference_name, preset)
|
success = settings_manager.delete_reference(reference_name, preset)
|
||||||
|
|
||||||
if not success:
|
if not success:
|
||||||
raise HTTPException(status_code=404, detail=f"Reference '{reference_name}' not found")
|
raise HTTPException(status_code=404, detail=f"Эталон \"{reference_name}\" не найден")
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"success": True,
|
"success": True,
|
||||||
"message": f"Reference '{reference_name}' deleted successfully"
|
"message": f"Эталон \"{reference_name}\" успешно удалён"
|
||||||
}
|
}
|
||||||
except HTTPException:
|
except HTTPException:
|
||||||
raise
|
raise
|
||||||
@ -709,7 +709,7 @@ async def clear_current_calibration() -> dict[str, Any]:
|
|||||||
"""Clear the current calibration selection."""
|
"""Clear the current calibration selection."""
|
||||||
try:
|
try:
|
||||||
singletons.settings_manager.calibration_manager.clear_current_calibration()
|
singletons.settings_manager.calibration_manager.clear_current_calibration()
|
||||||
return {"success": True, "message": "Current calibration cleared"}
|
return {"success": True, "message": "Текущая калибровка сброшена"}
|
||||||
except Exception as exc: # noqa: BLE001
|
except Exception as exc: # noqa: BLE001
|
||||||
logger.error("Failed to clear current calibration", error=repr(exc))
|
logger.error("Failed to clear current calibration", error=repr(exc))
|
||||||
raise HTTPException(status_code=500, detail=str(exc))
|
raise HTTPException(status_code=500, detail=str(exc))
|
||||||
@ -727,17 +727,17 @@ async def delete_calibration(calibration_name: str, preset_filename: str | None
|
|||||||
None
|
None
|
||||||
)
|
)
|
||||||
if not preset:
|
if not preset:
|
||||||
raise HTTPException(status_code=404, detail=f"Preset '{preset_filename}' not found")
|
raise HTTPException(status_code=404, detail=f"Пресет \"{preset_filename}\" не найден")
|
||||||
else:
|
else:
|
||||||
preset = settings_manager.get_current_preset()
|
preset = settings_manager.get_current_preset()
|
||||||
if not preset:
|
if not preset:
|
||||||
raise HTTPException(status_code=400, detail="No current preset set and no preset_filename provided")
|
raise HTTPException(status_code=400, detail="Текущий пресет не выбран и параметр preset_filename не указан")
|
||||||
|
|
||||||
settings_manager.delete_calibration(preset, calibration_name)
|
settings_manager.delete_calibration(preset, calibration_name)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"success": True,
|
"success": True,
|
||||||
"message": f"Calibration '{calibration_name}' deleted"
|
"message": f"Калибровка \"{calibration_name}\" удалена"
|
||||||
}
|
}
|
||||||
except HTTPException:
|
except HTTPException:
|
||||||
raise
|
raise
|
||||||
|
|||||||
@ -28,7 +28,7 @@ async def dashboard():
|
|||||||
if not index_file.exists():
|
if not index_file.exists():
|
||||||
logger.error(f"Dashboard template not found: {index_file}")
|
logger.error(f"Dashboard template not found: {index_file}")
|
||||||
return HTMLResponse(
|
return HTMLResponse(
|
||||||
content="<h1>Dashboard Not Available</h1><p>The web UI template could not be found.</p>",
|
content="<h1>Панель недоступна</h1><p>Не удалось найти шаблон веб-интерфейса.</p>",
|
||||||
status_code=404
|
status_code=404
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -40,7 +40,7 @@ async def dashboard():
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error serving dashboard: {e}")
|
logger.error(f"Error serving dashboard: {e}")
|
||||||
return HTMLResponse(
|
return HTMLResponse(
|
||||||
content=f"<h1>Error</h1><p>Unable to load dashboard: {e}</p>",
|
content=f"<h1>Ошибка</h1><p>Не удалось загрузить панель: {e}</p>",
|
||||||
status_code=500
|
status_code=500
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -48,10 +48,10 @@ async def dashboard():
|
|||||||
async def health_ui():
|
async def health_ui():
|
||||||
"""Health check endpoint for web UI."""
|
"""Health check endpoint for web UI."""
|
||||||
return {
|
return {
|
||||||
"service": "VNA Web UI",
|
"service": "Веб-интерфейс VNA",
|
||||||
"status": "healthy",
|
"status": "работает",
|
||||||
"web_ui_dir": str(WEB_UI_DIR),
|
"web_ui_dir": str(WEB_UI_DIR),
|
||||||
"static_dir_exists": STATIC_DIR.exists(),
|
"static_dir_exists": STATIC_DIR.exists(),
|
||||||
"templates_dir_exists": TEMPLATES_DIR.exists(),
|
"templates_dir_exists": TEMPLATES_DIR.exists(),
|
||||||
"index_exists": (TEMPLATES_DIR / "index.html").exists()
|
"index_exists": (TEMPLATES_DIR / "index.html").exists()
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,5 +4,5 @@
|
|||||||
"autoscale": true,
|
"autoscale": true,
|
||||||
"show_magnitude": true,
|
"show_magnitude": true,
|
||||||
"show_phase": false,
|
"show_phase": false,
|
||||||
"open_air": false
|
"open_air": true
|
||||||
}
|
}
|
||||||
@ -94,6 +94,23 @@
|
|||||||
height: 16px;
|
height: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.btn__shortcut {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 0 var(--space-2);
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
border: 1px solid var(--border-secondary);
|
||||||
|
background-color: var(--bg-tertiary);
|
||||||
|
color: var(--text-tertiary);
|
||||||
|
font-size: var(--font-size-xs);
|
||||||
|
font-weight: var(--font-weight-medium);
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
line-height: 1;
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
/* Processor Toggles */
|
/* Processor Toggles */
|
||||||
.processor-toggles {
|
.processor-toggles {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@ -397,6 +397,10 @@ body {
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.header__controls .btn__shortcut {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
.header__controls .btn {
|
.header__controls .btn {
|
||||||
min-width: auto;
|
min-width: auto;
|
||||||
padding: var(--space-2);
|
padding: var(--space-2);
|
||||||
|
|||||||
@ -198,8 +198,25 @@ class VNADashboard {
|
|||||||
* Handle keyboard shortcuts
|
* Handle keyboard shortcuts
|
||||||
*/
|
*/
|
||||||
handleKeyboardShortcuts(event) {
|
handleKeyboardShortcuts(event) {
|
||||||
|
const activeElement = document.activeElement;
|
||||||
|
const activeTag = activeElement?.tagName?.toLowerCase();
|
||||||
|
const isEditable = activeElement?.isContentEditable ||
|
||||||
|
activeTag === 'input' ||
|
||||||
|
activeTag === 'textarea' ||
|
||||||
|
activeTag === 'select';
|
||||||
|
const key = event.key?.toLowerCase();
|
||||||
|
|
||||||
|
if (!isEditable && !event.ctrlKey && !event.metaKey && !event.altKey && key === 's') {
|
||||||
|
if (event.repeat) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
event.preventDefault();
|
||||||
|
this.acquisition?.triggerSingleSweep?.();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Ctrl/Cmd + Shift + R: Reconnect WebSocket
|
// Ctrl/Cmd + Shift + R: Reconnect WebSocket
|
||||||
if ((event.ctrlKey || event.metaKey) && event.key === 'r' && event.shiftKey) {
|
if ((event.ctrlKey || event.metaKey) && key === 'r' && event.shiftKey) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
this.websocket.reconnect();
|
this.websocket.reconnect();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -51,9 +51,10 @@
|
|||||||
<span data-icon="square"></span>
|
<span data-icon="square"></span>
|
||||||
<span class="btn__text">Стоп</span>
|
<span class="btn__text">Стоп</span>
|
||||||
</button>
|
</button>
|
||||||
<button class="btn btn--accent btn--sm" id="singleSweepBtn" title="Запустить одиночный свип">
|
<button class="btn btn--accent btn--sm" id="singleSweepBtn" title="Запустить одиночный свип (клавиша S)" aria-keyshortcuts="S">
|
||||||
<span data-icon="zap"></span>
|
<span data-icon="zap"></span>
|
||||||
<span class="btn__text">Одиночный</span>
|
<span class="btn__text">Одиночный</span>
|
||||||
|
<span class="btn__shortcut" aria-hidden="true">S</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user