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()
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