418 lines
16 KiB
Python
418 lines
16 KiB
Python
from fastapi import APIRouter, HTTPException
|
|
from typing import List
|
|
from pathlib import Path
|
|
|
|
import vna_system.core.singletons as singletons
|
|
from vna_system.core.settings.calibration_manager import CalibrationStandard
|
|
from vna_system.core.visualization.magnitude_chart import generate_standards_magnitude_plots, generate_combined_standards_plot
|
|
from vna_system.api.models.settings import (
|
|
PresetModel,
|
|
CalibrationModel,
|
|
SettingsStatusModel,
|
|
SetPresetRequest,
|
|
StartCalibrationRequest,
|
|
CalibrateStandardRequest,
|
|
SaveCalibrationRequest,
|
|
SetCalibrationRequest,
|
|
RemoveStandardRequest,
|
|
WorkingCalibrationModel
|
|
)
|
|
|
|
router = APIRouter(prefix="/api/v1/settings", tags=["settings"])
|
|
|
|
|
|
@router.get("/status", response_model=SettingsStatusModel)
|
|
async def get_status():
|
|
"""Get current settings status"""
|
|
try:
|
|
status = singletons.settings_manager.get_status_summary()
|
|
return status
|
|
except Exception as e:
|
|
raise HTTPException(status_code=500, detail=str(e))
|
|
|
|
|
|
@router.get("/presets", response_model=List[PresetModel])
|
|
async def get_presets(mode: str | None = None):
|
|
"""Get all available configuration presets, optionally filtered by mode"""
|
|
try:
|
|
if mode:
|
|
from vna_system.core.settings.preset_manager import VNAMode
|
|
try:
|
|
vna_mode = VNAMode(mode.lower())
|
|
presets = singletons.settings_manager.get_presets_by_mode(vna_mode)
|
|
except ValueError:
|
|
raise HTTPException(status_code=400, detail=f"Invalid mode: {mode}")
|
|
else:
|
|
presets = singletons.settings_manager.get_available_presets()
|
|
|
|
return [
|
|
PresetModel(
|
|
filename=preset.filename,
|
|
mode=preset.mode.value,
|
|
start_freq=preset.start_freq,
|
|
stop_freq=preset.stop_freq,
|
|
points=preset.points,
|
|
bandwidth=preset.bandwidth
|
|
)
|
|
for preset in presets
|
|
]
|
|
except Exception as e:
|
|
raise HTTPException(status_code=500, detail=str(e))
|
|
|
|
|
|
@router.post("/preset/set")
|
|
async def set_preset(request: SetPresetRequest):
|
|
"""Set current configuration preset"""
|
|
try:
|
|
# Find preset by filename
|
|
presets = singletons.settings_manager.get_available_presets()
|
|
preset = next((p for p in presets if p.filename == request.filename), None)
|
|
|
|
if not preset:
|
|
raise HTTPException(status_code=404, detail=f"Preset not found: {request.filename}")
|
|
|
|
# Clear current calibration when changing preset
|
|
singletons.settings_manager.calibration_manager.clear_current_calibration()
|
|
|
|
singletons.settings_manager.set_current_preset(preset)
|
|
return {"success": True, "message": f"Preset set to {request.filename}"}
|
|
except Exception as e:
|
|
raise HTTPException(status_code=500, detail=str(e))
|
|
|
|
|
|
@router.get("/preset/current", response_model=PresetModel | None)
|
|
async def get_current_preset():
|
|
"""Get currently selected configuration preset"""
|
|
try:
|
|
preset = singletons.settings_manager.get_current_preset()
|
|
if not preset:
|
|
return None
|
|
|
|
return PresetModel(
|
|
filename=preset.filename,
|
|
mode=preset.mode.value,
|
|
start_freq=preset.start_freq,
|
|
stop_freq=preset.stop_freq,
|
|
points=preset.points,
|
|
bandwidth=preset.bandwidth
|
|
)
|
|
except Exception as e:
|
|
raise HTTPException(status_code=500, detail=str(e))
|
|
|
|
|
|
@router.get("/calibrations", response_model=List[CalibrationModel])
|
|
async def get_calibrations(preset_filename: str | None = None):
|
|
"""Get available calibrations for current or specified preset"""
|
|
try:
|
|
preset = None
|
|
if preset_filename:
|
|
presets = singletons.settings_manager.get_available_presets()
|
|
preset = next((p for p in presets if p.filename == preset_filename), None)
|
|
if not preset:
|
|
raise HTTPException(status_code=404, detail=f"Preset not found: {preset_filename}")
|
|
|
|
calibrations = singletons.settings_manager.get_available_calibrations(preset)
|
|
|
|
# Get detailed info for each calibration
|
|
calibration_details = []
|
|
current_preset = preset or singletons.settings_manager.get_current_preset()
|
|
|
|
if current_preset:
|
|
for calib_name in calibrations:
|
|
info = singletons.settings_manager.get_calibration_info(calib_name, current_preset)
|
|
|
|
# Convert standards format if needed
|
|
standards = info.get('standards', {})
|
|
if isinstance(standards, list):
|
|
# If standards is a list (from complete calibration), convert to dict
|
|
required_standards = singletons.settings_manager.get_required_standards(current_preset.mode)
|
|
standards = {std.value: std.value in standards for std in required_standards}
|
|
|
|
calibration_details.append(CalibrationModel(
|
|
name=calib_name,
|
|
is_complete=info.get('is_complete', False),
|
|
standards=standards
|
|
))
|
|
|
|
return calibration_details
|
|
except Exception as e:
|
|
raise HTTPException(status_code=500, detail=str(e))
|
|
|
|
|
|
@router.post("/calibration/start")
|
|
async def start_calibration(request: StartCalibrationRequest):
|
|
"""Start new calibration for current or specified preset"""
|
|
try:
|
|
preset = None
|
|
if request.preset_filename:
|
|
presets = singletons.settings_manager.get_available_presets()
|
|
preset = next((p for p in presets if p.filename == request.preset_filename), None)
|
|
if not preset:
|
|
raise HTTPException(status_code=404, detail=f"Preset not found: {request.preset_filename}")
|
|
|
|
calibration_set = singletons.settings_manager.start_new_calibration(preset)
|
|
required_standards = singletons.settings_manager.get_required_standards(calibration_set.preset.mode)
|
|
|
|
return {
|
|
"success": True,
|
|
"message": "Calibration started",
|
|
"preset": calibration_set.preset.filename,
|
|
"required_standards": [s.value for s in required_standards]
|
|
}
|
|
except Exception as e:
|
|
raise HTTPException(status_code=500, detail=str(e))
|
|
|
|
|
|
@router.post("/calibration/add-standard")
|
|
async def add_calibration_standard(request: CalibrateStandardRequest):
|
|
"""Add calibration standard from latest sweep"""
|
|
try:
|
|
# Validate standard
|
|
try:
|
|
standard = CalibrationStandard(request.standard)
|
|
except ValueError:
|
|
raise HTTPException(status_code=400, detail=f"Invalid calibration standard: {request.standard}")
|
|
|
|
# Capture from data acquisition
|
|
sweep_number = singletons.settings_manager.capture_calibration_standard_from_acquisition(
|
|
standard, singletons.vna_data_acquisition_instance
|
|
)
|
|
|
|
# Get current working calibration status
|
|
working_calib = singletons.settings_manager.get_current_working_calibration()
|
|
progress = working_calib.get_progress() if working_calib else (0, 0)
|
|
|
|
return {
|
|
"success": True,
|
|
"message": f"Added {standard.value} standard from sweep {sweep_number}",
|
|
"sweep_number": sweep_number,
|
|
"progress": f"{progress[0]}/{progress[1]}",
|
|
"is_complete": working_calib.is_complete() if working_calib else False
|
|
}
|
|
except Exception as e:
|
|
raise HTTPException(status_code=500, detail=str(e))
|
|
|
|
|
|
@router.post("/calibration/save")
|
|
async def save_calibration(request: SaveCalibrationRequest):
|
|
"""Save current working calibration set"""
|
|
try:
|
|
calibration_set = singletons.settings_manager.save_calibration_set(request.name)
|
|
|
|
return {
|
|
"success": True,
|
|
"message": f"Calibration '{request.name}' saved successfully",
|
|
"preset": calibration_set.preset.filename,
|
|
"standards": list(calibration_set.standards.keys())
|
|
}
|
|
except Exception as e:
|
|
raise HTTPException(status_code=500, detail=str(e))
|
|
|
|
|
|
@router.post("/calibration/set")
|
|
async def set_calibration(request: SetCalibrationRequest):
|
|
"""Set current active calibration"""
|
|
try:
|
|
preset = None
|
|
if request.preset_filename:
|
|
presets = singletons.settings_manager.get_available_presets()
|
|
preset = next((p for p in presets if p.filename == request.preset_filename), None)
|
|
if not preset:
|
|
raise HTTPException(status_code=404, detail=f"Preset not found: {request.preset_filename}")
|
|
|
|
singletons.settings_manager.set_current_calibration(request.name, preset)
|
|
|
|
return {
|
|
"success": True,
|
|
"message": f"Calibration set to '{request.name}'"
|
|
}
|
|
except Exception as e:
|
|
raise HTTPException(status_code=500, detail=str(e))
|
|
|
|
|
|
@router.get("/working-calibration", response_model=WorkingCalibrationModel)
|
|
async def get_working_calibration():
|
|
"""Get current working calibration status"""
|
|
try:
|
|
working_calib = singletons.settings_manager.get_current_working_calibration()
|
|
|
|
if not working_calib:
|
|
return WorkingCalibrationModel(active=False)
|
|
|
|
completed, total = working_calib.get_progress()
|
|
missing_standards = working_calib.get_missing_standards()
|
|
|
|
return WorkingCalibrationModel(
|
|
active=True,
|
|
preset=working_calib.preset.filename,
|
|
progress=f"{completed}/{total}",
|
|
is_complete=working_calib.is_complete(),
|
|
completed_standards=[s.value for s in working_calib.standards.keys()],
|
|
missing_standards=[s.value for s in missing_standards]
|
|
)
|
|
except Exception as e:
|
|
raise HTTPException(status_code=500, detail=str(e))
|
|
|
|
|
|
@router.delete("/calibration/remove-standard")
|
|
async def remove_calibration_standard(request: RemoveStandardRequest):
|
|
"""Remove calibration standard from current working set"""
|
|
try:
|
|
# Validate standard
|
|
try:
|
|
standard = CalibrationStandard(request.standard)
|
|
except ValueError:
|
|
raise HTTPException(status_code=400, detail=f"Invalid calibration standard: {request.standard}")
|
|
|
|
singletons.settings_manager.remove_calibration_standard(standard)
|
|
|
|
# Get current working calibration status
|
|
working_calib = singletons.settings_manager.get_current_working_calibration()
|
|
progress = working_calib.get_progress() if working_calib else (0, 0)
|
|
|
|
return {
|
|
"success": True,
|
|
"message": f"Removed {standard.value} standard",
|
|
"progress": f"{progress[0]}/{progress[1]}",
|
|
"is_complete": working_calib.is_complete() if working_calib else False
|
|
}
|
|
except Exception as e:
|
|
raise HTTPException(status_code=500, detail=str(e))
|
|
|
|
|
|
@router.get("/calibration/current")
|
|
async def get_current_calibration():
|
|
"""Get currently selected calibration details"""
|
|
try:
|
|
current_calib = singletons.settings_manager.get_current_calibration()
|
|
|
|
if not current_calib:
|
|
return {"active": False}
|
|
|
|
return {
|
|
"active": True,
|
|
"preset": {
|
|
"filename": current_calib.preset.filename,
|
|
"mode": current_calib.preset.mode.value
|
|
},
|
|
"calibration_name": current_calib.name,
|
|
"standards": [s.value for s in current_calib.standards.keys()],
|
|
"is_complete": current_calib.is_complete()
|
|
}
|
|
except Exception as e:
|
|
raise HTTPException(status_code=500, detail=str(e))
|
|
|
|
|
|
@router.get("/calibration/{calibration_name}/standards-plots")
|
|
async def get_calibration_standards_plots(calibration_name: str, preset_filename: str = None):
|
|
"""Get magnitude plots for all standards in a calibration set"""
|
|
try:
|
|
# Get preset
|
|
preset = None
|
|
if preset_filename:
|
|
presets = singletons.settings_manager.get_available_presets()
|
|
preset = next((p for p in presets if p.filename == preset_filename), None)
|
|
if not preset:
|
|
raise HTTPException(status_code=404, detail=f"Preset not found: {preset_filename}")
|
|
else:
|
|
preset = singletons.settings_manager.get_current_preset()
|
|
if not preset:
|
|
raise HTTPException(status_code=400, detail="No current preset selected")
|
|
|
|
# Get calibration directory
|
|
calibration_manager = singletons.settings_manager.calibration_manager
|
|
calibration_dir = calibration_manager._get_preset_calibration_dir(preset) / calibration_name
|
|
|
|
if not calibration_dir.exists():
|
|
raise HTTPException(status_code=404, detail=f"Calibration not found: {calibration_name}")
|
|
|
|
# Generate plots for each standard
|
|
individual_plots = generate_standards_magnitude_plots(calibration_dir, preset)
|
|
|
|
return {
|
|
"calibration_name": calibration_name,
|
|
"preset": {
|
|
"filename": preset.filename,
|
|
"mode": preset.mode.value
|
|
},
|
|
"individual_plots": individual_plots
|
|
}
|
|
except Exception as e:
|
|
raise HTTPException(status_code=500, detail=str(e))
|
|
|
|
|
|
@router.get("/working-calibration/standards-plots")
|
|
async def get_working_calibration_standards_plots():
|
|
"""Get magnitude plots for standards in current working calibration"""
|
|
try:
|
|
working_calib = singletons.settings_manager.get_current_working_calibration()
|
|
|
|
if not working_calib:
|
|
raise HTTPException(status_code=404, detail="No working calibration active")
|
|
|
|
# Check if there are any standards captured
|
|
if not working_calib.standards:
|
|
raise HTTPException(status_code=404, detail="No standards captured in working calibration")
|
|
|
|
# Generate plots directly from in-memory sweep data
|
|
from vna_system.core.visualization.magnitude_chart import generate_magnitude_plot_from_sweep_data
|
|
|
|
individual_plots = {}
|
|
standard_colors = {
|
|
'open': '#2ca02c', # Green
|
|
'short': '#d62728', # Red
|
|
'load': '#ff7f0e', # Orange
|
|
'through': '#1f77b4' # Blue
|
|
}
|
|
|
|
for standard, sweep_data in working_calib.standards.items():
|
|
try:
|
|
# Generate plot for this standard
|
|
plot_config = generate_magnitude_plot_from_sweep_data(sweep_data, working_calib.preset)
|
|
|
|
if 'error' not in plot_config:
|
|
# Customize color and title for this standard
|
|
if plot_config.get('data'):
|
|
plot_config['data'][0]['line']['color'] = standard_colors.get(standard.value, '#1f77b4')
|
|
plot_config['data'][0]['name'] = f'{standard.value.upper()} Standard'
|
|
plot_config['layout']['title'] = f'{standard.value.upper()} Standard Magnitude (Working)'
|
|
|
|
# Include raw sweep data for download
|
|
plot_config['raw_sweep_data'] = {
|
|
'sweep_number': sweep_data.sweep_number,
|
|
'timestamp': sweep_data.timestamp,
|
|
'total_points': sweep_data.total_points,
|
|
'points': sweep_data.points, # Raw complex data points
|
|
'file_path': None # No file path for working calibration
|
|
}
|
|
|
|
# Add frequency information
|
|
plot_config['frequency_info'] = {
|
|
'start_freq': working_calib.preset.start_freq,
|
|
'stop_freq': working_calib.preset.stop_freq,
|
|
'points': working_calib.preset.points,
|
|
'bandwidth': working_calib.preset.bandwidth
|
|
}
|
|
|
|
individual_plots[standard.value] = plot_config
|
|
else:
|
|
individual_plots[standard.value] = plot_config
|
|
|
|
except Exception as e:
|
|
individual_plots[standard.value] = {'error': f'Failed to generate plot for {standard.value}: {str(e)}'}
|
|
|
|
if not individual_plots:
|
|
raise HTTPException(status_code=404, detail="No valid plots generated for working calibration")
|
|
|
|
return {
|
|
"calibration_name": "Working Calibration",
|
|
"preset": {
|
|
"filename": working_calib.preset.filename,
|
|
"mode": working_calib.preset.mode.value
|
|
},
|
|
"individual_plots": individual_plots,
|
|
"is_working": True,
|
|
"is_complete": working_calib.is_complete()
|
|
}
|
|
except Exception as e:
|
|
raise HTTPException(status_code=500, detail=str(e)) |