Exemple #1
0
async def test_switching_adapters_based_on_stale(hass, enable_bluetooth):
    """Test switching adapters based on the previous advertisement being stale."""

    address = "44:44:33:11:23:41"
    start_time_monotonic = 50.0

    switchbot_device_poor_signal_hci0 = BLEDevice(address,
                                                  "wohand_poor_signal_hci0",
                                                  rssi=-100)
    switchbot_adv_poor_signal_hci0 = AdvertisementData(
        local_name="wohand_poor_signal_hci0", service_uuids=[])
    inject_advertisement_with_time_and_source(
        hass,
        switchbot_device_poor_signal_hci0,
        switchbot_adv_poor_signal_hci0,
        start_time_monotonic,
        "hci0",
    )

    assert (bluetooth.async_ble_device_from_address(hass, address) is
            switchbot_device_poor_signal_hci0)

    switchbot_device_poor_signal_hci1 = BLEDevice(address,
                                                  "wohand_poor_signal_hci1",
                                                  rssi=-99)
    switchbot_adv_poor_signal_hci1 = AdvertisementData(
        local_name="wohand_poor_signal_hci1", service_uuids=[])
    inject_advertisement_with_time_and_source(
        hass,
        switchbot_device_poor_signal_hci1,
        switchbot_adv_poor_signal_hci1,
        start_time_monotonic,
        "hci1",
    )

    # Should not switch adapters until the advertisement is stale
    assert (bluetooth.async_ble_device_from_address(hass, address) is
            switchbot_device_poor_signal_hci0)

    # Should switch to hci1 since the previous advertisement is stale
    # even though the signal is poor because the device is now
    # likely unreachable via hci0
    inject_advertisement_with_time_and_source(
        hass,
        switchbot_device_poor_signal_hci1,
        switchbot_adv_poor_signal_hci1,
        start_time_monotonic + STALE_ADVERTISEMENT_SECONDS + 1,
        "hci1",
    )

    assert (bluetooth.async_ble_device_from_address(hass, address) is
            switchbot_device_poor_signal_hci1)
Exemple #2
0
async def test_switching_adapters_based_on_rssi(hass, enable_bluetooth):
    """Test switching adapters based on rssi."""

    address = "44:44:33:11:23:45"

    switchbot_device_poor_signal = BLEDevice(address,
                                             "wohand_poor_signal",
                                             rssi=-100)
    switchbot_adv_poor_signal = AdvertisementData(
        local_name="wohand_poor_signal", service_uuids=[])
    inject_advertisement_with_source(hass, switchbot_device_poor_signal,
                                     switchbot_adv_poor_signal, "hci0")

    assert (bluetooth.async_ble_device_from_address(hass, address) is
            switchbot_device_poor_signal)

    switchbot_device_good_signal = BLEDevice(address,
                                             "wohand_good_signal",
                                             rssi=-60)
    switchbot_adv_good_signal = AdvertisementData(
        local_name="wohand_good_signal", service_uuids=[])
    inject_advertisement_with_source(hass, switchbot_device_good_signal,
                                     switchbot_adv_good_signal, "hci1")

    assert (bluetooth.async_ble_device_from_address(hass, address) is
            switchbot_device_good_signal)

    inject_advertisement_with_source(hass, switchbot_device_good_signal,
                                     switchbot_adv_poor_signal, "hci0")
    assert (bluetooth.async_ble_device_from_address(hass, address) is
            switchbot_device_good_signal)

    # We should not switch adapters unless the signal hits the threshold
    switchbot_device_similar_signal = BLEDevice(address,
                                                "wohand_similar_signal",
                                                rssi=-62)
    switchbot_adv_similar_signal = AdvertisementData(
        local_name="wohand_similar_signal", service_uuids=[])

    inject_advertisement_with_source(hass, switchbot_device_similar_signal,
                                     switchbot_adv_similar_signal, "hci0")
    assert (bluetooth.async_ble_device_from_address(hass, address) is
            switchbot_device_good_signal)
