some fixes again

This commit is contained in:
Ayzen
2026-06-05 17:50:59 +03:00
parent bbea744459
commit 3c30a12d4a
24 changed files with 209 additions and 157 deletions
@@ -361,7 +361,16 @@ class LaserController:
raw = self._protocol.receive_raw(2)
if raw and len(raw) == 2:
state = Protocol.decode_state(raw)
logger.debug("STATE response after command: 0x%04x", state)
if state != 0:
# Surface a device-reported non-OK STATE instead of silently treating
# a board-rejected command as success. (Returned to the caller too.)
logger.warning(
"Device returned non-OK STATE 0x%04x after command: %s",
state,
Protocol.state_to_description(f"{state:04x}"),
)
else:
logger.debug("STATE response after command: 0x%04x", state)
return state
return 0
@@ -173,12 +173,21 @@ class USBTransport:
self._rx_thread = None
if self._handle is not None:
self._handle.releaseInterface(self.INTERFACE)
self._handle.close()
# Always release AND close the handle, even if releaseInterface raises on
# a vanished/changed device. Otherwise the handle leaks with the interface
# still claimed, and every subsequent connect() fails forever with
# "Failed to claim USB interface" (LIBUSB_ERROR_BUSY) — the device never
# recovers after a hot-unplug/replug. close() releases the claim at the OS
# level regardless, so reconnect can claim it again.
with suppress(usb1.USBError):
self._handle.releaseInterface(self.INTERFACE)
with suppress(usb1.USBError):
self._handle.close()
self._handle = None
if self._ctx is not None:
self._ctx.close()
with suppress(usb1.USBError):
self._ctx.close()
self._ctx = None
logger.info("USB disconnected (serial=%s)", self.connected_serial)
+11 -1
View File
@@ -3,11 +3,14 @@
from __future__ import annotations
from dataclasses import dataclass, field
import logging
from python_app.hardware_full.librevna_backends import LibreVnaBackend, MockLibreVnaBackend, NativeLibreVnaBackend
from python_app.hardware_full.librevna_driver.models import SweepResult
from python_app.models.run_config_model import RadarSweepModel
logger = logging.getLogger(__name__)
@dataclass(slots=True)
class LibreVnaService:
@@ -41,9 +44,16 @@ class LibreVnaService:
strict_protocol_version=self.strict_protocol_version,
)
self._driver_available = True
except Exception:
except Exception as exc:
# 'native' demands real hardware — never substitute synthetic data.
if mode == "native":
raise
# 'auto' falls back to the mock backend ONLY when the native driver
# library itself is unavailable (a dev host without the USB stack) —
# this is not device-absence (that surfaces later from open()). Log it
# loudly so it is never a silent surprise; a deployed appliance should
# set driver_mode='native' to forbid the fallback entirely.
logger.warning("LibreVNA native backend unavailable; falling back to mock (mode=auto): %s", exc)
self._backend = MockLibreVnaBackend()
self._using_mock_backend = True
@@ -40,7 +40,9 @@ def create_single_radar_service(config: RunConfigModel) -> SingleRadarService:
model = config.radar.model or RunConfigModel.LIBREVNA_MODEL
if model == RunConfigModel.LIBREVNA_MODEL:
return LibreVnaService(serial=config.radar.serial or None)
# Forward driver_mode (mirrors the matrix path): 'native' must require real
# hardware and 'mock' must use the synthetic backend — never silently the wrong one.
return LibreVnaService(serial=config.radar.serial or None, backend_mode=config.radar.driver_mode)
if model == RunConfigModel.COMPACT_M_K209_MODEL:
if config.radar.driver_mode != "native":