Beispiel #1
0
class BTWATTCH2:
    def __init__(self, address):
        self.client = BleakClient(address)
        self.loop = asyncio.get_event_loop()

        self.services = self.loop.run_until_complete(self.setup())
        self.Tx = self.services.get_characteristic(UART_TX_UUID)
        self.Rx = self.services.get_characteristic(UART_RX_UUID)
        self.char_device_name = self.services.get_characteristic(
            DEVICE_NAME_UUID)
        self.loop.create_task(self.enable_notify())
        self.callback = print_measurement

    @property
    def address(self):
        return self.client.address

    @property
    def model_number(self):
        read_device_name = self.client.read_gatt_char(self.char_device_name)
        return self.loop.run_until_complete(read_device_name).decode()

    async def setup(self):
        await self.client.connect()
        return await self.client.get_services()

    async def enable_notify(self):
        await self.client.start_notify(self.Rx, self._cache_message())

    async def disable_notify(self):
        await self.client.stop_notify(self.Rx)

    def pack_command(self, payload: bytearray):
        pld_length = len(payload).to_bytes(2, 'big')
        return CMD_HEADER + pld_length + payload + crc8(payload).to_bytes(
            1, 'big')

    def _write(self, payload: bytearray):
        async def _write_(payload):
            command = self.pack_command(payload)
            await self.client.write_gatt_char(self.Tx, command, True)

        if self.loop.is_running():
            return self.loop.create_task(_write_(payload))
        else:
            return self.loop.run_until_complete(_write_(payload))

    def set_timer(self):
        time.sleep(1 - datetime.datetime.now().microsecond / 1e6)

        d = datetime.datetime.now().timetuple()
        payload = (ID_TIMER[0], d.tm_sec, d.tm_min, d.tm_hour, d.tm_mday,
                   d.tm_mon - 1, d.tm_year - 1900, d.tm_wday)
        self._write(bytearray(payload))

    def on(self):
        self._write(ID_TURN_ON)

    def off(self):
        self._write(ID_TURN_OFF)

    def measure(self):
        self._write(ID_ENERGY_USAGE)
        interval = 1.05 - datetime.datetime.now().microsecond / 1e6
        self.loop.run_until_complete(asyncio.sleep(interval))

    def _cache_message(self):
        buffer = bytearray()

        def _cache_message_(sender: int, value: bytearray):
            nonlocal buffer
            buffer = buffer + value

            if buffer[0] == CMD_HEADER[0]:
                payload_length = int.from_bytes(buffer[1:3], 'big')
                if len(buffer[3:-1]) < payload_length:
                    return
                elif len(buffer[3:-1]) == payload_length:
                    if crc8(buffer[3:]) == 0:
                        self._classify_response(buffer)

            buffer.clear()

        return _cache_message_

    def _classify_response(self, data):
        if data[3] == ID_ENERGY_USAGE[0]:
            measurement = self.decode_measurement(data)
            self.callback(**measurement)
        else:
            pass  # to be implemented

    def decode_measurement(self, data: bytearray):
        return {
            "voltage":
            int.from_bytes(data[5:11], 'little') / (16**6),
            "current":
            int.from_bytes(data[11:17], 'little') / (32**6) * 1000,
            "wattage":
            int.from_bytes(data[17:23], 'little') / (16**6),
            "timestamp":
            datetime.datetime(1900 + data[28], data[27] + 1, *data[26:22:-1]),
        }
