Ejemplo n.º 1
0
 async def _async_poll(service_info: BluetoothServiceInfoBleak):
     # BluetoothServiceInfoBleak is defined in HA, otherwise would just pass it
     # directly to the Xiaomi code
     # Make sure the device we have is one that we can connect with
     # in case its coming from a passive scanner
     if service_info.connectable:
         connectable_device = service_info.device
     elif device := async_ble_device_from_address(
             hass, service_info.device.address, True):
         connectable_device = device
Ejemplo n.º 2
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)
Ejemplo n.º 3
0
 async def _async_see_update_ble_battery(
     mac: str,
     now: datetime,
     service_info: bluetooth.BluetoothServiceInfoBleak,
 ) -> None:
     """Lookup Bluetooth LE devices and update status."""
     battery = None
     # We need one we can connect to since the tracker will
     # accept devices from non-connectable sources
     if service_info.connectable:
         device = service_info.device
     elif connectable_device := bluetooth.async_ble_device_from_address(
             hass, service_info.device.address, True):
         device = connectable_device
Ejemplo n.º 4
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)
Ejemplo n.º 5
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)
Ejemplo n.º 6
0
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
    """Set up Switchbot from a config entry."""
    assert entry.unique_id is not None
    hass.data.setdefault(DOMAIN, {})
    if CONF_ADDRESS not in entry.data and CONF_MAC in entry.data:
        # Bleak uses addresses not mac addresses which are are actually
        # UUIDs on some platforms (MacOS).
        mac = entry.data[CONF_MAC]
        if "-" not in mac:
            mac = dr.format_mac(mac)
        hass.config_entries.async_update_entry(
            entry,
            data={
                **entry.data, CONF_ADDRESS: mac
            },
        )

    if not entry.options:
        hass.config_entries.async_update_entry(
            entry,
            options={CONF_RETRY_COUNT: DEFAULT_RETRY_COUNT},
        )

    sensor_type: str = entry.data[CONF_SENSOR_TYPE]
    switchbot_model = HASS_SENSOR_TYPE_TO_SWITCHBOT_MODEL[sensor_type]
    # connectable means we can make connections to the device
    connectable = switchbot_model in CONNECTABLE_SUPPORTED_MODEL_TYPES
    address: str = entry.data[CONF_ADDRESS]
    ble_device = bluetooth.async_ble_device_from_address(
        hass, address.upper(), connectable)
    if not ble_device:
        raise ConfigEntryNotReady(
            f"Could not find Switchbot {sensor_type} with address {address}")
    cls = CLASS_BY_DEVICE.get(sensor_type, switchbot.SwitchbotDevice)
    device = cls(
        device=ble_device,
        password=entry.data.get(CONF_PASSWORD),
        retry_count=entry.options[CONF_RETRY_COUNT],
    )

    coordinator = hass.data[DOMAIN][
        entry.entry_id] = SwitchbotDataUpdateCoordinator(
            hass,
            _LOGGER,
            ble_device,
            device,
            entry.unique_id,
            entry.data.get(CONF_NAME, entry.title),
            connectable,
            switchbot_model,
        )
    entry.async_on_unload(coordinator.async_start())
    if not await coordinator.async_wait_ready():
        raise ConfigEntryNotReady(
            f"Switchbot {sensor_type} with {address} not ready")

    entry.async_on_unload(entry.add_update_listener(_async_update_listener))
    await hass.config_entries.async_forward_entry_setups(
        entry, PLATFORMS_BY_TYPE[sensor_type])

    return True
Ejemplo n.º 7
0
 async def async_connect_and_update(self) -> AsyncIterator[Device]:
     """Provide an up to date device for use during connections."""
     if ble_device := async_ble_device_from_address(self.hass,
                                                    self.device.address):
         self.device.device = ble_device
Ejemplo n.º 8
0
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
    """Set up LED BLE from a config entry."""
    address: str = entry.data[CONF_ADDRESS]
    ble_device = bluetooth.async_ble_device_from_address(
        hass, address.upper(), True)
    if not ble_device:
        raise ConfigEntryNotReady(
            f"Could not find LED BLE device with address {address}")

    led_ble = LEDBLE(ble_device)

    @callback
    def _async_update_ble(
        service_info: bluetooth.BluetoothServiceInfoBleak,
        change: bluetooth.BluetoothChange,
    ) -> None:
        """Update from a ble callback."""
        led_ble.set_ble_device(service_info.device)

    entry.async_on_unload(
        bluetooth.async_register_callback(
            hass,
            _async_update_ble,
            BluetoothCallbackMatcher({ADDRESS: address}),
            bluetooth.BluetoothScanningMode.PASSIVE,
        ))

    async def _async_update():
        """Update the device state."""
        try:
            await led_ble.update()
        except BLEAK_EXCEPTIONS as ex:
            raise UpdateFailed(str(ex)) from ex

    startup_event = asyncio.Event()
    cancel_first_update = led_ble.register_callback(
        lambda *_: startup_event.set())
    coordinator = DataUpdateCoordinator(
        hass,
        _LOGGER,
        name=led_ble.name,
        update_method=_async_update,
        update_interval=timedelta(seconds=UPDATE_SECONDS),
    )

    try:
        await coordinator.async_config_entry_first_refresh()
    except ConfigEntryNotReady:
        cancel_first_update()
        raise

    try:
        async with async_timeout.timeout(DEVICE_TIMEOUT):
            await startup_event.wait()
    except asyncio.TimeoutError as ex:
        raise ConfigEntryNotReady(
            "Unable to communicate with the device; "
            f"Try moving the Bluetooth adapter closer to {led_ble.name}"
        ) from ex
    finally:
        cancel_first_update()

    hass.data.setdefault(DOMAIN, {})[entry.entry_id] = LEDBLEData(
        entry.title, led_ble, coordinator)

    await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
    entry.async_on_unload(entry.add_update_listener(_async_update_listener))

    async def _async_stop(event: Event) -> None:
        """Close the connection."""
        await led_ble.stop()

    entry.async_on_unload(
        hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, _async_stop))
    return True