Danielhiversen/pySwitchbot

_ensure_connected() doesn't seem to work.

Opened this issue · 3 comments

If I run:

ble_device = await BleakScanner.find_device_by_address(MAC, timeout=20)
device = Switchbot(ble_device)
device.turn_on()

Then after 60 seconds if I try to run

device.turn_off()

It says:
bleak_retry_connector.BleakNotFoundError: D0-A7-8B-16-46-4E (D0:A7:8B:16:46:4E) - /org/bluez/hci1/dev_D0_A7_8B_16_46_4E: Failed to connect: Device with address D0:A7:8B:16:46:4E was not found. It may have been removed from BlueZ when scanning stopped.

You need to keep the scanner running and pass in new BLE Device objects to the lib via bleak's advertisement callbacks otherwise bluez will remove the object from the bus

Thank you for explaining this! Do you have an example of how this would be / has been implemented? Thanks!

@d-walsh I have this working in a (not yet open-source) application of myself.

You need to initialize the bot & scanner like this (taken from my codebase):

class MyBot:
    def __init__(mac, password) 
        self.mac = mac
        self.password = password

    async def press(self):
        return await self._try_command(lambda: self.bot.press())

    async def _init(self):
        if self.bot: return True

        self.scanner = BleakScanner(self._detection_callback)
        device = await self.scanner.find_device_by_address(self.mac)

        if not device:
            logger.error("Could not find device")
            return False

        self.bot = Switchbot(device=device, password=self.password)
        await self.scanner.start()

    def _detection_callback(device, adv_data):
        if device.address == self.mac:
            result = parse_advertisement_data(device, adv_data, SwitchbotModel.BOT)
            self.bot.update_from_advertisement(result)

    async def _try_command(self, block):
        if not await self._init(): return

        try:
            return await block()
        except (BleakNotFoundError, BleakDeviceNotFoundError):
            await self.scanner.start()
            await asyncio.sleep(5)
            return await block()

This makes sure the advertisement data is continously updated. If you have multiple devices, you need to refactor this so you are only using a single BleakScanner instance in your app.