diff --git a/python_app/gui/controllers/app_window_preprocess_mixin.py b/python_app/gui/controllers/app_window_preprocess_mixin.py index 737347f..765511f 100644 --- a/python_app/gui/controllers/app_window_preprocess_mixin.py +++ b/python_app/gui/controllers/app_window_preprocess_mixin.py @@ -14,9 +14,13 @@ from python_app.workflows.multi_radar_capture_workflow import ( MultiRadarSequentialCaptureSession, ) from python_app.workflows.radar_config_variants import scan_radar_config_variants +from python_app.workflows.reference_workflow import capture_reference_set from python_app.workflows.sequential_capture_workflow import SequentialCaptureSession +TMP_REFERENCE_SET_NAME = "tmp_reference" + + class AppWindowPreprocessMixin: """Handles preprocess set management and sequential capture workflows.""" @@ -103,6 +107,63 @@ class AppWindowPreprocessMixin: dialog.raise_() dialog.activateWindow() + def _capture_tmp_reference(self) -> None: + """Capture, save, and select a temporary S21 reference with current sweep settings.""" + if self._capture_session is not None: + self._show_error( + "Cannot capture tmp reference during active capture sequence", + details=self._capture_state_details(), + ) + return + + pipeline_was_running = self._supervisor.is_running() + pipeline_was_paused = False + + try: + if pipeline_was_running: + self._log("Pipeline paused for tmp reference capture") + self._stop_run() + pipeline_was_paused = True + + config = self._build_config() + radar_key = self._radar_key(config) + self._log( + "Tmp S21 Reference capture started: " + f"set={TMP_REFERENCE_SET_NAME}, radar_key={radar_key}" + ) + + radar_key, collection = capture_reference_set(config, TMP_REFERENCE_SET_NAME, self._store) + self._selected_preprocess_sets["s21_reference"] = TMP_REFERENCE_SET_NAME + self._selected_preprocess_radar_key = radar_key + self._processor_run_signature = None + self._history_run_signature = None + if self._supervisor.is_processor_running(): + self._stop_all_processes() + self._reset_runtime_history() + self._refresh_preprocess_summary_labels() + if collection.traces: + self._draw_single_trace( + collection.traces[-1], + title="Tmp S21 Reference last trace", + channel="s21", + ) + if self._preprocess_dialog is not None: + self._refresh_sets() + self._preprocess_dialog.set_status( + f"S21 Reference set saved: {TMP_REFERENCE_SET_NAME} ({len(collection.traces)} traces)" + ) + + self._log( + "Tmp S21 Reference captured and selected: " + f"set={TMP_REFERENCE_SET_NAME}, key={radar_key}, traces={len(collection.traces)}; " + "runtime history reset" + ) + except Exception as exc: # noqa: BLE001 + self._show_exception("Failed to capture tmp reference", exc) + finally: + if pipeline_was_paused: + self._start_run() + def _ensure_preprocess_dialog(self) -> PreprocessDialog: """Create preprocessing dialog lazily and wire its signals once.""" if self._preprocess_dialog is not None: diff --git a/python_app/gui/controllers/sections/data_actions_section.py b/python_app/gui/controllers/sections/data_actions_section.py index 5dd09d5..c48c597 100644 --- a/python_app/gui/controllers/sections/data_actions_section.py +++ b/python_app/gui/controllers/sections/data_actions_section.py @@ -31,6 +31,9 @@ def build_data_actions_group(owner) -> QGroupBox: remove_last_button = QPushButton("Remove Last Measurement") remove_last_button.clicked.connect(owner._remove_last_runtime_history) remove_last_button.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed) + capture_tmp_reference_button = QPushButton("Capture Tmp Reference") + capture_tmp_reference_button.clicked.connect(owner._capture_tmp_reference) + capture_tmp_reference_button.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed) owner._save_count = QSpinBox() owner._save_count.setMinimum(1) owner._save_count.setMaximum(10_000) @@ -41,7 +44,8 @@ def build_data_actions_group(owner) -> QGroupBox: button_grid.setVerticalSpacing(8) button_grid.addWidget(save_button, 0, 0) button_grid.addWidget(save_vna_json_button, 0, 1) - button_grid.addWidget(remove_last_button, 1, 0, 1, 2) + button_grid.addWidget(remove_last_button, 1, 0) + button_grid.addWidget(capture_tmp_reference_button, 1, 1) button_grid.setColumnStretch(0, 1) button_grid.setColumnStretch(1, 1) layout.addLayout(button_grid) diff --git a/python_app/workflows/calibration_workflow.py b/python_app/workflows/calibration_workflow.py index f523beb..b8c9c90 100644 --- a/python_app/workflows/calibration_workflow.py +++ b/python_app/workflows/calibration_workflow.py @@ -2,13 +2,10 @@ from __future__ import annotations -import time - -from python_app.hardware_full.single_radar_service import create_single_radar_service -from python_app.hardware_full.switch_service import SwitchService -from python_app.models.dataset_model import ComboKey, SweepCollection, TraceData +from python_app.models.dataset_model import SweepCollection from python_app.models.run_config_model import RunConfigModel -from python_app.storage.npz_store import NpzStore, radar_key_from_config +from python_app.storage.npz_store import NpzStore +from python_app.workflows.sequential_capture_workflow import SequentialCaptureSession def capture_calibration_set( @@ -24,66 +21,11 @@ def capture_calibration_set( "and captured explicitly." ) - combos = RunConfigModel.build_full_combos(config.input_switch.positions, config.output_switch.positions) - - radar = create_single_radar_service(config) - input_switch = SwitchService( - name=config.input_switch.name, - positions=config.input_switch.positions, - mode=config.input_switch.driver_mode, - driver=config.input_switch.driver, - gpio_chip=config.input_switch.gpio_chip, - pin_a=config.input_switch.pin_a, - pin_b=config.input_switch.pin_b, - invert_logic=config.input_switch.invert_logic, - ) - output_switch = SwitchService( - name=config.output_switch.name, - positions=config.output_switch.positions, - mode=config.output_switch.driver_mode, - driver=config.output_switch.driver, - gpio_chip=config.output_switch.gpio_chip, - pin_a=config.output_switch.pin_a, - pin_b=config.output_switch.pin_b, - invert_logic=config.output_switch.invert_logic, - ) - - traces: list[TraceData] = [] + session = SequentialCaptureSession(config=config, kind="s21_calibration", set_name=set_name) try: - radar.open() - radar.configure(config.radar.sweep) - input_switch.open() - output_switch.open() - - for combo in combos: - output_switch.switch_to(combo.output) - input_switch.switch_to(combo.input) - if config.runtime.settling_ms > 0: - time.sleep(config.runtime.settling_ms / 1000.0) - - sweep = radar.acquire() - traces.append( - TraceData( - combo=ComboKey(input_pos=combo.input, output_pos=combo.output), - frequency_hz=sweep.x, - s11=sweep.trace("s11"), - s21=sweep.trace("s21"), - ) - ) + session.open() + while not session.is_complete(): + session.capture_current_combo() + return session.finalize(store) finally: - output_switch.close() - input_switch.close() - radar.close() - - collection = SweepCollection(collection_id=1, monotonic_ns=time.monotonic_ns(), traces=traces) - radar_key = radar_key_from_config( - model_name=config.radar.model, - serial=config.radar.serial, - sweep_start_hz=config.radar.sweep.start_hz, - sweep_stop_hz=config.radar.sweep.stop_hz, - sweep_points=config.radar.sweep.points, - ifbw_hz=config.radar.sweep.if_bandwidth_hz, - power_dbm=config.radar.sweep.power_dbm, - ) - store.save_set("s21_calibration", radar_key, set_name, collection) - return radar_key, collection + session.close() diff --git a/python_app/workflows/reference_workflow.py b/python_app/workflows/reference_workflow.py index 24a002f..53c8b2c 100644 --- a/python_app/workflows/reference_workflow.py +++ b/python_app/workflows/reference_workflow.py @@ -2,14 +2,10 @@ from __future__ import annotations -import time - -from python_app.hardware_full.multi_device_service import MultiDeviceLibreVnaService -from python_app.hardware_full.single_radar_service import create_single_radar_service -from python_app.hardware_full.switch_service import SwitchService -from python_app.models.dataset_model import ComboKey, SweepCollection, TraceData +from python_app.models.dataset_model import SweepCollection from python_app.models.run_config_model import RunConfigModel -from python_app.storage.npz_store import NpzStore, radar_key_from_config +from python_app.storage.npz_store import NpzStore +from python_app.workflows.sequential_capture_workflow import SequentialCaptureSession def capture_reference_set( @@ -18,94 +14,11 @@ def capture_reference_set( store: NpzStore, ) -> tuple[str, SweepCollection]: """Capture all switch combinations and persist them as reference set.""" - if config.is_multi_device: - radar = MultiDeviceLibreVnaService( - master_serial=config.radar.serial, - slave_serials=list(config.radar.multi_device.slave_serials), - force_external_reference=config.radar.multi_device.force_external_reference, - recovery_attempts=config.radar.multi_device.recovery_attempts, - backend_mode=config.radar.driver_mode, - ) - try: - radar.open() - radar.configure(config.radar.sweep) - collection = radar.acquire_collection(collection_id=1) - finally: - radar.close() - - radar_key = radar_key_from_config( - model_name=config.radar.model, - serial=config.radar.serial, - sweep_start_hz=config.radar.sweep.start_hz, - sweep_stop_hz=config.radar.sweep.stop_hz, - sweep_points=config.radar.sweep.points, - ifbw_hz=config.radar.sweep.if_bandwidth_hz, - power_dbm=config.radar.sweep.power_dbm, - extra_serials=config.radar.multi_device.slave_serials, - ) - store.save_set("s21_reference", radar_key, set_name, collection) - return radar_key, collection - - combos = RunConfigModel.build_full_combos(config.input_switch.positions, config.output_switch.positions) - - radar = create_single_radar_service(config) - input_switch = SwitchService( - name=config.input_switch.name, - positions=config.input_switch.positions, - mode=config.input_switch.driver_mode, - driver=config.input_switch.driver, - gpio_chip=config.input_switch.gpio_chip, - pin_a=config.input_switch.pin_a, - pin_b=config.input_switch.pin_b, - invert_logic=config.input_switch.invert_logic, - ) - output_switch = SwitchService( - name=config.output_switch.name, - positions=config.output_switch.positions, - mode=config.output_switch.driver_mode, - driver=config.output_switch.driver, - gpio_chip=config.output_switch.gpio_chip, - pin_a=config.output_switch.pin_a, - pin_b=config.output_switch.pin_b, - invert_logic=config.output_switch.invert_logic, - ) - - traces: list[TraceData] = [] + session = SequentialCaptureSession(config=config, kind="s21_reference", set_name=set_name) try: - radar.open() - radar.configure(config.radar.sweep) - input_switch.open() - output_switch.open() - - for combo in combos: - output_switch.switch_to(combo.output) - input_switch.switch_to(combo.input) - if config.runtime.settling_ms > 0: - time.sleep(config.runtime.settling_ms / 1000.0) - - sweep = radar.acquire() - traces.append( - TraceData( - combo=ComboKey(input_pos=combo.input, output_pos=combo.output), - frequency_hz=sweep.x, - s11=sweep.trace("s11"), - s21=sweep.trace("s21"), - ) - ) + session.open() + while not session.is_complete(): + session.capture_current_combo() + return session.finalize(store) finally: - output_switch.close() - input_switch.close() - radar.close() - - collection = SweepCollection(collection_id=1, monotonic_ns=time.monotonic_ns(), traces=traces) - radar_key = radar_key_from_config( - model_name=config.radar.model, - serial=config.radar.serial, - sweep_start_hz=config.radar.sweep.start_hz, - sweep_stop_hz=config.radar.sweep.stop_hz, - sweep_points=config.radar.sweep.points, - ifbw_hz=config.radar.sweep.if_bandwidth_hz, - power_dbm=config.radar.sweep.power_dbm, - ) - store.save_set("s21_reference", radar_key, set_name, collection) - return radar_key, collection + session.close() diff --git a/tmp b/tmp new file mode 100644 index 0000000..8a3798a --- /dev/null +++ b/tmp @@ -0,0 +1,3 @@ +смотри сейчас будем добавлять в проект поддержку еще одного девайса в качестве радара. Когда будешь читать код смотри если есть файлы длинее чем 1300 строк то надо бы будет их грамотно разбить. В целом пиши код как мастер профессионал лучший в мире и самый опытный разработчик, пиши красивейший код, максимально читаемый, грамотный и понятный. Очень внимательнно смотри чтобы не было фолбеков, если в коде уже сейчас видишь какие то фолбеки то скажи где они и что делают, скорее всего будем удалять их в дальнейшем. И когда писать сейчас будешь то не создавай лишнего кода типа фолбеков изза отсутвтивия зависимостей и так далее, лишний код это плохо. объем в идеале уменьшать надо проекта. + +Давай постепенно будем добавлять поддержку нового типа радара в код, для начала - сбор данных свипов. По пути /home/europa/Documents/kamil_adc \ No newline at end of file