some refactoring
This commit is contained in:
BIN
reference_data_acquisition/vna_protocol.bin
Normal file
BIN
reference_data_acquisition/vna_protocol.bin
Normal file
Binary file not shown.
@ -1 +1 @@
|
|||||||
config_inputs/s11_start100_stop8800_points1000_bw1khz.bin
|
config_inputs/s21_start100_stop8800_points1000_bw1khz.bin
|
||||||
1
vna_system/calibration/current_calibration
Symbolic link
1
vna_system/calibration/current_calibration
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
s21_start100_stop8800_points1000_bw1khz/bambambum
|
||||||
@ -1,10 +1,10 @@
|
|||||||
{
|
{
|
||||||
"open_air": true,
|
"open_air": false,
|
||||||
"axis": "phase",
|
"axis": "abs",
|
||||||
"data_limitation": "ph_only_1",
|
"data_limitation": "ph_only_2",
|
||||||
"cut": 0.988,
|
"cut": 0.417,
|
||||||
"max": 2.4,
|
"max": 0.6,
|
||||||
"gain": 1.2,
|
"gain": 0.0,
|
||||||
"start_freq": 100.0,
|
"start_freq": 100.0,
|
||||||
"stop_freq": 8800.0,
|
"stop_freq": 8800.0,
|
||||||
"clear_history": false,
|
"clear_history": false,
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"y_min": -80,
|
"y_min": -80,
|
||||||
"y_max": 40,
|
"y_max": 40,
|
||||||
"show_phase": true
|
"show_phase": false
|
||||||
}
|
}
|
||||||
@ -166,8 +166,8 @@ class BScanProcessor(BaseProcessor):
|
|||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
# Skip B-scan processing for S21 mode - only works with S11
|
# Skip B-scan processing for S21 mode - only works with S11
|
||||||
if vna_config and vna_config.get("mode") == "s21":
|
# if vna_config and vna_config.get("mode") == "s21":
|
||||||
return {"error": "B-scan only available in S11 mode"}
|
# return {"error": "B-scan only available in S11 mode"}
|
||||||
|
|
||||||
# Choose calibrated data when provided
|
# Choose calibrated data when provided
|
||||||
data_to_process = calibrated_data or sweep_data
|
data_to_process = calibrated_data or sweep_data
|
||||||
|
|||||||
@ -1,7 +1,8 @@
|
|||||||
import numpy as np
|
import numpy as np
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
from dataclasses import asdict
|
||||||
|
from vna_system.core.acquisition.sweep_buffer import SweepData
|
||||||
from vna_system.core.logging.logger import get_component_logger
|
from vna_system.core.logging.logger import get_component_logger
|
||||||
from vna_system.core.processors.base_processor import BaseProcessor, UIParameter
|
from vna_system.core.processors.base_processor import BaseProcessor, UIParameter
|
||||||
|
|
||||||
@ -30,7 +31,7 @@ class MagnitudeProcessor(BaseProcessor):
|
|||||||
# ------------------------------------------------------------------ #
|
# ------------------------------------------------------------------ #
|
||||||
# Core processing
|
# Core processing
|
||||||
# ------------------------------------------------------------------ #
|
# ------------------------------------------------------------------ #
|
||||||
def process_sweep(self, sweep_data: Any, calibrated_data: Any, vna_config: dict[str, Any]) -> dict[str, Any]:
|
def process_sweep(self, sweep_data: SweepData, calibrated_data: SweepData, vna_config: dict[str, Any]) -> dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Produce magnitude trace (dB) and ancillary info from a calibrated sweep.
|
Produce magnitude trace (dB) and ancillary info from a calibrated sweep.
|
||||||
|
|
||||||
@ -70,15 +71,21 @@ class MagnitudeProcessor(BaseProcessor):
|
|||||||
# Magnitude in dB (clamp zero magnitude to -120 dB)
|
# Magnitude in dB (clamp zero magnitude to -120 dB)
|
||||||
mags_db: list[float] = []
|
mags_db: list[float] = []
|
||||||
phases_deg: list[float] = []
|
phases_deg: list[float] = []
|
||||||
|
# real_points: list[str] = []
|
||||||
|
# imag_points: list[str] = []
|
||||||
|
|
||||||
for real, imag in points:
|
for real, imag in points:
|
||||||
complex_val = complex(real, imag)
|
complex_val = complex(real, imag)
|
||||||
|
# real_points.append(str(real))
|
||||||
|
# imag_points.append(str(imag))
|
||||||
mag = abs(complex_val)
|
mag = abs(complex_val)
|
||||||
mags_db.append(20.0 * np.log10(mag) if mag > 0.0 else -120.0)
|
mags_db.append(20.0 * np.log10(mag) if mag > 0.0 else -120.0)
|
||||||
phases_deg.append(np.degrees(np.angle(complex_val)))
|
phases_deg.append(np.degrees(np.angle(complex_val)))
|
||||||
|
|
||||||
result = {
|
result = {
|
||||||
"frequencies": freqs,
|
"frequencies": freqs,
|
||||||
|
# "real_points" : real_points,
|
||||||
|
# "imag_points" : imag_points,
|
||||||
"magnitudes_db": mags_db,
|
"magnitudes_db": mags_db,
|
||||||
"phases_deg": phases_deg,
|
"phases_deg": phases_deg,
|
||||||
"y_min": float(self._config.get("y_min", -80)),
|
"y_min": float(self._config.get("y_min", -80)),
|
||||||
|
|||||||
@ -270,9 +270,10 @@ class ProcessorManager:
|
|||||||
from .implementations.magnitude_processor import MagnitudeProcessor
|
from .implementations.magnitude_processor import MagnitudeProcessor
|
||||||
from .implementations.bscan_processor import BScanProcessor
|
from .implementations.bscan_processor import BScanProcessor
|
||||||
|
|
||||||
self.register_processor(MagnitudeProcessor(self.config_dir))
|
|
||||||
# self.register_processor(PhaseProcessor(self.config_dir))
|
# self.register_processor(PhaseProcessor(self.config_dir))
|
||||||
self.register_processor(BScanProcessor(self.config_dir))
|
self.register_processor(BScanProcessor(self.config_dir))
|
||||||
|
self.register_processor(MagnitudeProcessor(self.config_dir))
|
||||||
# self.register_processor(SmithChartProcessor(self.config_dir))
|
# self.register_processor(SmithChartProcessor(self.config_dir))
|
||||||
|
|
||||||
logger.info("Default processors registered", count=len(self._processors))
|
logger.info("Default processors registered", count=len(self._processors))
|
||||||
|
|||||||
@ -1 +1 @@
|
|||||||
s11_start100_stop8800_points1000_bw1khz/test1
|
s21_start100_stop8800_points1000_bw1khz/testet
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"name": "asd",
|
||||||
|
"timestamp": "2025-09-30T15:20:21.546972",
|
||||||
|
"preset_filename": "s11_start100_stop8800_points1000_bw1khz.bin",
|
||||||
|
"description": "asd",
|
||||||
|
"metadata": {}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@ -38,6 +38,7 @@ body {
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: var(--space-3);
|
gap: var(--space-3);
|
||||||
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.header__icon {
|
.header__icon {
|
||||||
@ -52,12 +53,57 @@ body {
|
|||||||
color: var(--text-primary);
|
color: var(--text-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.header__subtitle {
|
.header__summary {
|
||||||
font-size: var(--font-size-sm);
|
display: flex;
|
||||||
color: var(--text-tertiary);
|
align-items: center;
|
||||||
padding: var(--space-1) var(--space-2);
|
gap: var(--space-3);
|
||||||
|
padding: var(--space-2) var(--space-4);
|
||||||
background-color: var(--bg-tertiary);
|
background-color: var(--bg-tertiary);
|
||||||
border-radius: var(--radius-default);
|
border: 1px solid var(--border-primary);
|
||||||
|
border-radius: var(--radius-lg);
|
||||||
|
box-shadow: var(--shadow-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-summary__item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--space-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-summary__label {
|
||||||
|
font-size: var(--font-size-xs);
|
||||||
|
color: var(--text-tertiary);
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-summary__value {
|
||||||
|
font-size: var(--font-size-sm);
|
||||||
|
color: var(--text-primary);
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-summary__divider {
|
||||||
|
width: 1px;
|
||||||
|
height: 32px;
|
||||||
|
background-color: var(--border-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.header__summary {
|
||||||
|
width: 100%;
|
||||||
|
justify-content: space-between;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
row-gap: var(--space-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-summary__divider {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-summary__item {
|
||||||
|
flex: 1 1 45%;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.header__status {
|
.header__status {
|
||||||
@ -374,4 +420,4 @@ body {
|
|||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
gap: var(--space-4);
|
gap: var(--space-4);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -131,40 +131,43 @@
|
|||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
}
|
}
|
||||||
|
|
||||||
.preset-dropdown,
|
:is(.settings-select, .preset-dropdown, .calibration-dropdown, .reference-dropdown) {
|
||||||
.calibration-dropdown {
|
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
min-width: 220px;
|
||||||
padding: var(--space-3) var(--space-4);
|
padding: var(--space-3) var(--space-4);
|
||||||
|
padding-right: var(--space-10);
|
||||||
background: var(--bg-primary);
|
background: var(--bg-primary);
|
||||||
border: 1px solid var(--border-primary);
|
border: 1px solid var(--border-primary);
|
||||||
border-radius: var(--radius-lg);
|
border-radius: var(--radius-lg);
|
||||||
color: var(--text-primary);
|
color: var(--text-primary);
|
||||||
font-size: var(--font-size-sm);
|
font-size: var(--font-size-sm);
|
||||||
min-height: 3rem;
|
|
||||||
font-weight: var(--font-weight-medium);
|
font-weight: var(--font-weight-medium);
|
||||||
transition: all var(--transition-fast);
|
transition: all var(--transition-fast);
|
||||||
box-shadow: var(--shadow-sm);
|
box-shadow: var(--shadow-sm);
|
||||||
|
appearance: none;
|
||||||
|
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='14' viewBox='0 0 24 24' stroke-width='1.5' stroke='%2394a3b8' fill='none' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E");
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: right var(--space-4) center;
|
||||||
|
background-size: 14px 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.preset-dropdown:focus,
|
:is(.settings-select, .preset-dropdown, .calibration-dropdown, .reference-dropdown):focus {
|
||||||
.calibration-dropdown:focus {
|
|
||||||
outline: none;
|
outline: none;
|
||||||
border-color: var(--color-primary-500);
|
border-color: var(--color-primary-500);
|
||||||
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
|
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
|
||||||
background: var(--bg-surface);
|
background: var(--bg-surface);
|
||||||
}
|
}
|
||||||
|
|
||||||
.preset-dropdown:hover,
|
:is(.settings-select, .preset-dropdown, .calibration-dropdown, .reference-dropdown):hover:not(:disabled) {
|
||||||
.calibration-dropdown:hover {
|
|
||||||
border-color: var(--color-primary-500);
|
border-color: var(--color-primary-500);
|
||||||
transform: translateY(-1px);
|
transform: translateY(-1px);
|
||||||
box-shadow: var(--shadow-md);
|
box-shadow: var(--shadow-md);
|
||||||
}
|
}
|
||||||
|
|
||||||
.preset-dropdown:disabled,
|
:is(.settings-select, .preset-dropdown, .calibration-dropdown, .reference-dropdown):disabled {
|
||||||
.calibration-dropdown:disabled {
|
|
||||||
opacity: 0.6;
|
opacity: 0.6;
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
|
transform: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.preset-info {
|
.preset-info {
|
||||||
@ -301,14 +304,18 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Calibration Actions */
|
/* Calibration Actions */
|
||||||
.calibration-actions {
|
:is(.settings-action-group, .calibration-actions, .reference-actions) {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: var(--space-3);
|
gap: var(--space-3);
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.calibration-actions {
|
||||||
margin-top: var(--space-4);
|
margin-top: var(--space-4);
|
||||||
}
|
}
|
||||||
|
|
||||||
.calibration-name-input {
|
:is(.settings-input, .calibration-name-input, .reference-name-input, .reference-description-input) {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
padding: var(--space-3) var(--space-4);
|
padding: var(--space-3) var(--space-4);
|
||||||
background: var(--bg-primary);
|
background: var(--bg-primary);
|
||||||
@ -316,30 +323,33 @@
|
|||||||
border-radius: var(--radius-lg);
|
border-radius: var(--radius-lg);
|
||||||
color: var(--text-primary);
|
color: var(--text-primary);
|
||||||
font-size: var(--font-size-sm);
|
font-size: var(--font-size-sm);
|
||||||
min-height: 2.5rem;
|
min-height: 2.75rem;
|
||||||
transition: all var(--transition-fast);
|
transition: all var(--transition-fast);
|
||||||
|
box-shadow: var(--shadow-sm);
|
||||||
}
|
}
|
||||||
|
|
||||||
.calibration-name-input:focus {
|
:is(.settings-input, .calibration-name-input, .reference-name-input, .reference-description-input):focus {
|
||||||
outline: none;
|
outline: none;
|
||||||
border-color: var(--color-primary-500);
|
border-color: var(--color-primary-500);
|
||||||
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
|
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.calibration-name-input:disabled {
|
:is(.settings-input, .calibration-name-input, .reference-name-input, .reference-description-input):disabled {
|
||||||
opacity: 0.6;
|
opacity: 0.6;
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
}
|
}
|
||||||
|
|
||||||
.calibration-name-input::placeholder {
|
:is(.settings-input, .calibration-name-input, .reference-name-input, .reference-description-input)::placeholder {
|
||||||
color: var(--text-tertiary);
|
color: var(--text-tertiary);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Existing Calibrations */
|
/* Existing Calibrations */
|
||||||
.existing-calibrations {
|
.existing-calibrations,
|
||||||
|
.existing-references {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: var(--space-3);
|
gap: var(--space-3);
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Status Grid */
|
/* Status Grid */
|
||||||
@ -390,7 +400,8 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
.existing-calibrations {
|
.existing-calibrations,
|
||||||
|
.existing-references {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -503,53 +514,8 @@
|
|||||||
margin-bottom: var(--space-4);
|
margin-bottom: var(--space-4);
|
||||||
}
|
}
|
||||||
|
|
||||||
.reference-name-input,
|
|
||||||
.reference-description-input {
|
|
||||||
padding: var(--space-2) var(--space-3);
|
|
||||||
border: 1px solid var(--color-border);
|
|
||||||
border-radius: var(--radius-md);
|
|
||||||
font-size: var(--text-sm);
|
|
||||||
background: var(--color-bg);
|
|
||||||
color: var(--color-text);
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.reference-name-input:focus,
|
|
||||||
.reference-description-input:focus {
|
|
||||||
outline: none;
|
|
||||||
border-color: var(--color-primary-500);
|
|
||||||
box-shadow: 0 0 0 3px var(--color-primary-100);
|
|
||||||
}
|
|
||||||
|
|
||||||
.reference-name-input::placeholder,
|
|
||||||
.reference-description-input::placeholder {
|
|
||||||
color: var(--color-text-secondary);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Reference Actions */
|
|
||||||
.reference-actions {
|
.reference-actions {
|
||||||
display: flex;
|
|
||||||
gap: var(--space-3);
|
|
||||||
align-items: stretch;
|
|
||||||
margin-top: var(--space-3);
|
margin-top: var(--space-3);
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.reference-dropdown {
|
|
||||||
flex: 1;
|
|
||||||
min-width: 200px;
|
|
||||||
padding: var(--space-2) var(--space-3);
|
|
||||||
border: 1px solid var(--color-border);
|
|
||||||
border-radius: var(--radius-md);
|
|
||||||
font-size: var(--text-sm);
|
|
||||||
background: var(--color-bg);
|
|
||||||
color: var(--color-text);
|
|
||||||
}
|
|
||||||
|
|
||||||
.reference-dropdown:disabled {
|
|
||||||
opacity: 0.6;
|
|
||||||
cursor: not-allowed;
|
|
||||||
background: var(--color-gray-50);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Current Reference Info */
|
/* Current Reference Info */
|
||||||
@ -623,4 +589,4 @@
|
|||||||
.reference-dropdown {
|
.reference-dropdown {
|
||||||
min-width: unset;
|
min-width: unset;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -26,6 +26,7 @@ export class SettingsManager {
|
|||||||
|
|
||||||
this.isInitialized = false;
|
this.isInitialized = false;
|
||||||
this.elements = {};
|
this.elements = {};
|
||||||
|
this.headerElements = {};
|
||||||
this.debouncer = new Debouncer();
|
this.debouncer = new Debouncer();
|
||||||
|
|
||||||
// Sub-managers
|
// Sub-managers
|
||||||
@ -99,6 +100,12 @@ export class SettingsManager {
|
|||||||
calibrationCount: document.getElementById('calibrationCount'),
|
calibrationCount: document.getElementById('calibrationCount'),
|
||||||
systemStatus: document.getElementById('systemStatus')
|
systemStatus: document.getElementById('systemStatus')
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this.headerElements = {
|
||||||
|
preset: document.getElementById('headerPresetSummary'),
|
||||||
|
calibration: document.getElementById('headerCalibrationSummary'),
|
||||||
|
reference: document.getElementById('headerReferenceSummary')
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
initSubManagers() {
|
initSubManagers() {
|
||||||
@ -109,10 +116,9 @@ export class SettingsManager {
|
|||||||
// Setup callbacks
|
// Setup callbacks
|
||||||
this.presetManager.onPresetChanged = async () => {
|
this.presetManager.onPresetChanged = async () => {
|
||||||
await this.loadStatus();
|
await this.loadStatus();
|
||||||
const preset = this.presetManager.getCurrentPreset();
|
|
||||||
this.calibrationManager.setCurrentPreset(preset);
|
|
||||||
this.calibrationManager.reset();
|
this.calibrationManager.reset();
|
||||||
this.referenceManager.setCurrentPreset(preset);
|
await this.calibrationManager.loadWorkingCalibration();
|
||||||
|
this.updateReferenceSummary(this.referenceManager.getCurrentReference());
|
||||||
};
|
};
|
||||||
|
|
||||||
this.calibrationManager.onCalibrationSaved = async () => {
|
this.calibrationManager.onCalibrationSaved = async () => {
|
||||||
@ -122,6 +128,10 @@ export class SettingsManager {
|
|||||||
this.calibrationManager.onCalibrationSet = async () => {
|
this.calibrationManager.onCalibrationSet = async () => {
|
||||||
await this.loadStatus();
|
await this.loadStatus();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this.referenceManager.onReferenceUpdated = (reference) => {
|
||||||
|
this.updateReferenceSummary(reference);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
setupEventHandlers() {
|
setupEventHandlers() {
|
||||||
@ -130,12 +140,9 @@ export class SettingsManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async loadInitialData() {
|
async loadInitialData() {
|
||||||
await Promise.all([
|
await this.presetManager.loadPresets();
|
||||||
this.presetManager.loadPresets(),
|
await this.loadStatus();
|
||||||
this.loadStatus(),
|
await this.calibrationManager.loadWorkingCalibration();
|
||||||
this.calibrationManager.loadWorkingCalibration(),
|
|
||||||
this.referenceManager.loadReferences()
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async loadStatus() {
|
async loadStatus() {
|
||||||
@ -158,6 +165,9 @@ export class SettingsManager {
|
|||||||
this.elements.presetCount.textContent = status.available_presets || 0;
|
this.elements.presetCount.textContent = status.available_presets || 0;
|
||||||
this.elements.calibrationCount.textContent = status.available_calibrations || 0;
|
this.elements.calibrationCount.textContent = status.available_calibrations || 0;
|
||||||
this.elements.systemStatus.textContent = 'Ready';
|
this.elements.systemStatus.textContent = 'Ready';
|
||||||
|
|
||||||
|
this.updateHeaderSummary(status);
|
||||||
|
this.updateReferenceSummary(this.referenceManager.getCurrentReference());
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleViewPlots() {
|
async handleViewPlots() {
|
||||||
@ -475,6 +485,108 @@ export class SettingsManager {
|
|||||||
console.log('Settings Manager destroyed');
|
console.log('Settings Manager destroyed');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
formatFrequency(value) {
|
||||||
|
if (value === null || value === undefined) return null;
|
||||||
|
const numeric = Number(value);
|
||||||
|
if (!Number.isFinite(numeric)) return null;
|
||||||
|
const abs = Math.abs(numeric);
|
||||||
|
const units = [
|
||||||
|
{ divider: 1e9, suffix: 'GHz' },
|
||||||
|
{ divider: 1e6, suffix: 'MHz' },
|
||||||
|
{ divider: 1e3, suffix: 'kHz' }
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const unit of units) {
|
||||||
|
if (abs >= unit.divider) {
|
||||||
|
const scaled = numeric / unit.divider;
|
||||||
|
const formatted = scaled >= 10 ? scaled.toFixed(0) : scaled.toFixed(2);
|
||||||
|
return `${formatted} ${unit.suffix}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${numeric.toFixed(0)} Hz`;
|
||||||
|
}
|
||||||
|
|
||||||
|
formatPresetSummary(preset) {
|
||||||
|
if (!preset) return 'Not selected';
|
||||||
|
|
||||||
|
const parts = [];
|
||||||
|
if (preset.mode) {
|
||||||
|
parts.push(preset.mode.toUpperCase());
|
||||||
|
}
|
||||||
|
|
||||||
|
const start = this.formatFrequency(preset.start_freq);
|
||||||
|
const stop = this.formatFrequency(preset.stop_freq);
|
||||||
|
if (start && stop) {
|
||||||
|
parts.push(`${start} – ${stop}`);
|
||||||
|
} else if (start || stop) {
|
||||||
|
parts.push(start || stop);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (preset.points) {
|
||||||
|
parts.push(`${preset.points} pts`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (preset.bandwidth) {
|
||||||
|
const bw = this.formatFrequency(preset.bandwidth);
|
||||||
|
if (bw) parts.push(`BW ${bw}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return parts.join(' • ') || 'Preset configured';
|
||||||
|
}
|
||||||
|
|
||||||
|
formatCalibrationSummary(status) {
|
||||||
|
const active = status?.current_calibration;
|
||||||
|
if (active?.calibration_name) {
|
||||||
|
return `Active • ${active.calibration_name}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const working = status?.working_calibration;
|
||||||
|
if (working?.progress) {
|
||||||
|
const missingCount = working.missing_standards?.length || 0;
|
||||||
|
const missingText = missingCount ? ` • ${missingCount} missing` : '';
|
||||||
|
return `In progress • ${working.progress}${missingText}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'Not set';
|
||||||
|
}
|
||||||
|
|
||||||
|
updateHeaderSummary(status) {
|
||||||
|
if (!this.headerElements) return;
|
||||||
|
const { preset, calibration } = this.headerElements;
|
||||||
|
if (preset) {
|
||||||
|
preset.textContent = this.formatPresetSummary(status?.current_preset);
|
||||||
|
}
|
||||||
|
if (calibration) {
|
||||||
|
calibration.textContent = this.formatCalibrationSummary(status);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateReferenceSummary(reference) {
|
||||||
|
const target = this.headerElements?.reference;
|
||||||
|
if (!target) return;
|
||||||
|
|
||||||
|
if (!reference) {
|
||||||
|
target.textContent = 'Not captured';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const name = reference.name || 'Reference';
|
||||||
|
let timestampText = '';
|
||||||
|
if (reference.timestamp) {
|
||||||
|
const timestamp = new Date(reference.timestamp);
|
||||||
|
if (!Number.isNaN(timestamp.getTime())) {
|
||||||
|
timestampText = timestamp.toLocaleString(undefined, { dateStyle: 'short', timeStyle: 'short' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const summaryParts = ['Active', name];
|
||||||
|
if (timestampText) {
|
||||||
|
summaryParts.push(timestampText);
|
||||||
|
}
|
||||||
|
target.textContent = summaryParts.join(' • ');
|
||||||
|
}
|
||||||
|
|
||||||
notify(type, title, message) {
|
notify(type, title, message) {
|
||||||
this.notifications?.show?.({ type, title, message });
|
this.notifications?.show?.({ type, title, message });
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,7 +5,9 @@
|
|||||||
|
|
||||||
import { Debouncer, RequestGuard, ButtonState } from '../utils.js';
|
import { Debouncer, RequestGuard, ButtonState } from '../utils.js';
|
||||||
import { apiGet, apiPost } from '../api-client.js';
|
import { apiGet, apiPost } from '../api-client.js';
|
||||||
import { API, TIMING } from '../constants.js';
|
import { API, TIMING, NOTIFICATION_TYPES } from '../constants.js';
|
||||||
|
|
||||||
|
const { SUCCESS, ERROR } = NOTIFICATION_TYPES;
|
||||||
|
|
||||||
export class PresetManager {
|
export class PresetManager {
|
||||||
constructor(notifications) {
|
constructor(notifications) {
|
||||||
@ -36,7 +38,7 @@ export class PresetManager {
|
|||||||
this.populateDropdown(presets);
|
this.populateDropdown(presets);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Presets load failed:', e);
|
console.error('Presets load failed:', e);
|
||||||
this.notify('error', 'Load Error', 'Failed to load configuration presets');
|
this.notify(ERROR, 'Load Error', 'Failed to load configuration presets');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -47,7 +49,9 @@ export class PresetManager {
|
|||||||
if (!presets.length) {
|
if (!presets.length) {
|
||||||
dd.innerHTML = '<option value="">No presets available</option>';
|
dd.innerHTML = '<option value="">No presets available</option>';
|
||||||
dd.disabled = true;
|
dd.disabled = true;
|
||||||
this.elements.setPresetBtn.disabled = true;
|
if (this.elements.setPresetBtn) {
|
||||||
|
this.elements.setPresetBtn.disabled = true;
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -60,7 +64,7 @@ export class PresetManager {
|
|||||||
});
|
});
|
||||||
|
|
||||||
dd.disabled = false;
|
dd.disabled = false;
|
||||||
this.elements.setPresetBtn.disabled = true;
|
this.syncSelectedPreset();
|
||||||
}
|
}
|
||||||
|
|
||||||
formatDisplay(p) {
|
formatDisplay(p) {
|
||||||
@ -75,8 +79,7 @@ export class PresetManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handlePresetChange() {
|
handlePresetChange() {
|
||||||
const v = this.elements.presetDropdown.value;
|
this.updateSetButtonState();
|
||||||
this.elements.setPresetBtn.disabled = !v;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleSetPreset() {
|
async handleSetPreset() {
|
||||||
@ -90,17 +93,19 @@ export class PresetManager {
|
|||||||
|
|
||||||
const result = await apiPost(API.SETTINGS.PRESET_SET, { filename });
|
const result = await apiPost(API.SETTINGS.PRESET_SET, { filename });
|
||||||
|
|
||||||
this.notify('success', 'Preset Set', result.message);
|
this.notify(SUCCESS, 'Preset Set', result.message);
|
||||||
this.currentPreset = { filename };
|
this.currentPreset = { filename };
|
||||||
|
|
||||||
if (this.onPresetChanged) {
|
if (this.onPresetChanged) {
|
||||||
await this.onPresetChanged();
|
await this.onPresetChanged();
|
||||||
}
|
}
|
||||||
|
this.syncSelectedPreset();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Set preset failed:', e);
|
console.error('Set preset failed:', e);
|
||||||
this.notify('error', 'Preset Error', 'Failed to set configuration preset');
|
this.notify(ERROR, 'Preset Error', 'Failed to set configuration preset');
|
||||||
} finally {
|
} finally {
|
||||||
ButtonState.set(this.elements.setPresetBtn, { state: 'normal', icon: 'check', text: 'Set Active' });
|
ButtonState.set(this.elements.setPresetBtn, { state: 'normal', icon: 'check', text: 'Set Active' });
|
||||||
|
this.updateSetButtonState();
|
||||||
}
|
}
|
||||||
}), TIMING.DEBOUNCE_PRESET
|
}), TIMING.DEBOUNCE_PRESET
|
||||||
);
|
);
|
||||||
@ -114,13 +119,40 @@ export class PresetManager {
|
|||||||
this.currentPreset = null;
|
this.currentPreset = null;
|
||||||
this.elements.currentPreset.textContent = 'None';
|
this.elements.currentPreset.textContent = 'None';
|
||||||
}
|
}
|
||||||
|
this.syncSelectedPreset();
|
||||||
}
|
}
|
||||||
|
|
||||||
getCurrentPreset() {
|
getCurrentPreset() {
|
||||||
return this.currentPreset;
|
return this.currentPreset;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
syncSelectedPreset() {
|
||||||
|
const dd = this.elements.presetDropdown;
|
||||||
|
if (!dd) return;
|
||||||
|
|
||||||
|
const active = this.currentPreset?.filename || '';
|
||||||
|
if (active) {
|
||||||
|
const hasOption = Array.from(dd.options).some(opt => opt.value === active);
|
||||||
|
dd.value = hasOption ? active : '';
|
||||||
|
} else {
|
||||||
|
dd.value = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
this.updateSetButtonState();
|
||||||
|
}
|
||||||
|
|
||||||
|
updateSetButtonState() {
|
||||||
|
const dd = this.elements.presetDropdown;
|
||||||
|
const btn = this.elements.setPresetBtn;
|
||||||
|
if (!dd || !btn) return;
|
||||||
|
|
||||||
|
const selected = dd.value;
|
||||||
|
const hasSelection = selected !== '';
|
||||||
|
const isActive = this.currentPreset?.filename === selected;
|
||||||
|
btn.disabled = !hasSelection || isActive;
|
||||||
|
}
|
||||||
|
|
||||||
notify(type, title, message) {
|
notify(type, title, message) {
|
||||||
this.notifications?.show?.({ type, title, message });
|
this.notifications?.show?.({ type, title, message });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -18,6 +18,7 @@ export class ReferenceManager {
|
|||||||
this.elements = {};
|
this.elements = {};
|
||||||
this.debouncer = new Debouncer();
|
this.debouncer = new Debouncer();
|
||||||
this.reqGuard = new RequestGuard();
|
this.reqGuard = new RequestGuard();
|
||||||
|
this.onReferenceUpdated = null;
|
||||||
|
|
||||||
this.handleCreateReference = this.handleCreateReference.bind(this);
|
this.handleCreateReference = this.handleCreateReference.bind(this);
|
||||||
this.handleReferenceChange = this.handleReferenceChange.bind(this);
|
this.handleReferenceChange = this.handleReferenceChange.bind(this);
|
||||||
@ -79,6 +80,10 @@ export class ReferenceManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getCurrentReference() {
|
||||||
|
return this.currentReference;
|
||||||
|
}
|
||||||
|
|
||||||
renderDropdown(references) {
|
renderDropdown(references) {
|
||||||
if (!this.elements.referenceDropdown) return;
|
if (!this.elements.referenceDropdown) return;
|
||||||
|
|
||||||
@ -120,10 +125,16 @@ export class ReferenceManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
updateInfo(reference) {
|
updateInfo(reference) {
|
||||||
if (!this.elements.currentReferenceInfo) return;
|
this.currentReference = reference;
|
||||||
|
|
||||||
|
if (!this.elements.currentReferenceInfo) {
|
||||||
|
this.onReferenceUpdated?.(reference || null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!reference) {
|
if (!reference) {
|
||||||
this.elements.currentReferenceInfo.style.display = 'none';
|
this.elements.currentReferenceInfo.style.display = 'none';
|
||||||
|
this.onReferenceUpdated?.(null);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -134,12 +145,17 @@ export class ReferenceManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this.elements.currentReferenceTimestamp) {
|
if (this.elements.currentReferenceTimestamp) {
|
||||||
this.elements.currentReferenceTimestamp.textContent = new Date(reference.timestamp).toLocaleString();
|
const timestamp = new Date(reference.timestamp);
|
||||||
|
this.elements.currentReferenceTimestamp.textContent = Number.isNaN(timestamp.getTime())
|
||||||
|
? '-'
|
||||||
|
: timestamp.toLocaleString();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.elements.currentReferenceDescription) {
|
if (this.elements.currentReferenceDescription) {
|
||||||
this.elements.currentReferenceDescription.textContent = reference.description || '';
|
this.elements.currentReferenceDescription.textContent = reference.description || '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.onReferenceUpdated?.(reference);
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleCreateReference() {
|
async handleCreateReference() {
|
||||||
|
|||||||
@ -41,7 +41,22 @@
|
|||||||
<div class="header__brand">
|
<div class="header__brand">
|
||||||
<i data-lucide="activity" class="header__icon"></i>
|
<i data-lucide="activity" class="header__icon"></i>
|
||||||
<h1 class="header__title">VNA System</h1>
|
<h1 class="header__title">VNA System</h1>
|
||||||
<span class="header__subtitle">Real-time Analysis</span>
|
<div class="header__summary" id="headerSummary">
|
||||||
|
<div class="header-summary__item">
|
||||||
|
<span class="header-summary__label">Preset</span>
|
||||||
|
<span class="header-summary__value" id="headerPresetSummary">Not selected</span>
|
||||||
|
</div>
|
||||||
|
<span class="header-summary__divider" aria-hidden="true"></span>
|
||||||
|
<div class="header-summary__item">
|
||||||
|
<span class="header-summary__label">Calibration</span>
|
||||||
|
<span class="header-summary__value" id="headerCalibrationSummary">Not set</span>
|
||||||
|
</div>
|
||||||
|
<span class="header-summary__divider" aria-hidden="true"></span>
|
||||||
|
<div class="header-summary__item">
|
||||||
|
<span class="header-summary__label">Reference</span>
|
||||||
|
<span class="header-summary__value" id="headerReferenceSummary">Not captured</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="header__status">
|
<div class="header__status">
|
||||||
@ -149,7 +164,7 @@
|
|||||||
<div class="control-group">
|
<div class="control-group">
|
||||||
<label class="control-label">Available Presets:</label>
|
<label class="control-label">Available Presets:</label>
|
||||||
<div class="preset-selector" id="presetSelector">
|
<div class="preset-selector" id="presetSelector">
|
||||||
<select class="preset-dropdown" id="presetDropdown" disabled>
|
<select class="preset-dropdown settings-select" id="presetDropdown" disabled>
|
||||||
<option value="">Loading presets...</option>
|
<option value="">Loading presets...</option>
|
||||||
</select>
|
</select>
|
||||||
<button class="btn btn--primary" id="setPresetBtn" disabled>
|
<button class="btn btn--primary" id="setPresetBtn" disabled>
|
||||||
@ -209,12 +224,12 @@
|
|||||||
<!-- Standards buttons will be dynamically generated -->
|
<!-- Standards buttons will be dynamically generated -->
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="calibration-actions">
|
<div class="calibration-actions settings-action-group">
|
||||||
<button class="btn btn--secondary" id="viewCurrentPlotsBtn" disabled>
|
<button class="btn btn--secondary" id="viewCurrentPlotsBtn" disabled>
|
||||||
<i data-lucide="bar-chart-3"></i>
|
<i data-lucide="bar-chart-3"></i>
|
||||||
View Current Plots
|
View Current Plots
|
||||||
</button>
|
</button>
|
||||||
<input type="text" class="calibration-name-input" id="calibrationNameInput" placeholder="Enter calibration name" disabled>
|
<input type="text" class="calibration-name-input settings-input" id="calibrationNameInput" placeholder="Enter calibration name" disabled>
|
||||||
<button class="btn btn--success" id="saveCalibrationBtn" disabled>
|
<button class="btn btn--success" id="saveCalibrationBtn" disabled>
|
||||||
<i data-lucide="save"></i>
|
<i data-lucide="save"></i>
|
||||||
Save Calibration
|
Save Calibration
|
||||||
@ -226,10 +241,10 @@
|
|||||||
<div class="workflow-section">
|
<div class="workflow-section">
|
||||||
<h4 class="workflow-title">Existing Calibrations</h4>
|
<h4 class="workflow-title">Existing Calibrations</h4>
|
||||||
<div class="existing-calibrations" id="existingCalibrations">
|
<div class="existing-calibrations" id="existingCalibrations">
|
||||||
<select class="calibration-dropdown" id="calibrationDropdown" disabled>
|
<select class="calibration-dropdown settings-select" id="calibrationDropdown" disabled>
|
||||||
<option value="">No calibrations available</option>
|
<option value="">No calibrations available</option>
|
||||||
</select>
|
</select>
|
||||||
<div class="calibration-actions">
|
<div class="calibration-actions settings-action-group">
|
||||||
<button class="btn btn--primary" id="setCalibrationBtn" disabled>
|
<button class="btn btn--primary" id="setCalibrationBtn" disabled>
|
||||||
<i data-lucide="check"></i>
|
<i data-lucide="check"></i>
|
||||||
Set Active
|
Set Active
|
||||||
@ -253,8 +268,8 @@
|
|||||||
<div class="workflow-section">
|
<div class="workflow-section">
|
||||||
<h4 class="workflow-title">Create Reference</h4>
|
<h4 class="workflow-title">Create Reference</h4>
|
||||||
<div class="reference-creation">
|
<div class="reference-creation">
|
||||||
<input type="text" class="reference-name-input" id="referenceNameInput" placeholder="Enter reference name">
|
<input type="text" class="reference-name-input settings-input" id="referenceNameInput" placeholder="Enter reference name">
|
||||||
<input type="text" class="reference-description-input" id="referenceDescriptionInput" placeholder="Description (optional)">
|
<input type="text" class="reference-description-input settings-input" id="referenceDescriptionInput" placeholder="Description (optional)">
|
||||||
<button class="btn btn--primary" id="createReferenceBtn">
|
<button class="btn btn--primary" id="createReferenceBtn">
|
||||||
<i data-lucide="target"></i>
|
<i data-lucide="target"></i>
|
||||||
Capture Reference
|
Capture Reference
|
||||||
@ -266,10 +281,10 @@
|
|||||||
<div class="workflow-section">
|
<div class="workflow-section">
|
||||||
<h4 class="workflow-title">Existing References</h4>
|
<h4 class="workflow-title">Existing References</h4>
|
||||||
<div class="existing-references" id="existingReferences">
|
<div class="existing-references" id="existingReferences">
|
||||||
<select class="reference-dropdown" id="referenceDropdown" disabled>
|
<select class="reference-dropdown settings-select" id="referenceDropdown" disabled>
|
||||||
<option value="">No references available</option>
|
<option value="">No references available</option>
|
||||||
</select>
|
</select>
|
||||||
<div class="reference-actions">
|
<div class="reference-actions settings-action-group">
|
||||||
<button class="btn btn--primary" id="setReferenceBtn" disabled>
|
<button class="btn btn--primary" id="setReferenceBtn" disabled>
|
||||||
<i data-lucide="check"></i>
|
<i data-lucide="check"></i>
|
||||||
Set Active
|
Set Active
|
||||||
@ -361,4 +376,4 @@
|
|||||||
<script src="https://unpkg.com/lucide@latest/dist/umd/lucide.js"></script>
|
<script src="https://unpkg.com/lucide@latest/dist/umd/lucide.js"></script>
|
||||||
<script type="module" src="/static/js/main.js"></script>
|
<script type="module" src="/static/js/main.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
Reference in New Issue
Block a user