Add raw advertisement data to Bluetooth WebSocket API (#150358)

This commit is contained in:
J. Nick Koston
2025-08-10 02:44:15 -05:00
committed by GitHub
parent 1c603f968f
commit 865b3a6646
3 changed files with 22 additions and 3 deletions

View File

@@ -39,7 +39,13 @@ def async_setup(hass: HomeAssistant) -> None:
def serialize_service_info( def serialize_service_info(
service_info: BluetoothServiceInfoBleak, time_diff: float service_info: BluetoothServiceInfoBleak, time_diff: float
) -> dict[str, Any]: ) -> dict[str, Any]:
"""Serialize a BluetoothServiceInfoBleak object.""" """Serialize a BluetoothServiceInfoBleak object.
The raw field is included for:
1. Debugging - to see the actual advertisement packet
2. Data freshness - manufacturer_data and service_data are aggregated
across multiple advertisements, raw shows the latest packet only
"""
return { return {
"name": service_info.name, "name": service_info.name,
"address": service_info.address, "address": service_info.address,
@@ -57,6 +63,7 @@ def serialize_service_info(
"connectable": service_info.connectable, "connectable": service_info.connectable,
"time": service_info.time + time_diff, "time": service_info.time + time_diff,
"tx_power": service_info.tx_power, "tx_power": service_info.tx_power,
"raw": service_info.raw.hex() if service_info.raw else None,
} }

View File

@@ -145,6 +145,7 @@ def inject_advertisement_with_time_and_source_connectable(
time: float, time: float,
source: str, source: str,
connectable: bool, connectable: bool,
raw: bytes | None = None,
) -> None: ) -> None:
"""Inject an advertisement into the manager from a specific source at a time and connectable status.""" """Inject an advertisement into the manager from a specific source at a time and connectable status."""
async_get_advertisement_callback(hass)( async_get_advertisement_callback(hass)(
@@ -161,6 +162,7 @@ def inject_advertisement_with_time_and_source_connectable(
connectable=connectable, connectable=connectable,
time=time, time=time,
tx_power=adv.tx_power, tx_power=adv.tx_power,
raw=raw,
) )
) )

View File

@@ -22,6 +22,7 @@ from . import (
generate_advertisement_data, generate_advertisement_data,
generate_ble_device, generate_ble_device,
inject_advertisement_with_source, inject_advertisement_with_source,
inject_advertisement_with_time_and_source_connectable,
) )
from tests.common import MockConfigEntry, async_fire_time_changed from tests.common import MockConfigEntry, async_fire_time_changed
@@ -72,6 +73,7 @@ async def test_subscribe_advertisements(
"source": HCI0_SOURCE_ADDRESS, "source": HCI0_SOURCE_ADDRESS,
"time": ANY, "time": ANY,
"tx_power": -127, "tx_power": -127,
"raw": None,
} }
] ]
} }
@@ -83,8 +85,15 @@ async def test_subscribe_advertisements(
service_uuids=[], service_uuids=[],
rssi=-80, rssi=-80,
) )
inject_advertisement_with_source( # Inject with raw bytes data
hass, switchbot_device_signal_100, switchbot_adv_signal_100, HCI1_SOURCE_ADDRESS inject_advertisement_with_time_and_source_connectable(
hass,
switchbot_device_signal_100,
switchbot_adv_signal_100,
time.monotonic(),
HCI1_SOURCE_ADDRESS,
True,
raw=b"\x02\x01\x06\x03\x03\x0f\x18",
) )
async with asyncio.timeout(1): async with asyncio.timeout(1):
response = await client.receive_json() response = await client.receive_json()
@@ -101,6 +110,7 @@ async def test_subscribe_advertisements(
"source": HCI1_SOURCE_ADDRESS, "source": HCI1_SOURCE_ADDRESS,
"time": ANY, "time": ANY,
"tx_power": -127, "tx_power": -127,
"raw": "02010603030f18",
} }
] ]
} }