Beispiel #1
0
def terminate_handler(bulb_led, device):
    global exit_signal_received
    if isinstance(bulb_led, Bulb):
        bulb_led.stop_music()
    if isinstance(device, ble_device_type):
        device.disconnect()
    print("exit")
    exit_signal_received = True


# start

queue = Queue(maxsize=1)
#BLE connection
client = BleakClient(address_ledstrip)
client.connect()
client.write_gatt_char(IO_DATA_CHAR_UUID, bytearray([0xCC, 0x23, 0x33]))

#Yeelight Connection
bulb_led = Bulb("192.168.1.72", effect="smooth")
bulb_led.start_music(2000)

time.sleep(1)
t = threading.Thread(name="color_change_thread",
                     target=color_setter_thread,
                     args=(
                         bulb_led,
                         client,
                         queue,
                     ))
t.start()
Beispiel #2
0
class BASE_BLE_DEVICE:
    def __init__(self,
                 scan_dev,
                 init=False,
                 name=None,
                 lenbuff=100,
                 rssi=None,
                 log=None):
        # BLE
        self.ble_client = None
        if hasattr(scan_dev, 'address'):
            self.UUID = scan_dev.address
            self.name = scan_dev.name
            self.rssi = scan_dev.rssi
            self.address = self.UUID
        else:
            self.UUID = scan_dev
            self.name = name
            self.rssi = rssi
            self.address = self.UUID
        self.connected = False
        self.services = {}
        self.services_rsum = {}
        self.services_rsum_handles = {}
        self.chars_desc_rsum = {}
        self.readables = {}
        self.writeables = {}
        self.notifiables = {}
        self.readables_handles = {}
        self.writeables_handles = {}
        self.notifiables_handles = {}
        self.loop = asyncio.get_event_loop()
        # self.raw_buff_queue = asyncio.Queue()
        self.kb_cmd = None
        self.is_notifying = False
        self.cmd_finished = True
        self.len_buffer = lenbuff
        #
        self.bytes_sent = 0
        self.buff = b''
        self.raw_buff = b''
        self.prompt = b'>>> '
        self.response = ''
        self._cmdstr = ''
        self._cmdfiltered = False
        self._kbi = '\x03'
        self._banner = '\x02'
        self._reset = '\x04'
        self._traceback = b'Traceback (most recent call last):'
        self._flush = b''
        self.output = None
        self.platform = None
        self.break_flag = None
        self.log = log
        #
        if init:
            self.connect()
            # do connect

    def set_event_loop(self, loop):
        self.loop = loop
        # self.ble_client.loop = loop

    async def connect_client(self, n_tries=3, log=True):
        n = 0
        self.ble_client = BleakClient(self.UUID)
        while n < n_tries:
            try:
                await asyncio.wait_for(self.ble_client.connect(timeout=3),
                                       timeout=60)
                self.connected = await self.ble_client.is_connected()
                if self.connected:
                    self.name = self.ble_client._device_info.name()
                    if log:
                        self.log.info("Connected to: {}".format(self.UUID))
                    break
            except Exception as e:
                if log:
                    if not self.break_flag:
                        self.log.error(e)
                        self.log.info('Trying again...')
                    else:
                        break
                time.sleep(1)
                n += 1

    async def disconnect_client(self, log=True, timeout=None):
        if timeout:

            await asyncio.wait_for(self.ble_client.disconnect(),
                                   timeout=timeout)

        else:
            await self.ble_client.disconnect()
        self.connected = await self.ble_client.is_connected()
        if not self.connected:
            if log:
                self.log.info("Disconnected successfully")

    def connect(self, n_tries=3, show_servs=False, log=True):
        self.loop.run_until_complete(
            self.connect_client(n_tries=n_tries, log=log))
        self.get_services(log=show_servs)

    def is_connected(self):
        return self.loop.run_until_complete(self.ble_client.is_connected())

    def disconnect(self, log=True, timeout=None):
        self.loop.run_until_complete(
            self.disconnect_client(log=log, timeout=timeout))

    def set_disconnected_callback(self, callback):
        self.ble_client.set_disconnected_callback(callback)

    def disconnection_callback(self, client):
        self.connected = False

    # RSSI
    def get_RSSI(self):
        if hasattr(self.ble_client, 'get_rssi'):
            self.rssi = self.loop.run_until_complete(
                self.ble_client.get_rssi())
        else:
            self.rssi = 0
        return self.rssi

    # SERVICES

    def get_services(self, log=True):
        for service in self.ble_client.services:
            if log:
                print("[Service] {0}: {1}".format(service.uuid.lower(),
                                                  service.description))
            self.services[service.description] = {
                'UUID': service.uuid.lower(),
                'CHARS': {}
            }
            self.services_rsum_handles[service.description] = []

            for char in service.characteristics:
                self.services_rsum_handles[service.description].append(
                    char.handle)
                if "read" in char.properties:
                    try:
                        self.readables[char.description] = char.uuid
                        self.readables_handles[char.handle] = char.description
                    except Exception as e:
                        print(e)

                if "notify" in char.properties or 'indicate' in char.properties:
                    try:
                        self.notifiables[char.description] = char.uuid
                        self.notifiables_handles[
                            char.handle] = char.description
                    except Exception as e:
                        print(e)

                if "write" in char.properties or 'write-without-response' in char.properties:
                    try:
                        self.writeables[char.description] = char.uuid
                        self.writeables_handles[char.handle] = char.description
                    except Exception as e:
                        print(e)
                try:
                    self.services[service.description]['CHARS'][char.uuid] = {
                        char.description: ",".join(char.properties),
                        'Descriptors': {
                            descriptor.uuid: descriptor.handle
                            for descriptor in char.descriptors
                        }
                    }
                except Exception as e:
                    print(e)

                    self.chars_desc_rsum[char.description] = {}
                    for descriptor in char.descriptors:
                        self.chars_desc_rsum[char.description][
                            descriptor.description] = descriptor.handle
                if log:

                    try:
                        print(
                            "\t[Characteristic] {0}: ({1}) | Name: {2}".format(
                                char.uuid, ",".join(char.properties),
                                char.description))
                    except Exception as e:
                        print(e)

                if log:
                    for descriptor in char.descriptors:
                        print("\t\t[Descriptor] [{0}]: {1} (Handle: {2}) ".
                              format(descriptor.uuid, descriptor.description,
                                     descriptor.handle))
        self.services_rsum = {
            key: [
                list(list(val['CHARS'].values())[i].keys())[0]
                for i in range(len(list(val['CHARS'].values())))
            ]
            for key, val in self.services.items()
        }

    # WRITE/READ SERVICES

    def fmt_data(self, data, CR=True):
        if sys.platform == 'linux':
            if CR:
                return bytearray(data + '\r', 'utf-8')
            else:
                return bytearray(data, 'utf-8')
        else:
            if CR:
                return bytes(data + '\r', 'utf-8')
            else:
                return bytes(data, 'utf-8')

    async def as_read_descriptor(self, handle):
        return bytes(await self.ble_client.read_gatt_descriptor(handle))

    def read_descriptor_raw(self, key=None, char=None):
        if key is not None:
            # print(self.chars_desc_rsum[char])
            if key in list(self.chars_desc_rsum[char]):
                data = self.loop.run_until_complete(
                    self.as_read_descriptor(self.chars_desc_rsum[char][key]))
                return data
            else:
                print('Descriptor not available for this characteristic')

    def read_descriptor(self, key=None, char=None, data_fmt="utf8"):
        try:
            if data_fmt == 'utf8':
                data = self.read_descriptor_raw(key=key,
                                                char=char).decode('utf8')
                return data
            else:
                data, = struct.unpack(data_fmt,
                                      self.read_char_raw(key=key, char=char))
                return data
        except Exception as e:
            print(e)

    async def as_read_char(self, uuid):
        return bytes(await self.ble_client.read_gatt_char(uuid))

    def read_char_raw(self, key=None, uuid=None, handle=None):
        if key is not None:
            if key in list(self.readables.keys()):
                if handle:
                    data = self.loop.run_until_complete(
                        self.as_read_char(handle))
                else:
                    data = self.loop.run_until_complete(
                        self.as_read_char(self.readables[key]))
                return data
            else:
                print('Characteristic not readable')

        else:
            if uuid is not None:
                if uuid in list(self.readables.values()):
                    if handle:
                        data = self.loop.run_until_complete(
                            self.as_read_char(handle))
                    else:
                        data = self.loop.run_until_complete(
                            self.as_read_char(uuid))
                    return data
                else:
                    print('Characteristic not readable')

    def read_char(self, key=None, uuid=None, data_fmt="<h", handle=None):
        try:
            if data_fmt == 'utf8':  # Here function that handles format and unpack properly
                data = self.read_char_raw(key=key, uuid=uuid,
                                          handle=handle).decode('utf8')
                return data
            else:
                if data_fmt == 'raw':
                    data = self.read_char_raw(key=key,
                                              uuid=uuid,
                                              handle=handle)
                    return data
                else:
                    data, = struct.unpack(
                        data_fmt,
                        self.read_char_raw(key=key, uuid=uuid, handle=handle))
                return data
        except Exception as e:
            print(e)

    async def as_write_char(self, uuid, data):
        await self.ble_client.write_gatt_char(uuid, data)

    def write_char(self, key=None, uuid=None, data=None, handle=None):
        if key is not None:
            if key in list(self.writeables.keys()):
                if handle:
                    data = self.loop.run_until_complete(
                        self.as_write_char(handle, data))
                else:
                    data = self.loop.run_until_complete(
                        self.as_write_char(self.writeables[key],
                                           data))  # make fmt_data
                return data
            else:
                print('Characteristic not writeable')

        else:
            if uuid is not None:
                if uuid in list(self.writeables.values()):
                    if handle:
                        data = self.loop.run_until_complete(
                            self.as_write_char(handle, data))
                    else:
                        data = self.loop.run_until_complete(
                            self.as_write_char(uuid, data))  # make fmt_data
                    return data
                else:
                    print('Characteristic not writeable')

    def write_char_raw(self, key=None, uuid=None, data=None):
        if key is not None:
            if key in list(self.writeables.keys()):
                data = self.loop.run_until_complete(
                    self.as_write_char(self.writeables[key],
                                       self.fmt_data(
                                           data, CR=False)))  # make fmt_data
                return data
            else:
                print('Characteristic not writeable')

        else:
            if uuid is not None:
                if uuid in list(self.writeables.values()):
                    data = self.loop.run_until_complete(
                        self.as_write_char(uuid, self.fmt_data(
                            data, CR=False)))  # make fmt_data
                    return data
                else:
                    print('Characteristic not writeable')

    def read_callback(self, sender, data):
        self.raw_buff += data

    def read_callback_follow(self, sender, data):
        try:
            if not self._cmdfiltered:
                cmd_filt = bytes(self._cmdstr + '\r\n', 'utf-8')
                data = b'' + data
                data = data.replace(cmd_filt, b'', 1)
                # data = data.replace(b'\r\n>>> ', b'')
                self._cmdfiltered = True
            else:
                try:
                    data = b'' + data
                    # data = data.replace(b'\r\n>>> ', b'')
                except Exception as e:
                    pass
            self.raw_buff += data
            if self.prompt in data:
                data = data.replace(b'\r',
                                    b'').replace(b'\r\n>>> ', b'').replace(
                                        b'>>> ',
                                        b'').decode('utf-8', 'ignore')
                if data != '':
                    print(data, end='')
            else:
                data = data.replace(b'\r',
                                    b'').replace(b'\r\n>>> ', b'').replace(
                                        b'>>> ',
                                        b'').decode('utf-8', 'ignore')
                print(data, end='')
        except KeyboardInterrupt:
            print('CALLBACK_KBI')
            pass
        #

    async def as_write_read_waitp(self, data, rtn_buff=False):
        await self.ble_client.start_notify(self.readables['TX'],
                                           self.read_callback)
        if len(data) > self.len_buffer:
            for i in range(0, len(data), self.len_buffer):
                await self.ble_client.write_gatt_char(
                    self.writeables['RX'], data[i:i + self.len_buffer])

        else:
            await self.ble_client.write_gatt_char(self.writeables['RX'], data)
        while self.prompt not in self.raw_buff:
            await asyncio.sleep(0.01, loop=self.loop)
        await self.ble_client.stop_notify(self.readables['TX'])
        if rtn_buff:
            return self.raw_buff

    async def as_write_read_follow(self, data, rtn_buff=False):
        if not self.is_notifying:
            try:
                await self.ble_client.start_notify(self.readables['TX'],
                                                   self.read_callback_follow)
                self.is_notifying = True
            except Exception as e:
                pass
        if len(data) > self.len_buffer:
            for i in range(0, len(data), self.len_buffer):
                await self.ble_client.write_gatt_char(
                    self.writeables['RX'], data[i:i + self.len_buffer])
        else:
            await self.ble_client.write_gatt_char(self.writeables['RX'], data)
        while self.prompt not in self.raw_buff:
            try:
                await asyncio.sleep(0.01, loop=self.loop)
            except KeyboardInterrupt:
                print('Catch here1')
                data = bytes(self._kbi, 'utf-8')
                await self.ble_client.write_gatt_char(self.writeables['RX'],
                                                      data)
        if self.is_notifying:
            try:
                await self.ble_client.stop_notify(self.readables['TX'])
                self.is_notifying = False
            except Exception as e:
                pass
        self._cmdfiltered = False
        if rtn_buff:
            return self.raw_buff

    def write_read(self, data='', follow=False, kb=False):
        if not follow:
            if not kb:
                try:
                    self.loop.run_until_complete(
                        self.as_write_read_waitp(data))
                except Exception as e:
                    print(e)
            else:
                asyncio.ensure_future(self.as_write_read_waitp(data),
                                      loop=self.loop)
                # wait here until there is raw_buff

        else:
            if not kb:
                try:
                    self.loop.run_until_complete(
                        self.as_write_read_follow(data))
                except Exception as e:
                    print('Catch here0')
                    print(e)
            else:

                asyncio.ensure_future(self.as_write_read_follow(data,
                                                                rtn_buff=True),
                                      loop=self.loop)

    def send_recv_cmd(self, cmd, follow=False, kb=False):
        data = self.fmt_data(cmd)  # make fmt_data
        n_bytes = len(data)
        self.write_read(data=data, follow=follow, kb=kb)
        return n_bytes

    def write(self, cmd):
        data = self.fmt_data(cmd, CR=False)  # make fmt_data
        n_bytes = len(data)
        self.write_char_raw(key='RX', data=data)
        return n_bytes

    def read_all(self):
        try:
            return self.raw_buff
        except Exception as e:
            print(e)
            return self.raw_buff

    def flush(self):
        flushed = 0
        self.buff = self.read_all()
        flushed += 1
        self.buff = b''

    def wr_cmd(self,
               cmd,
               silent=False,
               rtn=True,
               rtn_resp=False,
               long_string=False,
               follow=False,
               kb=False):
        self.output = None
        self.response = ''
        self.raw_buff = b''
        self.buff = b''
        self._cmdstr = cmd
        # self.flush()
        self.bytes_sent = self.send_recv_cmd(cmd, follow=follow,
                                             kb=kb)  # make fmt_datas
        # time.sleep(0.1)
        # self.buff = self.read_all()[self.bytes_sent:]
        self.buff = self.read_all()
        if self.buff == b'':
            # time.sleep(0.1)
            self.buff = self.read_all()
        # print(self.buff)
        # filter command
        if follow:
            silent = True
        cmd_filt = bytes(cmd + '\r\n', 'utf-8')
        self.buff = self.buff.replace(cmd_filt, b'', 1)
        if self._traceback in self.buff:
            long_string = True
        if long_string:
            self.response = self.buff.replace(b'\r', b'').replace(
                b'\r\n>>> ', b'').replace(b'>>> ',
                                          b'').decode('utf-8', 'ignore')
        else:
            self.response = self.buff.replace(b'\r\n', b'').replace(
                b'\r\n>>> ', b'').replace(b'>>> ',
                                          b'').decode('utf-8', 'ignore')
        if not silent:
            if self.response != '\n' and self.response != '':
                print(self.response)
            else:
                self.response = ''
        if rtn:
            self.get_output()
            if self.output == '\n' and self.output == '':
                self.output = None
            if self.output is None:
                if self.response != '' and self.response != '\n':
                    self.output = self.response
        if rtn_resp:
            return self.output

    async def as_wr_cmd(self,
                        cmd,
                        silent=False,
                        rtn=True,
                        rtn_resp=False,
                        long_string=False,
                        follow=False,
                        kb=False):
        self.output = None
        self.response = ''
        self.raw_buff = b''
        self.buff = b''
        self._cmdstr = cmd
        self.cmd_finished = False
        # self.flush()
        data = self.fmt_data(cmd)  # make fmt_data
        n_bytes = len(data)
        self.bytes_sent = n_bytes
        # time.sleep(0.1)
        # self.buff = self.read_all()[self.bytes_sent:]
        if follow:
            self.buff = await self.as_write_read_follow(data, rtn_buff=True)
        else:
            self.buff = await self.as_write_read_waitp(data, rtn_buff=True)
        if self.buff == b'':
            # time.sleep(0.1)
            self.buff = self.read_all()
        # print(self.buff)
        # filter command
        if follow:
            silent = True
        cmd_filt = bytes(cmd + '\r\n', 'utf-8')
        self.buff = self.buff.replace(cmd_filt, b'', 1)
        if self._traceback in self.buff:
            long_string = True
        if long_string:
            self.response = self.buff.replace(b'\r', b'').replace(
                b'\r\n>>> ', b'').replace(b'>>> ',
                                          b'').decode('utf-8', 'ignore')
        else:
            self.response = self.buff.replace(b'\r\n', b'').replace(
                b'\r\n>>> ', b'').replace(b'>>> ',
                                          b'').decode('utf-8', 'ignore')
        if not silent:
            if self.response != '\n' and self.response != '':
                print(self.response)
            else:
                self.response = ''
        if rtn:
            self.get_output()
            if self.output == '\n' and self.output == '':
                self.output = None
            if self.output is None:
                if self.response != '' and self.response != '\n':
                    self.output = self.response
        self.cmd_finished = True
        if rtn_resp:
            return self.output

    def kbi(self, silent=True, pipe=None):
        if pipe is not None:
            self.wr_cmd(self._kbi, silent=silent)
            bf_output = self.response.split('Traceback')[0]
            traceback = 'Traceback' + self.response.split('Traceback')[1]
            if bf_output != '' and bf_output != '\n':
                pipe(bf_output)
            pipe(traceback, std='stderr')
        else:
            self.wr_cmd(self._kbi, silent=silent)

    async def as_kbi(self):
        for i in range(1):
            print('This is buff: {}'.format(self.raw_buff))
            await asyncio.sleep(1, loop=self.loop)
            data = bytes(self._kbi + '\r', 'utf-8')
            await self.ble_client.write_gatt_char(self.writeables['RX'], data)

    def banner(self, pipe=None, kb=False, follow=False):
        self.wr_cmd(self._banner,
                    silent=True,
                    long_string=True,
                    kb=kb,
                    follow=follow)
        if pipe is None and not follow:
            print(self.response.replace('\n\n', '\n'))
        else:
            if pipe:
                pipe(self.response.replace('\n\n', '\n'))

    def reset(self, silent=True):
        if not silent:
            print('Rebooting device...')
        self.write_char_raw(key='RX', data=self._reset)
        if not silent:
            print('Done!')

    async def as_reset(self, silent=True):
        if not silent:
            print('Rebooting device...')
        await self.as_write_char(self.writeables['RX'],
                                 bytes(self._reset, 'utf-8'))
        if not silent:
            print('Done!')
        return None

    def get_output(self):
        try:
            self.output = ast.literal_eval(self.response)
        except Exception as e:
            if 'bytearray' in self.response:
                try:
                    self.output = bytearray(
                        ast.literal_eval(
                            self.response.strip().split('bytearray')[1]))
                except Exception as e:
                    pass
            else:
                if 'array' in self.response:
                    try:
                        arr = ast.literal_eval(
                            self.response.strip().split('array')[1])
                        self.output = array(arr[0], arr[1])
                    except Exception as e:
                        pass
            pass