Prefilter noisy apple devices from bluetooth (#77808)

This commit is contained in:
J. Nick Koston 2022-09-04 20:57:40 -04:00 committed by GitHub
parent c5100d2895
commit 804e4ab989
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 106 additions and 40 deletions

View file

@ -54,6 +54,10 @@ if TYPE_CHECKING:
FILTER_UUIDS: Final = "UUIDs" FILTER_UUIDS: Final = "UUIDs"
APPLE_MFR_ID: Final = 76
APPLE_HOMEKIT_START_BYTE: Final = 0x06 # homekit_controller
APPLE_DEVICE_ID_START_BYTE: Final = 0x10 # bluetooth_le_tracker
APPLE_START_BYTES_WANTED: Final = {APPLE_DEVICE_ID_START_BYTE, APPLE_HOMEKIT_START_BYTE}
RSSI_SWITCH_THRESHOLD = 6 RSSI_SWITCH_THRESHOLD = 6
@ -290,6 +294,19 @@ class BluetoothManager:
than the source from the history or the timestamp than the source from the history or the timestamp
in the history is older than 180s in the history is older than 180s
""" """
# Pre-filter noisy apple devices as they can account for 20-35% of the
# traffic on a typical network.
advertisement_data = service_info.advertisement
manufacturer_data = advertisement_data.manufacturer_data
if (
len(manufacturer_data) == 1
and (apple_data := manufacturer_data.get(APPLE_MFR_ID))
and apple_data[0] not in APPLE_START_BYTES_WANTED
and not advertisement_data.service_data
):
return
device = service_info.device device = service_info.device
connectable = service_info.connectable connectable = service_info.connectable
address = device.address address = device.address
@ -299,7 +316,6 @@ class BluetoothManager:
return return
self._history[address] = service_info self._history[address] = service_info
advertisement_data = service_info.advertisement
source = service_info.source source = service_info.source
if connectable: if connectable:

View file

@ -1291,16 +1291,16 @@ async def test_register_callback_by_manufacturer_id(
cancel = bluetooth.async_register_callback( cancel = bluetooth.async_register_callback(
hass, hass,
_fake_subscriber, _fake_subscriber,
{MANUFACTURER_ID: 76}, {MANUFACTURER_ID: 21},
BluetoothScanningMode.ACTIVE, BluetoothScanningMode.ACTIVE,
) )
assert len(mock_bleak_scanner_start.mock_calls) == 1 assert len(mock_bleak_scanner_start.mock_calls) == 1
apple_device = BLEDevice("44:44:33:11:23:45", "apple") apple_device = BLEDevice("44:44:33:11:23:45", "rtx")
apple_adv = AdvertisementData( apple_adv = AdvertisementData(
local_name="apple", local_name="rtx",
manufacturer_data={76: b"\xd8.\xad\xcd\r\x85"}, manufacturer_data={21: b"\xd8.\xad\xcd\r\x85"},
) )
inject_advertisement(hass, apple_device, apple_adv) inject_advertisement(hass, apple_device, apple_adv)
@ -1316,9 +1316,59 @@ async def test_register_callback_by_manufacturer_id(
assert len(callbacks) == 1 assert len(callbacks) == 1
service_info: BluetoothServiceInfo = callbacks[0][0] service_info: BluetoothServiceInfo = callbacks[0][0]
assert service_info.name == "apple" assert service_info.name == "rtx"
assert service_info.manufacturer == "Apple, Inc." assert service_info.manufacturer == "RTX Telecom A/S"
assert service_info.manufacturer_id == 76 assert service_info.manufacturer_id == 21
async def test_filtering_noisy_apple_devices(
hass, mock_bleak_scanner_start, enable_bluetooth
):
"""Test filtering noisy apple devices."""
mock_bt = []
callbacks = []
def _fake_subscriber(
service_info: BluetoothServiceInfo, change: BluetoothChange
) -> None:
"""Fake subscriber for the BleakScanner."""
callbacks.append((service_info, change))
with patch(
"homeassistant.components.bluetooth.async_get_bluetooth", return_value=mock_bt
):
await async_setup_with_default_adapter(hass)
with patch.object(hass.config_entries.flow, "async_init"):
hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED)
await hass.async_block_till_done()
cancel = bluetooth.async_register_callback(
hass,
_fake_subscriber,
{MANUFACTURER_ID: 21},
BluetoothScanningMode.ACTIVE,
)
assert len(mock_bleak_scanner_start.mock_calls) == 1
apple_device = BLEDevice("44:44:33:11:23:45", "rtx")
apple_adv = AdvertisementData(
local_name="noisy",
manufacturer_data={76: b"\xd8.\xad\xcd\r\x85"},
)
inject_advertisement(hass, apple_device, apple_adv)
empty_device = BLEDevice("11:22:33:44:55:66", "empty")
empty_adv = AdvertisementData(local_name="empty")
inject_advertisement(hass, empty_device, empty_adv)
await hass.async_block_till_done()
cancel()
assert len(callbacks) == 0
async def test_register_callback_by_address_connectable_manufacturer_id( async def test_register_callback_by_address_connectable_manufacturer_id(
@ -1346,21 +1396,21 @@ async def test_register_callback_by_address_connectable_manufacturer_id(
cancel = bluetooth.async_register_callback( cancel = bluetooth.async_register_callback(
hass, hass,
_fake_subscriber, _fake_subscriber,
{MANUFACTURER_ID: 76, CONNECTABLE: False, ADDRESS: "44:44:33:11:23:45"}, {MANUFACTURER_ID: 21, CONNECTABLE: False, ADDRESS: "44:44:33:11:23:45"},
BluetoothScanningMode.ACTIVE, BluetoothScanningMode.ACTIVE,
) )
assert len(mock_bleak_scanner_start.mock_calls) == 1 assert len(mock_bleak_scanner_start.mock_calls) == 1
apple_device = BLEDevice("44:44:33:11:23:45", "apple") apple_device = BLEDevice("44:44:33:11:23:45", "rtx")
apple_adv = AdvertisementData( apple_adv = AdvertisementData(
local_name="apple", local_name="rtx",
manufacturer_data={76: b"\xd8.\xad\xcd\r\x85"}, manufacturer_data={21: b"\xd8.\xad\xcd\r\x85"},
) )
inject_advertisement(hass, apple_device, apple_adv) inject_advertisement(hass, apple_device, apple_adv)
apple_device_wrong_address = BLEDevice("44:44:33:11:23:46", "apple") apple_device_wrong_address = BLEDevice("44:44:33:11:23:46", "rtx")
inject_advertisement(hass, apple_device_wrong_address, apple_adv) inject_advertisement(hass, apple_device_wrong_address, apple_adv)
await hass.async_block_till_done() await hass.async_block_till_done()
@ -1370,9 +1420,9 @@ async def test_register_callback_by_address_connectable_manufacturer_id(
assert len(callbacks) == 1 assert len(callbacks) == 1
service_info: BluetoothServiceInfo = callbacks[0][0] service_info: BluetoothServiceInfo = callbacks[0][0]
assert service_info.name == "apple" assert service_info.name == "rtx"
assert service_info.manufacturer == "Apple, Inc." assert service_info.manufacturer == "RTX Telecom A/S"
assert service_info.manufacturer_id == 76 assert service_info.manufacturer_id == 21
async def test_register_callback_by_manufacturer_id_and_address( async def test_register_callback_by_manufacturer_id_and_address(
@ -1400,19 +1450,19 @@ async def test_register_callback_by_manufacturer_id_and_address(
cancel = bluetooth.async_register_callback( cancel = bluetooth.async_register_callback(
hass, hass,
_fake_subscriber, _fake_subscriber,
{MANUFACTURER_ID: 76, ADDRESS: "44:44:33:11:23:45"}, {MANUFACTURER_ID: 21, ADDRESS: "44:44:33:11:23:45"},
BluetoothScanningMode.ACTIVE, BluetoothScanningMode.ACTIVE,
) )
assert len(mock_bleak_scanner_start.mock_calls) == 1 assert len(mock_bleak_scanner_start.mock_calls) == 1
apple_device = BLEDevice("44:44:33:11:23:45", "apple") rtx_device = BLEDevice("44:44:33:11:23:45", "rtx")
apple_adv = AdvertisementData( rtx_adv = AdvertisementData(
local_name="apple", local_name="rtx",
manufacturer_data={76: b"\xd8.\xad\xcd\r\x85"}, manufacturer_data={21: b"\xd8.\xad\xcd\r\x85"},
) )
inject_advertisement(hass, apple_device, apple_adv) inject_advertisement(hass, rtx_device, rtx_adv)
yale_device = BLEDevice("44:44:33:11:23:45", "apple") yale_device = BLEDevice("44:44:33:11:23:45", "apple")
yale_adv = AdvertisementData( yale_adv = AdvertisementData(
@ -1426,7 +1476,7 @@ async def test_register_callback_by_manufacturer_id_and_address(
other_apple_device = BLEDevice("44:44:33:11:23:22", "apple") other_apple_device = BLEDevice("44:44:33:11:23:22", "apple")
other_apple_adv = AdvertisementData( other_apple_adv = AdvertisementData(
local_name="apple", local_name="apple",
manufacturer_data={76: b"\xd8.\xad\xcd\r\x85"}, manufacturer_data={21: b"\xd8.\xad\xcd\r\x85"},
) )
inject_advertisement(hass, other_apple_device, other_apple_adv) inject_advertisement(hass, other_apple_device, other_apple_adv)
@ -1435,9 +1485,9 @@ async def test_register_callback_by_manufacturer_id_and_address(
assert len(callbacks) == 1 assert len(callbacks) == 1
service_info: BluetoothServiceInfo = callbacks[0][0] service_info: BluetoothServiceInfo = callbacks[0][0]
assert service_info.name == "apple" assert service_info.name == "rtx"
assert service_info.manufacturer == "Apple, Inc." assert service_info.manufacturer == "RTX Telecom A/S"
assert service_info.manufacturer_id == 76 assert service_info.manufacturer_id == 21
async def test_register_callback_by_service_uuid_and_address( async def test_register_callback_by_service_uuid_and_address(
@ -1603,31 +1653,31 @@ async def test_register_callback_by_local_name(
cancel = bluetooth.async_register_callback( cancel = bluetooth.async_register_callback(
hass, hass,
_fake_subscriber, _fake_subscriber,
{LOCAL_NAME: "apple"}, {LOCAL_NAME: "rtx"},
BluetoothScanningMode.ACTIVE, BluetoothScanningMode.ACTIVE,
) )
assert len(mock_bleak_scanner_start.mock_calls) == 1 assert len(mock_bleak_scanner_start.mock_calls) == 1
apple_device = BLEDevice("44:44:33:11:23:45", "apple") rtx_device = BLEDevice("44:44:33:11:23:45", "rtx")
apple_adv = AdvertisementData( rtx_adv = AdvertisementData(
local_name="apple", local_name="rtx",
manufacturer_data={76: b"\xd8.\xad\xcd\r\x85"}, manufacturer_data={21: b"\xd8.\xad\xcd\r\x85"},
) )
inject_advertisement(hass, apple_device, apple_adv) inject_advertisement(hass, rtx_device, rtx_adv)
empty_device = BLEDevice("11:22:33:44:55:66", "empty") empty_device = BLEDevice("11:22:33:44:55:66", "empty")
empty_adv = AdvertisementData(local_name="empty") empty_adv = AdvertisementData(local_name="empty")
inject_advertisement(hass, empty_device, empty_adv) inject_advertisement(hass, empty_device, empty_adv)
apple_device_2 = BLEDevice("44:44:33:11:23:45", "apple") rtx_device_2 = BLEDevice("44:44:33:11:23:45", "rtx")
apple_adv_2 = AdvertisementData( rtx_adv_2 = AdvertisementData(
local_name="apple2", local_name="rtx2",
manufacturer_data={76: b"\xd8.\xad\xcd\r\x85"}, manufacturer_data={21: b"\xd8.\xad\xcd\r\x85"},
) )
inject_advertisement(hass, apple_device_2, apple_adv_2) inject_advertisement(hass, rtx_device_2, rtx_adv_2)
await hass.async_block_till_done() await hass.async_block_till_done()
@ -1636,9 +1686,9 @@ async def test_register_callback_by_local_name(
assert len(callbacks) == 1 assert len(callbacks) == 1
service_info: BluetoothServiceInfo = callbacks[0][0] service_info: BluetoothServiceInfo = callbacks[0][0]
assert service_info.name == "apple" assert service_info.name == "rtx"
assert service_info.manufacturer == "Apple, Inc." assert service_info.manufacturer == "RTX Telecom A/S"
assert service_info.manufacturer_id == 76 assert service_info.manufacturer_id == 21
async def test_register_callback_by_local_name_overly_broad( async def test_register_callback_by_local_name_overly_broad(