as
This commit is contained in:
@@ -1,16 +1,16 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Advertising mirror: unregister → register with debounce."""
|
||||
"""Advertising mirror for bless 0.3.x (BlueZ): stop → register with ManufacturerData."""
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import struct
|
||||
import time
|
||||
from typing import Any, Callable, List, Optional
|
||||
from typing import Any, List, Optional
|
||||
|
||||
from config import (
|
||||
ADV_DEBOUNCE_S,
|
||||
ADV_VERSION,
|
||||
COMPANY_ID,
|
||||
LOCAL_NAME,
|
||||
N,
|
||||
SERVICE_UUID_16,
|
||||
)
|
||||
@@ -19,9 +19,8 @@ log = logging.getLogger("collector.adv")
|
||||
|
||||
|
||||
def build_manufacturer_data(bitmap: int, reports: List[bytes]) -> bytes:
|
||||
"""ver=1 + for N=4: bitmap + 4×4 B reports (fits in 31-byte AD with UUID)."""
|
||||
"""ver=1 + bitmap + N×4 B reports."""
|
||||
if N == 1 and reports:
|
||||
# ver + single report (legacy compact form from spec sketch)
|
||||
return bytes((ADV_VERSION,)) + reports[0][:4]
|
||||
body = bytearray()
|
||||
body.append(ADV_VERSION)
|
||||
@@ -33,29 +32,18 @@ def build_manufacturer_data(bitmap: int, reports: List[bytes]) -> bytes:
|
||||
|
||||
|
||||
def build_ad_payload(mfr: bytes) -> bytes:
|
||||
"""Full AD structure excluding Flags (BlueZ/bless adds Flags itself).
|
||||
|
||||
03 03 00 A0 ; Complete list of 16-bit UUIDs
|
||||
LL FF FF FF <mfr...> ; Manufacturer Specific Data
|
||||
"""
|
||||
"""Legacy AD bytes (debug)."""
|
||||
uuid_ad = bytes((0x03, 0x03, SERVICE_UUID_16 & 0xFF, (SERVICE_UUID_16 >> 8) & 0xFF))
|
||||
# company id little-endian + payload
|
||||
mfr_body = struct.pack("<H", COMPANY_ID) + mfr
|
||||
mfr_body = COMPANY_ID.to_bytes(2, "little") + mfr
|
||||
mfr_ad = bytes((1 + len(mfr_body), 0xFF)) + mfr_body
|
||||
return uuid_ad + mfr_ad
|
||||
|
||||
|
||||
class Advertiser:
|
||||
"""Debounced advertising re-registration wrapper."""
|
||||
class BlessAdvertiser:
|
||||
"""Debounced advertising updates through a running BlessServer (0.3.x)."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
register_fn: Callable[[bytes], Any],
|
||||
unregister_fn: Callable[[], Any],
|
||||
debounce_s: float = ADV_DEBOUNCE_S,
|
||||
):
|
||||
self._register = register_fn
|
||||
self._unregister = unregister_fn
|
||||
def __init__(self, server: Any, debounce_s: float = ADV_DEBOUNCE_S):
|
||||
self._server = server
|
||||
self._debounce_s = debounce_s
|
||||
self._last_apply = 0.0
|
||||
self._pending: Optional[bytes] = None
|
||||
@@ -67,8 +55,12 @@ class Advertiser:
|
||||
if force:
|
||||
self._force = True
|
||||
|
||||
def tick(self) -> bool:
|
||||
"""Apply pending update if debounce elapsed. Returns True if re-registered."""
|
||||
def force_reregister(self) -> None:
|
||||
self._force = True
|
||||
if self._pending is None:
|
||||
self._pending = self._current
|
||||
|
||||
async def tick(self) -> bool:
|
||||
if self._pending is None and not self._force:
|
||||
return False
|
||||
now = time.monotonic()
|
||||
@@ -81,25 +73,80 @@ class Advertiser:
|
||||
if not self._force and payload == self._current:
|
||||
self._pending = None
|
||||
return False
|
||||
try:
|
||||
self._unregister()
|
||||
except Exception as e:
|
||||
log.warning("adv unregister: %s", e)
|
||||
try:
|
||||
ad = build_ad_payload(payload)
|
||||
self._register(ad)
|
||||
ok = await self._apply(payload)
|
||||
if ok:
|
||||
self._current = payload
|
||||
self._pending = None
|
||||
self._force = False
|
||||
self._last_apply = now
|
||||
log.debug("adv re-registered (%d bytes mfr)", len(payload))
|
||||
else:
|
||||
self._force = False
|
||||
return ok
|
||||
|
||||
async def _apply(self, mfr_payload: bytes) -> bool:
|
||||
server = self._server
|
||||
try:
|
||||
setup = getattr(server, "setup_task", None)
|
||||
if setup is not None:
|
||||
await setup
|
||||
except Exception as e:
|
||||
log.debug("setup_task: %s", e)
|
||||
|
||||
app = getattr(server, "app", None)
|
||||
adapter = getattr(server, "adapter", None)
|
||||
if app is None or adapter is None:
|
||||
log.error("bless server has no app/adapter — cannot update adv")
|
||||
return False
|
||||
|
||||
try:
|
||||
from dbus_next.signature import Variant # type: ignore
|
||||
from bless.backends.bluezdbus.dbus.advertisement import ( # type: ignore
|
||||
BlueZLEAdvertisement,
|
||||
Type,
|
||||
)
|
||||
except Exception as e:
|
||||
log.error("bless bluez advertisement import failed: %s", e)
|
||||
return False
|
||||
|
||||
# Drop previous advertisement(s) owned by this app.
|
||||
try:
|
||||
while getattr(app, "advertisements", None):
|
||||
await app.stop_advertising(adapter)
|
||||
except Exception as e:
|
||||
log.debug("stop_advertising: %s", e)
|
||||
|
||||
try:
|
||||
await app.set_name(adapter, LOCAL_NAME)
|
||||
advertisement = BlueZLEAdvertisement(
|
||||
Type.PERIPHERAL, len(app.advertisements) + 1, app
|
||||
)
|
||||
app.advertisements.append(advertisement)
|
||||
|
||||
if app.services:
|
||||
advertisement._service_uuids.append(app.services[0].UUID)
|
||||
else:
|
||||
advertisement._service_uuids.append(f"{SERVICE_UUID_16:04x}")
|
||||
|
||||
advertisement._local_name = LOCAL_NAME
|
||||
advertisement._manufacturer_data = {
|
||||
COMPANY_ID: Variant("ay", bytes(mfr_payload))
|
||||
}
|
||||
# Avoid experimental interval props on BlueZ 5.55 if possible —
|
||||
# leave bless defaults; they are ignored without -E.
|
||||
|
||||
app.bus.export(advertisement.path, advertisement)
|
||||
iface = adapter.get_interface("org.bluez.LEAdvertisingManager1")
|
||||
await iface.call_register_advertisement(advertisement.path, {})
|
||||
log.info(
|
||||
"advertisement registered (mfr %d bytes, path=%s)",
|
||||
len(mfr_payload),
|
||||
advertisement.path,
|
||||
)
|
||||
return True
|
||||
except Exception as e:
|
||||
log.error("adv register failed: %s", e)
|
||||
self._force = False
|
||||
return False
|
||||
|
||||
def force_reregister(self) -> None:
|
||||
self._force = True
|
||||
if self._pending is None:
|
||||
self._pending = self._current
|
||||
|
||||
# Back-compat
|
||||
Advertiser = BlessAdvertiser
|
||||
|
||||
Reference in New Issue
Block a user