ble version

This commit is contained in:
2026-10-16 07:19:06 +03:00
parent 32abb8bf0a
commit f74d52d39d
65 changed files with 3835 additions and 143 deletions
+152
View File
@@ -0,0 +1,152 @@
#!/usr/bin/env python3
"""Advertising mirror for bless 0.3.x (BlueZ): stop → register with ManufacturerData."""
from __future__ import annotations
import logging
import time
from typing import Any, List, Optional
from config import (
ADV_DEBOUNCE_S,
ADV_VERSION,
COMPANY_ID,
LOCAL_NAME,
N,
SERVICE_UUID_16,
)
log = logging.getLogger("collector.adv")
def build_manufacturer_data(bitmap: int, reports: List[bytes]) -> bytes:
"""ver=1 + bitmap + N×4 B reports."""
if N == 1 and reports:
return bytes((ADV_VERSION,)) + reports[0][:4]
body = bytearray()
body.append(ADV_VERSION)
body.append(bitmap & 0xFF)
for i in range(N):
r = reports[i] if i < len(reports) else bytes((0xFF, 0, 0xFF, 0xFF))
body.extend(r[:4])
return bytes(body)
def build_ad_payload(mfr: bytes) -> bytes:
"""Legacy AD bytes (debug)."""
uuid_ad = bytes((0x03, 0x03, SERVICE_UUID_16 & 0xFF, (SERVICE_UUID_16 >> 8) & 0xFF))
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 BlessAdvertiser:
"""Debounced advertising updates through a running BlessServer (0.3.x)."""
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
self._current: Optional[bytes] = None
self._force = False
def set_mirror(self, mfr_payload: bytes, force: bool = False) -> None:
self._pending = mfr_payload
if force:
self._force = True
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()
if not self._force and (now - self._last_apply) < self._debounce_s:
return False
payload = self._pending if self._pending is not None else self._current
if payload is None:
self._force = False
return False
if not self._force and payload == self._current:
self._pending = None
return False
ok = await self._apply(payload)
if ok:
self._current = payload
self._pending = None
self._force = False
self._last_apply = now
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)
return False
# Back-compat
Advertiser = BlessAdvertiser