Exemple #3
0
async def test_advertisements_do_not_switch_adapters_for_no_reason(
        hass, enable_bluetooth):
    """Test we only switch adapters when needed."""

    address = "44:44:33:11:23:12"

    switchbot_device_signal_100 = BLEDevice(address,
                                            "wohand_signal_100",
                                            rssi=-100)
    switchbot_adv_signal_100 = AdvertisementData(
        local_name="wohand_signal_100", service_uuids=[])
    inject_advertisement_with_source(hass, switchbot_device_signal_100,
                                     switchbot_adv_signal_100, "hci0")

    assert (bluetooth.async_ble_device_from_address(hass, address) is
            switchbot_device_signal_100)

    switchbot_device_signal_99 = BLEDevice(address,
                                           "wohand_signal_99",
                                           rssi=-99)
    switchbot_adv_signal_99 = AdvertisementData(local_name="wohand_signal_99",
                                                service_uuids=[])
    inject_advertisement_with_source(hass, switchbot_device_signal_99,
                                     switchbot_adv_signal_99, "hci0")

    assert (bluetooth.async_ble_device_from_address(hass, address) is
            switchbot_device_signal_99)

    switchbot_device_signal_98 = BLEDevice(address,
                                           "wohand_good_signal",
                                           rssi=-98)
    switchbot_adv_signal_98 = AdvertisementData(
        local_name="wohand_good_signal", service_uuids=[])
    inject_advertisement_with_source(hass, switchbot_device_signal_98,
                                     switchbot_adv_signal_98, "hci1")

    # should not switch to hci1
    assert (bluetooth.async_ble_device_from_address(hass, address) is
            switchbot_device_signal_99)
Exemple #4
0
async def test_preserve_new_tracked_device_name(hass, mock_bluetooth,
                                                mock_device_tracker_conf):
    """Test preserving tracked device name across new seens."""

    address = "DE:AD:BE:EF:13:37"
    name = "Mock device name"
    entity_id = f"{DOMAIN}.{slugify(name)}"

    with patch(
            "homeassistant.components.bluetooth.async_discovered_service_info"
    ) as mock_async_discovered_service_info, patch.object(
            device_tracker, "MIN_SEEN_NEW", 3):

        device = BluetoothServiceInfoBleak(
            name=name,
            address=address,
            rssi=-19,
            manufacturer_data={},
            service_data={},
            service_uuids=[],
            source="local",
            device=BLEDevice(address, None),
            advertisement=AdvertisementData(local_name="empty"),
            time=0,
            connectable=False,
        )
        # Return with name when seen first time
        mock_async_discovered_service_info.return_value = [device]

        config = {
            CONF_PLATFORM: "bluetooth_le_tracker",
            CONF_SCAN_INTERVAL: timedelta(minutes=1),
            CONF_TRACK_NEW: True,
        }
        result = await async_setup_component(hass, DOMAIN, {DOMAIN: config})
        assert result

        # Seen once here; return without name when seen subsequent times
        device = BluetoothServiceInfoBleak(
            name=None,
            address=address,
            rssi=-19,
            manufacturer_data={},
            service_data={},
            service_uuids=[],
            source="local",
            device=BLEDevice(address, None),
            advertisement=AdvertisementData(local_name="empty"),
            time=0,
            connectable=False,
        )
        # Return with name when seen first time
        mock_async_discovered_service_info.return_value = [device]

        # Tick until device seen enough times for to be registered for tracking
        for _ in range(device_tracker.MIN_SEEN_NEW - 1):
            async_fire_time_changed(
                hass,
                dt_util.utcnow() + config[CONF_SCAN_INTERVAL] +
                timedelta(seconds=1),
            )
            await hass.async_block_till_done()

    state = hass.states.get(entity_id)
    assert state
    assert state.name == name
