Refresh selected BlueZ path before K1 GATT operations
This commit is contained in:
@@ -17,6 +17,7 @@ from uuid import UUID
|
||||
from bleak import BleakScanner
|
||||
from bleak.backends.device import BLEDevice
|
||||
from bleak.backends.scanner import AdvertisementData
|
||||
from bleak.exc import BleakDeviceNotFoundError
|
||||
|
||||
from k1link.artifacts import utc_now_iso
|
||||
from k1link.device_plugins.xgrids_k1.ble.runtime_arbiter import (
|
||||
@@ -838,16 +839,60 @@ async def _retrieve_bluez_device(address: str, details: object = None) -> BLEDev
|
||||
return None
|
||||
|
||||
|
||||
async def status_read_device_is_current(device: BLEDevice) -> bool:
|
||||
"""Check a BlueZ handle before the one explicit status-read connection.
|
||||
async def ensure_device_for_gatt(device: BLEDevice, *, timeout_seconds: float) -> None:
|
||||
"""Restore a vanished BlueZ path before the one admitted GATT connection.
|
||||
|
||||
BlueZ may remove an unpaired object after a completed scan session. This
|
||||
cache check neither scans nor connects and never changes macOS selection.
|
||||
A BLEDevice stores a D-Bus path, not a native object lease. After the form
|
||||
has been filled in, BlueZ may have removed that path. Observe the same
|
||||
address on the same adapter once, then require that exact path to exist.
|
||||
Keep the original selection/capture; no session pin, public scan generation,
|
||||
GATT connection or write is created here. CoreBluetooth needs no refresh.
|
||||
"""
|
||||
details = getattr(device, "details", None)
|
||||
if not sys.platform.startswith("linux") or not isinstance(details, dict):
|
||||
return True
|
||||
return await _retrieve_bluez_device(device.address, details) is not None
|
||||
return
|
||||
path = details.get("path", "")
|
||||
address = device.address
|
||||
match = re.fullmatch(r"/org/bluez/(hci[0-9]+)/dev_[0-9A-F_]+", path)
|
||||
if (match is None
|
||||
or not re.fullmatch(r"(?:[0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2}", address)
|
||||
or path.rsplit("/", 1)[-1] != "dev_" + address.upper().replace(":", "_")
|
||||
or not math.isfinite(timeout_seconds) or timeout_seconds <= 0):
|
||||
raise BleakDeviceNotFoundError(address, "Invalid selected BlueZ transport")
|
||||
|
||||
owner_epoch = ble_runtime_owner_epoch_for_current_loop()
|
||||
operation_kind = ble_runtime_snapshot()["active_operation_kind"]
|
||||
|
||||
def require_owner() -> None:
|
||||
runtime = ble_runtime_snapshot()
|
||||
if (owner_epoch is None
|
||||
or ble_runtime_owner_epoch_for_current_loop() != owner_epoch
|
||||
or runtime["owner_epoch"] != owner_epoch
|
||||
or not runtime["owner_loop_bound"] or runtime["poisoned"]
|
||||
or operation_kind not in {"status-read", "wifi-provision"}
|
||||
or runtime["active_operation_kind"] != operation_kind):
|
||||
raise BleakDeviceNotFoundError(address, "BLE operation owner changed")
|
||||
|
||||
require_owner()
|
||||
current = await _retrieve_bluez_device(address, details)
|
||||
require_owner()
|
||||
if current is not None:
|
||||
return
|
||||
|
||||
candidate = await BleakScanner.find_device_by_address(
|
||||
address,
|
||||
timeout=min(timeout_seconds, 8.0),
|
||||
bluez={"adapter": match[1]},
|
||||
)
|
||||
require_owner()
|
||||
if (candidate is None or candidate.address.casefold() != address.casefold()
|
||||
or not isinstance(candidate.details, dict)
|
||||
or candidate.details.get("path") != path):
|
||||
raise BleakDeviceNotFoundError(address, "Selected BlueZ transport unavailable")
|
||||
current = await _retrieve_bluez_device(address, details)
|
||||
require_owner()
|
||||
if current is None:
|
||||
raise BleakDeviceNotFoundError(address, "Selected BlueZ transport disappeared")
|
||||
|
||||
|
||||
async def _retrieve_corebluetooth_device(
|
||||
|
||||
@@ -23,10 +23,10 @@ from k1link.device_plugins.xgrids_k1.ble.scanner import (
|
||||
demote_connected_device_handle_after_gatt_failure,
|
||||
discover_known_device_capture_for_status_read,
|
||||
discovered_device_selection,
|
||||
ensure_device_for_gatt,
|
||||
mark_captured_device_gatt_validated,
|
||||
retrieve_connected_device_capture,
|
||||
retrieve_known_device_capture_for_status_read,
|
||||
status_read_device_is_current,
|
||||
)
|
||||
|
||||
PROFILE_ID = "xgrids-k1-fw3-wifi-v1"
|
||||
@@ -391,18 +391,10 @@ async def _read_wifi_status_impl(
|
||||
"Exact BLE device is unavailable; run an explicit recovery or scan.",
|
||||
)
|
||||
|
||||
if not await status_read_device_is_current(device):
|
||||
# One explicit read may refresh the exact vanished BlueZ object
|
||||
# before GATT. No failed connect/write is retried; public discovery
|
||||
# generations, the pinned target and macOS behavior are unchanged.
|
||||
progress.operation_stage = "exact-uuid-scan"
|
||||
active_captured_device = await discover_known_device_capture_for_status_read(
|
||||
device_macos_uuid, timeout_seconds=min(timeout_seconds, 8.0),
|
||||
)
|
||||
device = (captured_device_handle(active_captured_device)
|
||||
if active_captured_device is not None else None)
|
||||
if device is None:
|
||||
raise BleakDeviceNotFoundError(device_macos_uuid, "Exact BLE device unavailable")
|
||||
await ensure_device_for_gatt(device, timeout_seconds=timeout_seconds)
|
||||
if (active_captured_device is not None
|
||||
and captured_device_handle(active_captured_device) is not device):
|
||||
raise BleakDeviceNotFoundError(device_macos_uuid, "BLE selection invalidated")
|
||||
progress.operation_stage = "connect"
|
||||
async with BleakClient(device, timeout=timeout_seconds, pair=False) as client:
|
||||
progress.operation_stage = "gatt-contract"
|
||||
@@ -627,6 +619,10 @@ async def _provision_wifi_impl(
|
||||
"Device was not rediscovered; keep the K1 powered and nearby.",
|
||||
)
|
||||
|
||||
await ensure_device_for_gatt(device, timeout_seconds=timeout_seconds)
|
||||
if (active_captured_device is not None
|
||||
and captured_device_handle(active_captured_device) is not device):
|
||||
raise BleakDeviceNotFoundError(device_macos_uuid, "BLE selection invalidated")
|
||||
operation_stage = "connect"
|
||||
progress.operation_stage = operation_stage
|
||||
async with BleakClient(device, timeout=timeout_seconds, pair=False) as client:
|
||||
|
||||
Reference in New Issue
Block a user