Beispiel #2
0
class BleakLink(BaseLink):
    def __init__(self, device="hci0", loop=None, *args, **kwargs):
        self.device = device
        self.timeout = 5
        self.loop = loop or asyncio.get_event_loop()
        self._rx_fifo = Fifo()
        self._client = None
        self._th = None

    def __enter__(self):
        self.start()
        return self

    def start(self):
        if self._th:
            return

        self._th = Thread(target=run_worker, args=(self.loop, ))
        self._th.daemon = True
        self._th.start()

    def __exit__(self, exc_type, exc_value, traceback):
        if self._client:
            self.close()

    def close(self):
        asyncio.run_coroutine_threadsafe(self._client.disconnect(),
                                         self.loop).result(10)

    def scan(self, timeout=1):
        devices = asyncio.run_coroutine_threadsafe(
            discover(timeout=timeout, device=self.device),
            self.loop).result(timeout * 3)

        # We need to keep scanning going for connect() to properly work
        asyncio.run_coroutine_threadsafe(
            discover(timeout=timeout, device=self.device), self.loop)

        return [
            (dev.name, dev.address) for dev in devices
            if dev.metadata.get('manufacturer_data', {}).get(_manuf_id, []) in
            [_manuf_data_xiaomi, _manuf_data_xiaomi_pro, _manuf_data_ninebot]
        ]

    def open(self, port):
        fut = asyncio.run_coroutine_threadsafe(self._connect(port), self.loop)
        fut.result(10)

    async def _connect(self, port):
        if isinstance(port, tuple):
            port = port[1]
        self._client = BleakClient(port, device=self.device)
        await self._client.connect()
        print("connected")
        await self._client.start_notify(_tx_char_uuid, self._data_received)
        print("services:", list(await self._client.get_services()))

    def _data_received(self, sender, data):
        self._rx_fifo.write(data)

    def write(self, data):
        fut = asyncio.run_coroutine_threadsafe(
            self._client.write_gatt_char(_rx_char_uuid, bytearray(data), True),
            self.loop,
        )
        return fut.result(3)

    def read(self, size):
        try:
            data = self._rx_fifo.read(size, timeout=self.timeout)
        except queue.Empty:
            raise LinkTimeoutException
        return data

    def fetch_keys(self):
        return asyncio.run_coroutine_threadsafe(
            self._client.read_gatt_char(_keys_char_uuid), self.loop).result(5)
class BleAioTransport:
    """
    This class encapsulates management of the BLE transport using asyncio
    and communicating with the Telemetrix4Esp32BLE server resident on an
    ESP32 board.
    """
    def __init__(self, ble_mac_address=None, loop=None, receive_callback=None):
        """

        :param ble_mac_address: User specified mac address. If not specified
                                mac address discovery will be attempted.

        :param loop: asyncio loop

        :param receive_callback: method to be called when data is received from
                                 the BLE connected server.

        """

        # make sure the user specified a handler for incoming data
        if not receive_callback:
            raise RuntimeError(
                'ble_aio_transport: A receive callback must be specified')
        else:
            self.receive_callback = receive_callback

        # mac address of device.
        # If set to None, then an attempt at autodiscovery will take place.
        self.ble_mac_address = ble_mac_address

        # loop management
        self.loop = loop

        if not self.loop:
            self.loop = asyncio.get_event_loop()

        # characteristics for the BLE UART service
        # Nordic NUS characteristic for transmit.
        self.UART_TX_UUID = "6e400002-b5a3-f393-e0a9-e50e24dcca9e"
        # Nordic NUS characteristic for receive.
        self.UART_RX_UUID = "6e400003-b5a3-f393-e0a9-e50e24dcca9e"

        # the client is what we call the BLE transport
        self.client = None

        # a variable to keep track if the transport is currently connected
        self.connected = False

    async def notification_handler(self, sender, data):
        """
        Process incoming BLE data
        Call the incoming data processing method
        :param sender: sender ID - not used
        :param data: data received
        """

        await self.receive_callback(sender, data)

    # noinspection PyTypeChecker
    async def connect(self):
        """
        This method will attempt a connection with the ble device if not already connected.

        If a ble MAC address was provided it will use that address, and if not,
        it will attempt to auto discover the device before connection

        """
        if self.connected:
            raise RuntimeError(
                'ble_aio_transport: connect - Already connected')

        # user did not specify a mac address, so we try to do auto-discovery of
        # the server's mac address.
        if not self.ble_mac_address:
            print('Retrieving BLE Mac Address of Ble Device. Please wait...')
            devices = await discover()
            for d in devices:
                if d.name == 'Telemetrix4ESP32BLE':
                    self.ble_mac_address = d.address

        # now attempt to connect
        print(f'Connecting to {self.ble_mac_address}. Please wait....')
        self.client = BleakClient(self.ble_mac_address)
        try:
            await self.client.connect()
        except bleak.exc.BleakDBusError:
            raise KeyboardInterrupt
        self.connected = True
        print('Connection successful')

        # associate the notification handler with incoming data
        await self.client.start_notify(self.UART_RX_UUID,
                                       self.notification_handler)
        # self.loop.create_task(self.ble_read())

    async def disconnect(self):
        try:
            await self.client.disconnect()
        except AttributeError:
            pass

    def ble_read(self):
        try:
            self.loop.run_until_complete(
                self.client.read_gatt_char(self.UART_RX_UUID))
        except:
            pass

    async def write(self, data):
        """
        This method writes sends data to the BLE device
        :param data: data is in the form of a bytearray

        """
        # noinspection PyBroadException
        try:
            await self.client.write_gatt_char(self.UART_TX_UUID, data)
        except Exception:
            pass

        if sys.platform.startswith('win32'):
            await asyncio.sleep(.2)