Exemple #5
0
async def test_tracking_battery_successful(hass, mock_bluetooth,
                                           mock_device_tracker_conf):
    """Test tracking the battery gets a value."""

    address = "DE:AD:BE:EF:13:37"
    name = "Mock device name"
    entity_id = f"{DOMAIN}.{slugify(name)}"

    with patch(
            "homeassistant.components.bluetooth.async_discovered_service_info"
    ) as mock_async_discovered_service_info, patch.object(
            device_tracker, "MIN_SEEN_NEW", 3):

        device = BluetoothServiceInfoBleak(
            name=name,
            address=address,
            rssi=-19,
            manufacturer_data={},
            service_data={},
            service_uuids=[],
            source="local",
            device=BLEDevice(address, None),
            advertisement=AdvertisementData(local_name="empty"),
            time=0,
            connectable=True,
        )
        # Return with name when seen first time
        mock_async_discovered_service_info.return_value = [device]

        config = {
            CONF_PLATFORM: "bluetooth_le_tracker",
            CONF_SCAN_INTERVAL: timedelta(minutes=1),
            CONF_TRACK_BATTERY: True,
            CONF_TRACK_BATTERY_INTERVAL: timedelta(minutes=2),
            CONF_TRACK_NEW: True,
        }
        result = await async_setup_component(hass, DOMAIN, {DOMAIN: config})
        assert result

        # Tick until device seen enough times for to be registered for tracking
        for _ in range(device_tracker.MIN_SEEN_NEW - 1):
            async_fire_time_changed(
                hass,
                dt_util.utcnow() + config[CONF_SCAN_INTERVAL] +
                timedelta(seconds=1),
            )
            await hass.async_block_till_done()

        with patch(
                "homeassistant.components.bluetooth_le_tracker.device_tracker.BleakClient",
                MockBleakClientBattery5,
        ):
            # Wait for the battery scan
            async_fire_time_changed(
                hass,
                dt_util.utcnow() + config[CONF_SCAN_INTERVAL] +
                timedelta(seconds=1) + timedelta(minutes=2),
            )
            await hass.async_block_till_done()

    state = hass.states.get(entity_id)
    assert state.name == name
    assert state.attributes["battery"] == 5
Exemple #6
0
async def test_recovery_from_dbus_restart(hass, one_adapter):
    """Test we can recover when DBus gets restarted out from under us."""

    called_start = 0
    called_stop = 0
    _callback = None
    mock_discovered = []

    class MockBleakScanner:
        async def start(self, *args, **kwargs):
            """Mock Start."""
            nonlocal called_start
            called_start += 1

        async def stop(self, *args, **kwargs):
            """Mock Start."""
            nonlocal called_stop
            called_stop += 1

        @property
        def discovered_devices(self):
            """Mock discovered_devices."""
            nonlocal mock_discovered
            return mock_discovered

        def register_detection_callback(self, callback: AdvertisementDataCallback):
            """Mock Register Detection Callback."""
            nonlocal _callback
            _callback = callback

    scanner = MockBleakScanner()

    with patch(
        "homeassistant.components.bluetooth.scanner.OriginalBleakScanner",
        return_value=scanner,
    ):
        await async_setup_with_one_adapter(hass)

        assert called_start == 1

    start_time_monotonic = 1000
    scanner = _get_manager()
    mock_discovered = [MagicMock()]

    # Ensure we don't restart the scanner if we don't need to
    with patch(
        "homeassistant.components.bluetooth.scanner.MONOTONIC_TIME",
        return_value=start_time_monotonic + 10,
    ):
        async_fire_time_changed(hass, dt_util.utcnow() + SCANNER_WATCHDOG_INTERVAL)
        await hass.async_block_till_done()

    assert called_start == 1

    # Fire a callback to reset the timer
    with patch(
        "homeassistant.components.bluetooth.scanner.MONOTONIC_TIME",
        return_value=start_time_monotonic,
    ):
        _callback(
            BLEDevice("44:44:33:11:23:42", "any_name"),
            AdvertisementData(local_name="any_name"),
        )

    # Ensure we don't restart the scanner if we don't need to
    with patch(
        "homeassistant.components.bluetooth.scanner.MONOTONIC_TIME",
        return_value=start_time_monotonic + 20,
    ):
        async_fire_time_changed(hass, dt_util.utcnow() + SCANNER_WATCHDOG_INTERVAL)
        await hass.async_block_till_done()

    assert called_start == 1

    # We hit the timer, so we restart the scanner
    with patch(
        "homeassistant.components.bluetooth.scanner.MONOTONIC_TIME",
        return_value=start_time_monotonic + SCANNER_WATCHDOG_TIMEOUT,
    ):
        async_fire_time_changed(hass, dt_util.utcnow() + SCANNER_WATCHDOG_INTERVAL)
        await hass.async_block_till_done()

    assert called_start == 2
Exemple #7
0
async def test_diagnostics(
    hass, hass_client, mock_bleak_scanner_start, enable_bluetooth, two_adapters
):
    """Test we can setup and unsetup bluetooth with multiple adapters."""
    # Normally we do not want to patch our classes, but since bleak will import
    # a different scanner based on the operating system, we need to patch here
    # because we cannot import the scanner class directly without it throwing an
    # error if the test is not running on linux since we won't have the correct
    # deps installed when testing on MacOS.
    with patch(
        "homeassistant.components.bluetooth.scanner.HaScanner.discovered_devices",
        [BLEDevice(name="x", rssi=-60, address="44:44:33:11:23:45")],
    ), patch(
        "homeassistant.components.bluetooth.diagnostics.platform.system",
        return_value="Linux",
    ), patch(
        "homeassistant.components.bluetooth.diagnostics.get_dbus_managed_objects",
        return_value={
            "org.bluez": {
                "/org/bluez/hci0": {
                    "org.bluez.Adapter1": {
                        "Name": "BlueZ 5.63",
                        "Alias": "BlueZ 5.63",
                        "Modalias": "usb:v1D6Bp0246d0540",
                        "Discovering": False,
                    },
                    "org.bluez.AdvertisementMonitorManager1": {
                        "SupportedMonitorTypes": ["or_patterns"],
                        "SupportedFeatures": [],
                    },
                }
            }
        },
    ):
        entry1 = MockConfigEntry(
            domain=bluetooth.DOMAIN, data={}, unique_id="00:00:00:00:00:01"
        )
        entry1.add_to_hass(hass)

        entry2 = MockConfigEntry(
            domain=bluetooth.DOMAIN, data={}, unique_id="00:00:00:00:00:02"
        )
        entry2.add_to_hass(hass)

        assert await hass.config_entries.async_setup(entry1.entry_id)
        await hass.async_block_till_done()
        assert await hass.config_entries.async_setup(entry2.entry_id)
        await hass.async_block_till_done()

        diag = await get_diagnostics_for_config_entry(hass, hass_client, entry1)
        assert diag == {
            "adapters": {
                "hci0": {
                    "address": "00:00:00:00:00:01",
                    "hw_version": "usbid:1234",
                    "passive_scan": False,
                    "sw_version": "BlueZ 4.63",
                },
                "hci1": {
                    "address": "00:00:00:00:00:02",
                    "hw_version": "usbid:1234",
                    "passive_scan": True,
                    "sw_version": "BlueZ 4.63",
                },
            },
            "dbus": {
                "org.bluez": {
                    "/org/bluez/hci0": {
                        "org.bluez.Adapter1": {
                            "Alias": "BlueZ " "5.63",
                            "Discovering": False,
                            "Modalias": "usb:v1D6Bp0246d0540",
                            "Name": "BlueZ " "5.63",
                        },
                        "org.bluez.AdvertisementMonitorManager1": {
                            "SupportedFeatures": [],
                            "SupportedMonitorTypes": ["or_patterns"],
                        },
                    }
                }
            },
            "manager": {
                "adapters": {
                    "hci0": {
                        "address": "00:00:00:00:00:01",
                        "hw_version": "usbid:1234",
                        "passive_scan": False,
                        "sw_version": "BlueZ 4.63",
                    },
                    "hci1": {
                        "address": "00:00:00:00:00:02",
                        "hw_version": "usbid:1234",
                        "passive_scan": True,
                        "sw_version": "BlueZ 4.63",
                    },
                },
                "connectable_history": [],
                "history": [],
                "scanners": [
                    {
                        "adapter": "hci0",
                        "discovered_devices": [
                            {"address": "44:44:33:11:23:45", "name": "x", "rssi": -60}
                        ],
                        "last_detection": ANY,
                        "name": "hci0 (00:00:00:00:00:01)",
                        "source": "hci0",
                        "start_time": ANY,
                        "type": "HaScanner",
                    },
                    {
                        "adapter": "hci0",
                        "discovered_devices": [
                            {"address": "44:44:33:11:23:45", "name": "x", "rssi": -60}
                        ],
                        "last_detection": ANY,
                        "name": "hci0 (00:00:00:00:00:01)",
                        "source": "hci0",
                        "start_time": ANY,
                        "type": "HaScanner",
                    },
                    {
                        "adapter": "hci1",
                        "discovered_devices": [
                            {"address": "44:44:33:11:23:45", "name": "x", "rssi": -60}
                        ],
                        "last_detection": ANY,
                        "name": "hci1 (00:00:00:00:00:02)",
                        "source": "hci1",
                        "start_time": ANY,
                        "type": "HaScanner",
                    },
                ],
            },
        }