class RileyLink(PacketRadio): def __init__(self): self.peripheral = None self.pa_level_index = PA_LEVELS.index(0x84) self.data_handle = None self.logger = getLogger() self.address = None if os.path.exists(RILEYLINK_MAC_FILE): with open(RILEYLINK_MAC_FILE, "r") as stream: self.address = stream.read() self.service = None self.response_handle = None self.notify_event = Event() self.initialized = False def connect(self, force_initialize=False): try: if self.address is None: self.address = self._findRileyLink() if self.peripheral is None: self.peripheral = Peripheral() try: state = self.peripheral.getState() if state == "conn": return except BTLEException: pass self._connect_retry(3) self.service = self.peripheral.getServiceByUUID(RILEYLINK_SERVICE_UUID) data_char = self.service.getCharacteristics(RILEYLINK_DATA_CHAR_UUID)[0] self.data_handle = data_char.getHandle() char_response = self.service.getCharacteristics(RILEYLINK_RESPONSE_CHAR_UUID)[0] self.response_handle = char_response.getHandle() response_notify_handle = self.response_handle + 1 notify_setup = b"\x01\x00" self.peripheral.writeCharacteristic(response_notify_handle, notify_setup) while self.peripheral.waitForNotifications(0.05): self.peripheral.readCharacteristic(self.data_handle) if self.initialized: self.init_radio(force_initialize) else: self.init_radio(True) except BTLEException: if self.peripheral is not None: self.disconnect() raise def disconnect(self, ignore_errors=True): try: if self.peripheral is None: self.logger.info("Already disconnected") return self.logger.info("Disconnecting..") if self.response_handle is not None: response_notify_handle = self.response_handle + 1 notify_setup = b"\x00\x00" self.peripheral.writeCharacteristic(response_notify_handle, notify_setup) except BTLEException: if not ignore_errors: raise finally: try: if self.peripheral is not None: self.peripheral.disconnect() self.peripheral = None except BTLEException: if ignore_errors: self.logger.exception("Ignoring btle exception during disconnect") else: raise def get_info(self): try: self.connect() bs = self.peripheral.getServiceByUUID(XGATT_BATTERYSERVICE_UUID) bc = bs.getCharacteristics(XGATT_BATTERY_CHAR_UUID)[0] bch = bc.getHandle() battery_value = int(self.peripheral.readCharacteristic(bch)[0]) self.logger.debug("Battery level read: %d", battery_value) version, v_major, v_minor = self._read_version() return { "battery_level": battery_value, "mac_address": self.address, "version_string": version, "version_major": v_major, "version_minor": v_minor } except BTLEException as btlee: raise PacketRadioError("Error communicating with RileyLink") from btlee finally: self.disconnect() def _read_version(self): version = None try: if os.path.exists(RILEYLINK_VERSION_FILE): with open(RILEYLINK_VERSION_FILE, "r") as stream: version = stream.read() else: response = self._command(Command.GET_VERSION) if response is not None and len(response) > 0: version = response.decode("ascii") self.logger.debug("RL reports version string: %s" % version) try: with open(RILEYLINK_VERSION_FILE, "w") as stream: stream.write(version) except IOError: self.logger.exception("Failed to store version in file") if version is None: return "0.0", 0, 0 try: m = re.search(".+([0-9]+)\\.([0-9]+)", version) if m is None: raise PacketRadioError("Failed to parse firmware version string: %s" % version) v_major = int(m.group(1)) v_minor = int(m.group(2)) self.logger.debug("Interpreted version major: %d minor: %d" % (v_major, v_minor)) return version, v_major, v_minor except Exception as ex: raise PacketRadioError("Failed to parse firmware version string: %s" % version) from ex except IOError: self.logger.exception("Error reading version file") except PacketRadioError: raise response = self._command(Command.GET_VERSION) if response is not None and len(response) > 0: version = response.decode("ascii") self.logger.debug("RL reports version string: %s" % version) try: m = re.search(".+([0-9]+)\\.([0-9]+)", version) if m is None: raise PacketRadioError("Failed to parse firmware version string: %s" % version) v_major = int(m.group(1)) v_minor = int(m.group(2)) self.logger.debug("Interpreted version major: %d minor: %d" % (v_major, v_minor)) return (version, v_major, v_minor) except PacketRadioError: raise except Exception as ex: raise PacketRadioError("Failed to parse firmware version string: %s" % version) from ex def init_radio(self, force_init=False): try: version, v_major, v_minor = self._read_version() if v_major < 2: self.logger.error("Firmware version is below 2.0") raise PacketRadioError("Unsupported RileyLink firmware %d.%d (%s)" % (v_major, v_minor, version)) if not force_init: if v_major == 2 and v_minor < 3: response = self._command(Command.READ_REGISTER, bytes([Register.SYNC1, 0x00])) else: response = self._command(Command.READ_REGISTER, bytes([Register.SYNC1])) if response is not None and len(response) > 0 and response[0] == 0xA5: return self._command(Command.RADIO_RESET_CONFIG) self._command(Command.SET_SW_ENCODING, bytes([Encoding.MANCHESTER])) frequency = int(433910000 / (24000000 / pow(2, 16))) self._command(Command.SET_PREAMBLE, bytes([0x66, 0x65])) self._command(Command.UPDATE_REGISTER, bytes([Register.FREQ0, frequency & 0xff])) self._command(Command.UPDATE_REGISTER, bytes([Register.FREQ1, (frequency >> 8) & 0xff])) self._command(Command.UPDATE_REGISTER, bytes([Register.FREQ2, (frequency >> 16) & 0xff])) self._command(Command.UPDATE_REGISTER, bytes([Register.PKTCTRL1, 0x20])) self._command(Command.UPDATE_REGISTER, bytes([Register.PKTCTRL0, 0x00])) self._command(Command.UPDATE_REGISTER, bytes([Register.FSCTRL1, 0x06])) self._command(Command.UPDATE_REGISTER, bytes([Register.MDMCFG4, 0xCA])) self._command(Command.UPDATE_REGISTER, bytes([Register.MDMCFG3, 0xBC])) self._command(Command.UPDATE_REGISTER, bytes([Register.MDMCFG2, 0x06])) self._command(Command.UPDATE_REGISTER, bytes([Register.MDMCFG1, 0x70])) self._command(Command.UPDATE_REGISTER, bytes([Register.MDMCFG0, 0x11])) self._command(Command.UPDATE_REGISTER, bytes([Register.DEVIATN, 0x44])) self._command(Command.UPDATE_REGISTER, bytes([Register.MCSM0, 0x18])) self._command(Command.UPDATE_REGISTER, bytes([Register.FOCCFG, 0x17])) self._command(Command.UPDATE_REGISTER, bytes([Register.FSCAL3, 0xE9])) self._command(Command.UPDATE_REGISTER, bytes([Register.FSCAL2, 0x2A])) self._command(Command.UPDATE_REGISTER, bytes([Register.FSCAL1, 0x00])) self._command(Command.UPDATE_REGISTER, bytes([Register.FSCAL0, 0x1F])) self._command(Command.UPDATE_REGISTER, bytes([Register.TEST1, 35])) self._command(Command.UPDATE_REGISTER, bytes([Register.TEST0, 0x09])) self._command(Command.UPDATE_REGISTER, bytes([Register.PATABLE0, PA_LEVELS[self.pa_level_index]])) self._command(Command.UPDATE_REGISTER, bytes([Register.FREND0, 0x00])) self._command(Command.UPDATE_REGISTER, bytes([Register.SYNC1, 0xA5])) self._command(Command.UPDATE_REGISTER, bytes([Register.SYNC0, 0x5A])) response = self._command(Command.GET_STATE) if response != b"OK": raise PacketRadioError("Rileylink state is not OK. Response returned: %s" % response) self.initialized = True except PacketRadioError as rle: self.logger.error("Error while initializing rileylink radio: %s", rle) raise def tx_up(self): if self.pa_level_index < len(PA_LEVELS) - 1: self.pa_level_index += 1 self._set_amp(self.pa_level_index) def tx_down(self): if self.pa_level_index > 0: self.pa_level_index -= 1 self._set_amp(self.pa_level_index) def set_tx_power(self, tx_power): if tx_power is None: return elif tx_power == TxPower.Lowest: self._set_amp(0) elif tx_power == TxPower.Low: self._set_amp(PA_LEVELS.index(0x12)) elif tx_power == TxPower.Normal: self._set_amp(PA_LEVELS.index(0x60)) elif tx_power == TxPower.High: self._set_amp(PA_LEVELS.index(0xC8)) elif tx_power == TxPower.Highest: self._set_amp(PA_LEVELS.index(0xC0)) def get_packet(self, timeout=5.0): try: self.connect() return self._command(Command.GET_PACKET, struct.pack(">BL", 0, int(timeout * 1000)), timeout=float(timeout)+0.5) except PacketRadioError as rle: self.logger.error("Error while receiving data: %s", rle) raise def send_and_receive_packet(self, packet, repeat_count, delay_ms, timeout_ms, retry_count, preamble_ext_ms): try: self.connect() return self._command(Command.SEND_AND_LISTEN, struct.pack(">BBHBLBH", 0, repeat_count, delay_ms, 0, timeout_ms, retry_count, preamble_ext_ms) + packet, timeout=30) except PacketRadioError as rle: self.logger.error("Error while sending and receiving data: %s", rle) raise def send_packet(self, packet, repeat_count, delay_ms, preamble_extension_ms): try: self.connect() result = self._command(Command.SEND_PACKET, struct.pack(">BBHH", 0, repeat_count, delay_ms, preamble_extension_ms) + packet, timeout=30) return result except PacketRadioError as rle: self.logger.error("Error while sending data: %s", rle) raise def _set_amp(self, index=None): try: self.connect() if index is not None: self.pa_level_index = index self._command(Command.UPDATE_REGISTER, bytes([Register.PATABLE0, PA_LEVELS[self.pa_level_index]])) self.logger.debug("Setting pa level to index %d of %d" % (self.pa_level_index, len(PA_LEVELS))) except PacketRadioError: self.logger.exception("Error while setting tx amplification") raise def _findRileyLink(self): scanner = Scanner() found = None self.logger.debug("Scanning for RileyLink") retries = 10 while found is None and retries > 0: retries -= 1 for result in scanner.scan(1.0): if result.getValueText(7) == RILEYLINK_SERVICE_UUID: self.logger.debug("Found RileyLink") found = result.addr try: with open(RILEYLINK_MAC_FILE, "w") as stream: stream.write(result.addr) except IOError: self.logger.warning("Cannot store rileylink mac radio_address for later") break if found is None: raise PacketRadioError("Could not find RileyLink") return found def _connect_retry(self, retries): while retries > 0: retries -= 1 self.logger.info("Connecting to RileyLink, retries left: %d" % retries) try: self.peripheral.connect(self.address) self.logger.info("Connected") break except BTLEException as btlee: self.logger.warning("BTLE exception trying to connect: %s" % btlee) try: p = subprocess.Popen(["ps", "-A"], stdout=subprocess.PIPE) out, err = p.communicate() for line in out.splitlines(): if "bluepy-helper" in line: pid = int(line.split(None, 1)[0]) os.kill(pid, 9) break except: self.logger.warning("Failed to kill bluepy-helper") time.sleep(1) def _command(self, command_type, command_data=None, timeout=10.0): try: if command_data is None: data = bytes([1, command_type]) else: data = bytes([len(command_data) + 1, command_type]) + command_data self.peripheral.writeCharacteristic(self.data_handle, data, withResponse=True) if not self.peripheral.waitForNotifications(timeout): raise PacketRadioError("Timed out while waiting for a response from RileyLink") response = self.peripheral.readCharacteristic(self.data_handle) if response is None or len(response) == 0: raise PacketRadioError("RileyLink returned no response") else: if response[0] == Response.COMMAND_SUCCESS: return response[1:] elif response[0] == Response.COMMAND_INTERRUPTED: self.logger.warning("A previous command was interrupted") return response[1:] elif response[0] == Response.RX_TIMEOUT: return None else: raise PacketRadioError("RileyLink returned error code: %02X. Additional response data: %s" % (response[0], response[1:]), response[0]) except Exception as e: raise PacketRadioError("Error executing command") from e
def _try_connect(self): log.info('Attempting to {}connect to Scooter: {}'.format('indefinitely ' if self._auto_reconnect else '', self.mac_address)) while True: try: Peripheral.connect(self, self.mac_address, addrType=ADDR_TYPE_RANDOM) log.info('Successfully connected to Scooter: ' + self.mac_address) if self._connected_callback: self._connected_callback(self) # Attach delegate self.withDelegate(M365Delegate(self)) # Turn on notifications, otherwise there won't be any notification self.writeCharacteristic(0xc, b'\x01\x00', True) self.writeCharacteristic(0x12, b'\x01\x00', True) self._all_characteristics = self.getCharacteristics() self._tx_char = M365._find_characteristic(M365.TX_CHARACTERISTIC, self._all_characteristics) self._rx_char = M365._find_characteristic(M365.RX_CHARACTERISTIC, self._all_characteristics) break except Exception as e: if self._auto_reconnect == True: log.warning('{}, retrying'.format(e)) else: raise e
def connect(self, address): Peripheral.connect(self, address) if self.__get_char(): self.__init_seq() self.update() return True else: self.disconnect() return False
class TraiterDevBLE: def __init__(self, dev): self.dev = dev self.peripheral = None def afficher_info_dev(self): print("Device : %s" % str(dev)) for (adtype, desc, value) in dev.getScanData(): print("Adtype %s" % dev.getDescription(adtype)) print(" %s = %s" % (desc, value)) def connecter(self, addr=None): # self.peripheral = Peripheral.connect(self.dev.addr) self.peripheral = Peripheral() self.peripheral.connect(addr) def afficher_services(self): services = self.peripheral.services for service in services: print("Service : %s" % str(service)) for characteristic in service.getCharacteristics(): print(" Characteristic %s : %s" % (characteristic.uuid.getCommonName(), characteristic.propertiesToString())) def ecrire(self, contenu: str): service = self.peripheral.getServiceByUUID( '6e400001-b5a3-f393-e0a9-e50e24dcca9e') write_charact = service.getCharacteristics( '6e400002-b5a3-f393-e0a9-e50e24dcca9e')[0] write_charact.write(contenu.encode('utf-8'), withResponse=True) def lire(self): service = self.peripheral.getServiceByUUID( '6e400001-b5a3-f393-e0a9-e50e24dcca9e') read_charact = service.getCharacteristics( '6e400004-b5a3-f393-e0a9-e50e24dcca9e')[0] contenu = read_charact.read() print("Lecture du contenu : %s" % contenu.decode('utf-8')) def ecouter(self): self.peripheral.withDelegate(ListenDelegate(dict())) service = self.peripheral.getServiceByUUID( '6e400001-b5a3-f393-e0a9-e50e24dcca9e') notify_charact = service.getCharacteristics( '6e400003-b5a3-f393-e0a9-e50e24dcca9e')[0] # notify_charact.StartNotify() while True: if self.peripheral.waitForNotifications(5.0): print("Ye!!!") else: print("Rien recu")
def run(self): #Creation d'un objet Peripheral p = Peripheral() try: #Connection au peripherique en passant son adresse en parametre p.connect(self.adresse) except: #Si erreur (MARCHE MAL) print "Erreur co" subprocess.Popen(['sudo', 'hcitool', 'ledc','64']) subprocess.Popen(['sudo', 'hcitool', 'ledc','65']) time.sleep(2) p.connect(self.adresse) print("Connect devices : " + str(self.adresse)) #Retourne la liste des characteristiques du peripherique chrs = p.getCharacteristics() #Parcours de la liste for chr in chrs: #print chr.getHandle() #Si le handle correspond a 18 on envoie les données au peripherique #18 Correspond au handle permettantde dialoguer avec le HM10 (WRITE,READ,NOTIF) if chr.getHandle() == 18: while True: #Recuperation des données via une Base de données sqlite3 """Toutes les secondes (time.sleep(1)) On recupere la DERNIERE ligne de la table MONTRE qui correspond a la derniere valeur que la montre a envoyée et que l'on a inseré dans la bases de données time.sleep permet ici de regler la frequence d'envoi des données aux devices de visualisation """ conn = sqlite3.connect('DatabaseIOT.db') cursor = conn.cursor() cursor.execute(""" SELECT BPM, RRINTERVAL FROM MONTREWITHTIME WHERE ID = (SELECT MAX(ID) FROM MONTREWITHTIME);""") conn.commit() watchDataSQLite = cursor.fetchone() print(str(watchDataSQLite)) conn.close() #Boucle infinie ou l'on envoie en boucle les données chr.write(str(watchDataSQLite)) print "Data Send on " + str(self.adresse) time.sleep(1) """On peut raccourcir le code en utilisant la methode writeCharacteristic()
class DeskManager: MAX_CONNECTION_ATTEMPTS = 5 def __init__(self): self._scanner = Scanner() self._peripheral = Peripheral() self._desk = None self.config = Config() def scan_devices(self) -> [ScanEntry]: scan_entries = self._scanner.scan() return [ entry for entry in scan_entries ] def disconnect(self): self._peripheral.disconnect() def connect(self, device_addr): connected = False error = None for _ in range(self.MAX_CONNECTION_ATTEMPTS): try: self._peripheral.connect(device_addr, ADDR_TYPE_RANDOM) except BTLEException as e: print("Connection failed: " + str(e)) sleep(0.3) error = e continue else: connected = True break if connected is False: raise error self._desk = factory(self._peripheral, device_addr) if self._desk is None: raise Exception("Device is not supported") @property def is_connected(self): return self.desk.position is not None if self.desk is not None else False @property def desk(self) -> DefaultDesk: return self._desk
def tracker_connect(addr): try: # try to connect dev = Peripheral() dev.connect(addr) print("coneccted") # Once connected enable notification handling and loop through dev.setDelegate(ScanDelegate()) while True: dev.waitForNotifications(10) dev.disconnect() print("Disconnected!") # if fail, go back to scanning, or if diconnects except Exception as e: print(e)
def check_connection(): """Check whether connection with BLE is normal. Reconnect it if disconnected""" global connection connected = False changed = False count = 0 while not connected: log_info = time.ctime()[4:-5] + ": disconnected,reconnecting." logger.info(log_info) if count > 10: log_info = time.ctime()[4:-5] + ": connect JDY_16 fail." logger.info(log_info) break connected = True try: connection.writeCharacteristic(handle, bytes.fromhex('aa0001')) except BTLEDisconnectError or BTLEException: changed = True log_info = time.ctime()[4:-5] + ": BLE not connected." logger.info(log_info) connected = False connection = Peripheral(JDY_MAC) time.sleep(1) count += 1 except BTLEInternalError: changed = True log_info = time.ctime()[4:-5] + ": BLE not connected." logger.info(log_info) connected = False connection.connect(JDY_MAC) time.sleep(1) count += 1 time.sleep(0.05) if changed: log_info = time.ctime()[4:-5] + ": connected. Reset car." logger.info(log_info) reset_car()
(0x0C4C0000 + 0x3001)) OMRON_SENSOR_SERVICE_UUID = UUID('%08X-7700-46F4-AA96-D5E974E32A54' % (0x0C4C0000 + (0xFFF0 & 0x3000))) parser = argparse.ArgumentParser(description='OMRONの環境センサーからLatestDataを取得します') parser.add_argument("--addr", required=True, type=str, help='環境センサーのMACアドレスを指定する') args = parser.parse_args() # 環境センサーに接続する ble_peripheral = Peripheral() print(f"connecting... {args.addr}") ble_peripheral.connect(addr=args.addr, addrType="random") print(f"ble_peripheral={ble_peripheral}") # BLE サービスを取得 service = ble_peripheral.getServiceByUUID(uuidVal=OMRON_SENSOR_SERVICE_UUID) print(f"service = {service}") # BLE Characteristicsを取得 ble_char = service.getCharacteristics(forUUID=OMRON_LATEST_DATA_UUID)[0] print(f"ble_char = {ble_char}") # LatestDataから測定データの読み出し raw_data = ble_char.read() print(f"raw_data = {raw_data}") # 生の測定データを変換
class RileyLink(PacketRadio): def __init__(self): self.peripheral = None self.pa_level_index = PA_LEVELS.index(0x84) self.data_handle = None self.logger = getLogger() self.packet_logger = get_packet_logger() self.address = g_rl_address self.service = None self.response_handle = None self.notify_event = Event() self.initialized = False self.manchester = ManchesterCodec() def connect(self, force_initialize=False): try: if self.address is None: self.address = self._findRileyLink() if self.peripheral is None: self.peripheral = Peripheral() try: state = self.peripheral.getState() if state == "conn": return except BTLEException: pass self._connect_retry(3) self.service = self.peripheral.getServiceByUUID( RILEYLINK_SERVICE_UUID) data_char = self.service.getCharacteristics( RILEYLINK_DATA_CHAR_UUID)[0] self.data_handle = data_char.getHandle() char_response = self.service.getCharacteristics( RILEYLINK_RESPONSE_CHAR_UUID)[0] self.response_handle = char_response.getHandle() response_notify_handle = self.response_handle + 1 notify_setup = b"\x01\x00" self.peripheral.writeCharacteristic(response_notify_handle, notify_setup) while self.peripheral.waitForNotifications(0.05): self.peripheral.readCharacteristic(self.data_handle) if self.initialized: self.init_radio(force_initialize) else: self.init_radio(True) except BTLEException as be: if self.peripheral is not None: self.disconnect() raise PacketRadioError("Error while connecting") from be except Exception as e: raise PacketRadioError("Error while connecting") from e def disconnect(self, ignore_errors=True): try: if self.peripheral is None: self.logger.info("Already disconnected") return self.logger.info("Disconnecting..") if self.response_handle is not None: response_notify_handle = self.response_handle + 1 notify_setup = b"\x00\x00" self.peripheral.writeCharacteristic(response_notify_handle, notify_setup) except Exception as e: if not ignore_errors: raise PacketRadioError("Error while disconnecting") from e finally: try: if self.peripheral is not None: self.peripheral.disconnect() self.peripheral = None except BTLEException as be: if ignore_errors: self.logger.exception( "Ignoring btle exception during disconnect") else: raise PacketRadioError("Error while disconnecting") from be except Exception as e: raise PacketRadioError("Error while disconnecting") from e def get_info(self): try: self.connect() bs = self.peripheral.getServiceByUUID(XGATT_BATTERYSERVICE_UUID) bc = bs.getCharacteristics(XGATT_BATTERY_CHAR_UUID)[0] bch = bc.getHandle() battery_value = int(self.peripheral.readCharacteristic(bch)[0]) self.logger.debug("Battery level read: %d", battery_value) version, v_major, v_minor = self._read_version() return { "battery_level": battery_value, "mac_address": self.address, "version_string": version, "version_major": v_major, "version_minor": v_minor } except Exception as e: raise PacketRadioError("Error communicating with RileyLink") from e finally: self.disconnect() def _read_version(self): global g_rl_version, g_rl_v_major, g_rl_v_minor version = None try: if g_rl_version is not None: return g_rl_version, g_rl_v_major, g_rl_v_minor else: response = self._command(Command.GET_VERSION) if response is not None and len(response) > 0: version = response.decode("ascii") self.logger.debug("RL reports version string: %s" % version) g_rl_version = version if version is None: return "0.0", 0, 0 try: m = re.search(".+([0-9]+)\\.([0-9]+)", version) if m is None: raise PacketRadioError( "Failed to parse firmware version string: %s" % version) g_rl_v_major = int(m.group(1)) g_rl_v_minor = int(m.group(2)) self.logger.debug("Interpreted version major: %d minor: %d" % (g_rl_v_major, g_rl_v_minor)) return g_rl_version, g_rl_v_major, g_rl_v_minor except Exception as ex: raise PacketRadioError( "Failed to parse firmware version string: %s" % version) from ex except PacketRadioError: raise except Exception as e: raise PacketRadioError("Error while reading version") from e def init_radio(self, force_init=False): try: version, v_major, v_minor = self._read_version() if v_major < 2: self.logger.error("Firmware version is below 2.0") raise PacketRadioError( "Unsupported RileyLink firmware %d.%d (%s)" % (v_major, v_minor, version)) if not force_init: if v_major == 2 and v_minor < 3: response = self._command(Command.READ_REGISTER, bytes([Register.PKTLEN, 0x00])) else: response = self._command(Command.READ_REGISTER, bytes([Register.PKTLEN])) if response is not None and len( response) > 0 and response[0] == 0x50: return self._command(Command.RADIO_RESET_CONFIG) self._command(Command.SET_SW_ENCODING, bytes([Encoding.NONE])) self._command(Command.SET_PREAMBLE, bytes([0x66, 0x65])) #self._command(Command.SET_PREAMBLE, bytes([0, 0])) frequency = int(433910000 / (24000000 / pow(2, 16))) self._command(Command.UPDATE_REGISTER, bytes([Register.FREQ0, frequency & 0xff])) self._command(Command.UPDATE_REGISTER, bytes([Register.FREQ1, (frequency >> 8) & 0xff])) self._command(Command.UPDATE_REGISTER, bytes([Register.FREQ2, (frequency >> 16) & 0xff])) # self._command(Command.UPDATE_REGISTER, bytes([Register.FREQ0, 0x5f])) # self._command(Command.UPDATE_REGISTER, bytes([Register.FREQ1, 0x14])) # self._command(Command.UPDATE_REGISTER, bytes([Register.FREQ2, 0x12])) self._command(Command.UPDATE_REGISTER, bytes([Register.DEVIATN, 0x44])) # self._command(Command.UPDATE_REGISTER, bytes([Register.DEVIATN, 0x44])) self._command(Command.UPDATE_REGISTER, bytes([Register.PKTCTRL1, 0x20])) self._command(Command.UPDATE_REGISTER, bytes([Register.PKTCTRL0, 0x00])) self._command(Command.UPDATE_REGISTER, bytes([Register.PKTLEN, 0x50])) # self._command(Command.UPDATE_REGISTER, bytes([Register.PKTCTRL1, 0x60])) # self._command(Command.UPDATE_REGISTER, bytes([Register.PKTCTRL0, 0x04])) self._command(Command.UPDATE_REGISTER, bytes([Register.FSCTRL1, 0x06])) # self._command(Command.UPDATE_REGISTER, bytes([Register.FSCTRL1, 0x06])) self._command(Command.UPDATE_REGISTER, bytes([Register.MDMCFG4, 0xCA])) self._command(Command.UPDATE_REGISTER, bytes([Register.MDMCFG3, 0xBC])) self._command(Command.UPDATE_REGISTER, bytes([Register.MDMCFG2, 0x06])) self._command(Command.UPDATE_REGISTER, bytes([Register.MDMCFG1, 0x70])) self._command(Command.UPDATE_REGISTER, bytes([Register.MDMCFG0, 0x11])) self._command(Command.UPDATE_REGISTER, bytes([Register.MCSM0, 0x18])) # self._command(Command.UPDATE_REGISTER, bytes([Register.MDMCFG4, 0xDA])) # self._command(Command.UPDATE_REGISTER, bytes([Register.MDMCFG3, 0xB5])) # self._command(Command.UPDATE_REGISTER, bytes([Register.MDMCFG2, 0x12])) # self._command(Command.UPDATE_REGISTER, bytes([Register.MDMCFG1, 0x23])) # self._command(Command.UPDATE_REGISTER, bytes([Register.MDMCFG0, 0x11])) # self._command(Command.UPDATE_REGISTER, bytes([Register.MCSM0, 0x18])) self._command(Command.UPDATE_REGISTER, bytes([Register.FOCCFG, 0x17])) self._command(Command.UPDATE_REGISTER, bytes([Register.FSCAL3, 0xE9])) self._command(Command.UPDATE_REGISTER, bytes([Register.FSCAL2, 0x2A])) self._command(Command.UPDATE_REGISTER, bytes([Register.FSCAL1, 0x00])) self._command(Command.UPDATE_REGISTER, bytes([Register.FSCAL0, 0x1F])) self._command(Command.UPDATE_REGISTER, bytes([Register.TEST1, 0x31])) self._command(Command.UPDATE_REGISTER, bytes([Register.TEST0, 0x09])) # self._command(Command.UPDATE_REGISTER, bytes([Register.FOCCFG, 0x17])) # self._command(Command.UPDATE_REGISTER, bytes([Register.FSCAL3, 0xE9])) # self._command(Command.UPDATE_REGISTER, bytes([Register.FSCAL2, 0x2A])) # self._command(Command.UPDATE_REGISTER, bytes([Register.FSCAL1, 0x00])) # self._command(Command.UPDATE_REGISTER, bytes([Register.FSCAL0, 0x1F])) # self._command(Command.UPDATE_REGISTER, bytes([Register.TEST2, 0x81])) ## register not defined on RL # self._command(Command.UPDATE_REGISTER, bytes([Register.TEST1, 0x35])) # self._command(Command.UPDATE_REGISTER, bytes([Register.TEST0, 0x09])) self._command( Command.UPDATE_REGISTER, bytes([Register.PATABLE0, PA_LEVELS[self.pa_level_index]])) self._command(Command.UPDATE_REGISTER, bytes([Register.FREND0, 0x00])) #self._command(Command.UPDATE_REGISTER, bytes([Register.SYNC1, 0xA5])) #self._command(Command.UPDATE_REGISTER, bytes([Register.SYNC0, 0x5A])) # self._command(Command.UPDATE_REGISTER, bytes([Register.PATABLE0, PA_LEVELS[self.pa_level_index]])) # self._command(Command.UPDATE_REGISTER, bytes([Register.FREND0, 0x00])) self._command(Command.UPDATE_REGISTER, bytes([Register.SYNC1, 0xA5])) self._command(Command.UPDATE_REGISTER, bytes([Register.SYNC0, 0x5A])) response = self._command(Command.GET_STATE) if response != b"OK": raise PacketRadioError( "Rileylink state is not OK. Response returned: %s" % response) self.initialized = True except Exception as e: raise PacketRadioError( "Error while initializing rileylink radio: %s", e) def tx_up(self): try: if self.pa_level_index < len(PA_LEVELS) - 1: self.pa_level_index += 1 self._set_amp(self.pa_level_index) except Exception as e: raise PacketRadioError("Error while setting tx up") from e def tx_down(self): try: if self.pa_level_index > 0: self.pa_level_index -= 1 self._set_amp(self.pa_level_index) except Exception as e: raise PacketRadioError("Error while setting tx down") from e def set_tx_power(self, tx_power): try: if tx_power is None: return elif tx_power == TxPower.Lowest: self._set_amp(0) elif tx_power == TxPower.Low: self._set_amp(PA_LEVELS.index(0x1D)) elif tx_power == TxPower.Normal: self._set_amp(PA_LEVELS.index(0x84)) elif tx_power == TxPower.High: self._set_amp(PA_LEVELS.index(0xC8)) elif tx_power == TxPower.Highest: self._set_amp(PA_LEVELS.index(0xC0)) except Exception as e: raise PacketRadioError("Error while setting tx level") from e def get_packet(self, timeout=5.0): try: self.connect() result = self._command(Command.GET_PACKET, struct.pack(">BL", 0, int(timeout * 1000)), timeout=float(timeout) + 0.5) if result is not None: return result[0:2] + self.manchester.decode(result[2:]) else: return None except Exception as e: raise PacketRadioError("Error while getting radio packet") from e def send_and_receive_packet(self, packet, repeat_count, delay_ms, timeout_ms, retry_count, preamble_ext_ms): try: self.connect() data = self.manchester.encode(packet) result = self._command( Command.SEND_AND_LISTEN, struct.pack(">BBHBLBH", 0, repeat_count, delay_ms, 0, timeout_ms, retry_count, preamble_ext_ms) + data, timeout=30) if result is not None: return result[0:2] + self.manchester.decode(result[2:]) else: return None except Exception as e: raise PacketRadioError( "Error while sending and receiving data") from e def send_packet(self, packet, repeat_count, delay_ms, preamble_extension_ms): try: self.connect() data = self.manchester.encode(packet) result = self._command( Command.SEND_PACKET, struct.pack(">BBHH", 0, repeat_count, delay_ms, preamble_extension_ms) + data, timeout=30) return result except Exception as e: raise PacketRadioError("Error while sending data") from e def _set_amp(self, index=None): try: if index is not None: previous_level = self.pa_level_index self.pa_level_index = index if PA_LEVELS[previous_level] == PA_LEVELS[index]: return self.connect() self._command( Command.UPDATE_REGISTER, bytes([Register.PATABLE0, PA_LEVELS[self.pa_level_index]])) self.packet_logger.debug("Setting pa to %02X (%d of %d)" % (PA_LEVELS[self.pa_level_index], self.pa_level_index, len(PA_LEVELS))) except PacketRadioError: self.logger.exception("Error while setting tx amplification") raise def _findRileyLink(self): global g_rl_address scanner = Scanner() g_rl_address = None self.logger.debug("Scanning for RileyLink") retries = 10 while g_rl_address is None and retries > 0: retries -= 1 for result in scanner.scan(1.0): if result.getValueText(7) == RILEYLINK_SERVICE_UUID: self.logger.debug("Found RileyLink") g_rl_address = result.addr if g_rl_address is None: raise PacketRadioError("Could not find RileyLink") return g_rl_address def _connect_retry(self, retries): while retries > 0: retries -= 1 self.logger.info("Connecting to RileyLink, retries left: %d" % retries) try: self.peripheral.connect(self.address) self.logger.info("Connected") break except BTLEException as btlee: self.logger.warning("BTLE exception trying to connect: %s" % btlee) try: p = subprocess.Popen(["ps", "-A"], stdout=subprocess.PIPE) out, err = p.communicate() for line in out.splitlines(): if "bluepy-helper" in line: pid = int(line.split(None, 1)[0]) os.kill(pid, 9) break except: self.logger.warning("Failed to kill bluepy-helper") time.sleep(1) def _command(self, command_type, command_data=None, timeout=10.0): try: if command_data is None: data = bytes([1, command_type]) else: data = bytes([len(command_data) + 1, command_type ]) + command_data self.peripheral.writeCharacteristic(self.data_handle, data, withResponse=True) if not self.peripheral.waitForNotifications(timeout): raise PacketRadioError( "Timed out while waiting for a response from RileyLink") response = self.peripheral.readCharacteristic(self.data_handle) if response is None or len(response) == 0: raise PacketRadioError("RileyLink returned no response") else: if response[0] == Response.COMMAND_SUCCESS: return response[1:] elif response[0] == Response.COMMAND_INTERRUPTED: self.logger.warning("A previous command was interrupted") return response[1:] elif response[0] == Response.RX_TIMEOUT: return None else: raise PacketRadioError( "RileyLink returned error code: %02X. Additional response data: %s" % (response[0], response[1:]), response[0]) except PacketRadioError: raise except Exception as e: raise PacketRadioError("Error executing command") from e
class SHT31: def __init__(self, addr=None, iface=None): self.loggedData = {'Temp': {}, 'Humi': {}} self.newestTimeStampMs = 0 self.loggerInterval = 0 self.loggingReadout = False self.peripheral = Peripheral(addr, 'random', iface) if addr is not None: self.peripheral.setDelegate(MyDelegate(self)) self.getCharacteristics() def getCharacteristics(self): self.characteristics = {} # READ WRITE self.characteristics[ 'DeviceName'] = self.peripheral.getCharacteristics( uuid=UUID("00002a00-0000-1000-8000-00805f9b34fb"))[0] # READ NOTIFY self.characteristics['Battery'] = self.peripheral.getCharacteristics( uuid=UUID(0x2A19))[0] # WRITE self.characteristics[ 'SyncTimeMs'] = self.peripheral.getCharacteristics( uuid=UUID("0000f235-b38d-4985-720e-0f993a68ee41"))[0] # READ WRITE self.characteristics[ 'OldestTimeStampMs'] = self.peripheral.getCharacteristics( uuid=UUID("0000f236-b38d-4985-720e-0f993a68ee41"))[0] # READ WRITE self.characteristics[ 'NewestTimeStampMs'] = self.peripheral.getCharacteristics( uuid=UUID("0000f237-b38d-4985-720e-0f993a68ee41"))[0] # WRITE NOTIFY self.characteristics[ 'StartLoggerDownload'] = self.peripheral.getCharacteristics( uuid=UUID("0000f238-b38d-4985-720e-0f993a68ee41"))[0] # READ WRITE self.characteristics[ 'LoggerIntervalMs'] = self.peripheral.getCharacteristics( uuid=UUID("0000f239-b38d-4985-720e-0f993a68ee41"))[0] # READ NOTIFY self.characteristics['Humidity'] = self.peripheral.getCharacteristics( uuid=UUID("00001235-b38d-4985-720e-0f993a68ee41"))[0] # READ NOTIFY self.characteristics[ 'Temperature'] = self.peripheral.getCharacteristics( uuid=UUID("00002235-b38d-4985-720e-0f993a68ee41"))[0] def connect(self, addr, iface=None): self.peripheral.setDelegate(MyDelegate(self)) self.peripheral.connect(addr, 'random', iface) self.getCharacteristics() def disconnect(self): self.peripheral.disconnect() def readDeviceName(self): return self.characteristics['DeviceName'].read().decode('ascii') def setDeviceName(self, name): return self.characteristics['DeviceName'].write(name.encode('ascii')) def readTemperature(self): return struct.unpack('f', self.characteristics['Temperature'].read())[0] def setTemperatureNotification(self, enabled): if enabled: self.peripheral.writeCharacteristic( self.characteristics['Temperature'].valHandle + 2, int(1).to_bytes(1, byteorder='little')) else: self.peripheral.writeCharacteristic( self.characteristics['Temperature'].valHandle + 2, int(0).to_bytes(1, byteorder='little')) def readHumidity(self): return struct.unpack('f', self.characteristics['Humidity'].read())[0] def setHumidityNotification(self, enabled): if enabled: self.peripheral.writeCharacteristic( self.characteristics['Humidity'].valHandle + 2, int(1).to_bytes(1, byteorder='little')) else: self.peripheral.writeCharacteristic( self.characteristics['Humidity'].valHandle + 2, int(0).to_bytes(1, byteorder='little')) def readBattery(self): return int.from_bytes(self.characteristics['Battery'].read(), byteorder='little') def setSyncTimeMs(self, timestamp=time.time()): timestampMs = int(round(timestamp * 1000)) self.characteristics['SyncTimeMs'].write( timestampMs.to_bytes(8, byteorder='little')) def readOldestTimestampMs(self): return int.from_bytes(self.characteristics['OldestTimeStampMs'].read(), byteorder='little') def setOldestTimestampMs(self, value): self.characteristics['OldestTimeStampMs'].write( value.to_bytes(8, byteorder='little')) def readNewestTimestampMs(self): return int.from_bytes(self.characteristics['NewestTimeStampMs'].read(), byteorder='little') def setNewestTimestampMs(self, value): self.characteristics['NewestTimeStampMs'].write( value.to_bytes(8, byteorder='little')) def readLoggerIntervalMs(self): return int.from_bytes(self.characteristics['LoggerIntervalMs'].read(), byteorder='little') def setLoggerIntervalMs(self, milliseconds): monthMs = (30 * 24 * 60 * 60 * 1000) if milliseconds < 1000: milliseconds = 1000 elif milliseconds > monthMs: milliseconds = monthMs self.characteristics['LoggerIntervalMs'].write( (int(milliseconds)).to_bytes(4, byteorder='little')) def readLoggedDataInterval(self, startMs=None, stopMs=None): self.setSyncTimeMs() time.sleep( 1 ) # Sleep 1s to enable the gadget to set the SyncTime; otherwise 0 is read when readNewestTimestampMs is used self.setTemperatureNotification(True) self.setHumidityNotification(True) self.loggerInterval = self.readLoggerIntervalMs() if startMs is not None: self.setOldestTimestampMs(startMs) if stopMs is not None: self.setNewestTimestampMs(stopMs) self.newestTimeStampMs = self.readNewestTimestampMs() self.loggingReadout = True self.characteristics['StartLoggerDownload'].write( (1).to_bytes(1, byteorder='little')) def waitForNotifications(self, timeout): return self.peripheral.waitForNotifications(timeout) def isLogReadoutInProgress(self): return self.loggingReadout
def hexStrToInt(hexstr): val = int(hexstr[0:2], 16) + (int(hexstr[2:4], 16) << 8) if ((val & 0x8000) == 0x8000): # treat signed 16bits val = -((val ^ 0xffff) + 1) return val # hexi.disconnect() # gyro_uuid = UUID(0x2012) gyro_uuid = UUID(0x2002) p = Peripheral() p.connect('00:35:40:08:00:48') try: ch = p.getCharacteristics(uuid=gyro_uuid)[0] print(ch) if (ch.supportsRead()): while 1: # print(ch.read()) readChar = ch.read() print(repr(readChar)) val = str(binascii.b2a_hex(readChar)) print(str(val)) print(float(hexStrToInt(val[0:4])) / 100), print(float(hexStrToInt(val[4:8])) / 100), print(float(hexStrToInt(val[8:12])) / 100) time.sleep(1)
import struct from bluepy.btle import Scanner, DefaultDelegate, Peripheral device = Peripheral() device.connect('78:A3:52:00:04:2D') services = device.getServices() characteristics = device.getCharacteristics() descriptors = device.getDescriptors() state = device.getState() #displays all services for service in services: print(service) print("Handle UUID Properties") print("-------------------------------------------------------") for ch in characteristics: print(" 0x"+ format(ch.getHandle(),'02X') +" "+str(ch.uuid) +" " + ch.propertiesToString()) if ch.uuid == '19b10011-e8f2-537e-4f6c-d104768a1214': print(struct.unpack('i', ch.read())[0])
if self.SYN is False: # and data.decode('ascii') is "SYN": self.SYN = True message = "SYN_ACK" print("received SYN") message = message.encode('ascii') self.peripheral.writeCharacteristic(hdl, message, True) elif self.SYN is True: if self.ACK is False: self.ACK = True message = "handshaking completed" print("received ACK") message = message.encode('ascii') self.peripheral.writeCharacteristic(hdl, message, True) elif self.ACK is True: print("Received message: " + data.decode('ascii')) message = input('>>') message = message.encode('ascii') self.peripheral.writeCharacteristic(hdl, message, True) else: print(data.decode('ascii')) hc08 = Peripheral() hc08.connect("a8:e2:c1:62:7f:bc", "public") hc08.withDelegate(MyDelegate(hc08)) while True: print("Start waiting for messages...") hc08.waitForNotifications(10.0)
class Blueterm(cmd.Cmd): intro = cfg['intro'] prompt = cfg['prompt'] def __init__(self, device_index, scan_timeout): cmd.Cmd.__init__(self) self.device_index = device_index self.scan_timeout = scan_timeout self.ble_devs = set() self.ble_gatt = dict() # setup Bluetooth self.scanner = Scanner(device_index) self.periph = Peripheral(None, ADDR_TYPE_PUBLIC, device_index) self.periph.setDelegate(ShellEventHandler()) # Pla def precmd(self, line): return line def do_scan(self, line): """Scan for available BLE RIOT shells. Running this command will reset the cached list of available devices. usage: scan <scan timeout in sec> """ args = line.strip().split(' ') if len(args[0]) > 0: try: to = float(args[0]) except: print("error: unable to parse timeout (must be a number)") return else: to = self.scan_timeout print("Scanning now (blocking for {} seconds)...".format(to)) try: self.ble_devs = list(self.scanner.scan(to)) print("Scan complete:") self.do_list("") except: print("error: failure while scanning") return def do_list(self, line): """List all available BLE devices offering a RIOT shell """ if len(self.ble_devs) == 0: print("No BLE devices available") return for i, dev in enumerate(self.ble_devs): print("[{:2}] {}".format(i, dev.addr), end='') for (adtype, desc, value) in dev.getScanData(): if adtype == 9: print(" (Name: '{}'')".format(value), end='') print() def do_connect(self, line): args = line.strip().split(' ') if len(args[0]) == 0: print("usage: connect <device index>") return try: dev = self.ble_devs[int(args[0])] except: print("error: unable to find given device index") return try: self.periph.connect(dev.addr, dev.addrType) services = self.periph.getServices() for i, service in enumerate(services): print("Service {:2} UUID: {} ({})".format( i, service.uuid, service.uuid.getCommonName())) chars = service.getCharacteristics() type(chars) for i, char in enumerate(chars): print(" Char {:2} UUID: {} ({})".format( i, char.uuid, char.uuid.getCommonName())) # if char.supportsRead(): # tmp = char.read() # print("Data: ", str(tmp)) except: print("error: while conneting something was bad") return
class BLEConnection: def __init__(self, address, mambo): """ Initialize with its BLE address - if you don't know the address, call findMambo and that will discover it for you. :param address: unique address for this mambo :param mambo: the Mambo object for this mambo (needed for callbacks for sensors) """ self.address = address self.drone_connection = Peripheral() self.mambo = mambo # the following UUID segments come from the Mambo and from the documenation at # http://forum.developer.parrot.com/t/minidrone-characteristics-uuid/4686/3 # the 3rd and 4th bytes are used to identify the service self.service_uuids = { 'fa00': 'ARCOMMAND_SENDING_SERVICE', 'fb00': 'ARCOMMAND_RECEIVING_SERVICE', 'fc00': 'PERFORMANCE_COUNTER_SERVICE', 'fd21': 'NORMAL_BLE_FTP_SERVICE', 'fd51': 'UPDATE_BLE_FTP', 'fe00': 'UPDATE_RFCOMM_SERVICE', '1800': 'Device Info', '1801': 'unknown', } # the following characteristic UUID segments come from the documentation at # http://forum.developer.parrot.com/t/minidrone-characteristics-uuid/4686/3 # the 4th bytes are used to identify the characteristic # the usage of the channels are also documented here # http://forum.developer.parrot.com/t/ble-characteristics-of-minidrones/5912/2 self.characteristic_send_uuids = { '0a': 'SEND_NO_ACK', # not-ack commandsandsensors (PCMD only) '0b': 'SEND_WITH_ACK', # ack commandsandsensors (all piloting commandsandsensors) '0c': 'SEND_HIGH_PRIORITY', # emergency commandsandsensors '1e': 'ACK_COMMAND' # ack for data sent on 0e } # counters for each packet (required as part of the packet) self.characteristic_send_counter = { 'SEND_NO_ACK': 0, 'SEND_WITH_ACK': 0, 'SEND_HIGH_PRIORITY': 0, 'ACK_COMMAND': 0, 'RECEIVE_WITH_ACK': 0 } # the following characteristic UUID segments come from the documentation at # http://forum.developer.parrot.com/t/minidrone-characteristics-uuid/4686/3 # the 4th bytes are used to identify the characteristic # the types of commandsandsensors and data coming back are also documented here # http://forum.developer.parrot.com/t/ble-characteristics-of-minidrones/5912/2 self.characteristic_receive_uuids = { '0e': 'ACK_DRONE_DATA', # drone data that needs an ack (needs to be ack on 1e) '0f': 'NO_ACK_DRONE_DATA', # data from drone (including battery and others), no ack '1b': 'ACK_COMMAND_SENT', # ack 0b channel, SEND_WITH_ACK '1c': 'ACK_HIGH_PRIORITY', # ack 0c channel, SEND_HIGH_PRIORITY } # these are the FTP incoming and outcoming channels # the handling characteristic seems to be the one to send commandsandsensors to (per the SDK) # information gained from reading ARUTILS_BLEFtp.m in the SDK self.characteristic_ftp_uuids = { '22': 'NORMAL_FTP_TRANSFERRING', '23': 'NORMAL_FTP_GETTING', '24': 'NORMAL_FTP_HANDLING', '52': 'UPDATE_FTP_TRANSFERRING', '53': 'UPDATE_FTP_GETTING', '54': 'UPDATE_FTP_HANDLING', } # FTP commandsandsensors (obtained via ARUTILS_BLEFtp.m in the SDK) self.ftp_commands = { "list": "LIS", "get": "GET" } # need to save for communication (but they are initialized in connect) self.services = None self.send_characteristics = dict() self.receive_characteristics = dict() self.handshake_characteristics = dict() self.ftp_characteristics = dict() self.data_types = { 'ACK': 1, 'DATA_NO_ACK': 2, 'LOW_LATENCY_DATA': 3, 'DATA_WITH_ACK': 4 } # store whether a command was acked self.command_received = { 'SEND_WITH_ACK': False, 'SEND_HIGH_PRIORITY': False, 'ACK_COMMAND': False } # instead of parsing the XML file every time, cache the results self.command_tuple_cache = dict() self.sensor_tuple_cache = dict() # maximum number of times to try a packet before assuming it failed self.max_packet_retries = 3 def connect(self, num_retries): """ Connects to the drone and re-tries in case of failure the specified number of times :param: num_retries is the number of times to retry :return: True if it succeeds and False otherwise """ # first try to connect to the wifi try_num = 1 connected = False while (try_num < num_retries and not connected): try: self._connect() connected = True except BTLEException: color_print("retrying connections", "INFO") try_num += 1 # fall through, return False as something failed return connected def _reconnect(self, num_retries): """ Reconnect to the drone (assumed the BLE crashed) :param: num_retries is the number of times to retry :return: True if it succeeds and False otherwise """ try_num = 1 success = False while (try_num < num_retries and not success): try: color_print("trying to re-connect to the mambo at address %s" % self.address, "WARN") self.drone_connection.connect(self.address, "random") color_print("connected! Asking for services and characteristics", "SUCCESS") success = True except BTLEException: color_print("retrying connections", "WARN") try_num += 1 if (success): # do the magic handshake self._perform_handshake() return success def _connect(self): """ Connect to the mambo to prepare for flying - includes getting the services and characteristics for communication :return: throws an error if the drone connection failed. Returns void if nothing failed. """ color_print("trying to connect to the mambo at address %s" % self.address, "INFO") self.drone_connection.connect(self.address, "random") color_print("connected! Asking for services and characteristics", "SUCCESS") # re-try until all services have been found allServicesFound = False # used for notifications handle_map = dict() while not allServicesFound: # get the services self.services = self.drone_connection.getServices() # loop through the services for s in self.services: hex_str = self._get_byte_str_from_uuid(s.uuid, 3, 4) # store the characteristics for receive & send if (self.service_uuids[hex_str] == 'ARCOMMAND_RECEIVING_SERVICE'): # only store the ones used to receive data for c in s.getCharacteristics(): hex_str = self._get_byte_str_from_uuid(c.uuid, 4, 4) if hex_str in self.characteristic_receive_uuids: self.receive_characteristics[self.characteristic_receive_uuids[hex_str]] = c handle_map[c.getHandle()] = hex_str elif (self.service_uuids[hex_str] == 'ARCOMMAND_SENDING_SERVICE'): # only store the ones used to send data for c in s.getCharacteristics(): hex_str = self._get_byte_str_from_uuid(c.uuid, 4, 4) if hex_str in self.characteristic_send_uuids: self.send_characteristics[self.characteristic_send_uuids[hex_str]] = c elif (self.service_uuids[hex_str] == 'UPDATE_BLE_FTP'): # store the FTP info for c in s.getCharacteristics(): hex_str = self._get_byte_str_from_uuid(c.uuid, 4, 4) if hex_str in self.characteristic_ftp_uuids: self.ftp_characteristics[self.characteristic_ftp_uuids[hex_str]] = c elif (self.service_uuids[hex_str] == 'NORMAL_BLE_FTP_SERVICE'): # store the FTP info for c in s.getCharacteristics(): hex_str = self._get_byte_str_from_uuid(c.uuid, 4, 4) if hex_str in self.characteristic_ftp_uuids: self.ftp_characteristics[self.characteristic_ftp_uuids[hex_str]] = c # need to register for notifications and write 0100 to the right handles # this is sort of magic (not in the docs!) but it shows up on the forum here # http://forum.developer.parrot.com/t/minimal-ble-commands-to-send-for-take-off/1686/2 # Note this code snippet below more or less came from the python example posted to that forum (I adapted it to my interface) for c in s.getCharacteristics(): if self._get_byte_str_from_uuid(c.uuid, 3, 4) in \ ['fb0f', 'fb0e', 'fb1b', 'fb1c', 'fd22', 'fd23', 'fd24', 'fd52', 'fd53', 'fd54']: self.handshake_characteristics[self._get_byte_str_from_uuid(c.uuid, 3, 4)] = c # check to see if all 8 characteristics were found allServicesFound = True for r_id in self.characteristic_receive_uuids.values(): if r_id not in self.receive_characteristics: color_print("setting to false in receive on %s" % r_id) allServicesFound = False for s_id in self.characteristic_send_uuids.values(): if s_id not in self.send_characteristics: color_print("setting to false in send") allServicesFound = False for f_id in self.characteristic_ftp_uuids.values(): if f_id not in self.ftp_characteristics: color_print("setting to false in ftp") allServicesFound = False # and ensure all handshake characteristics were found if len(self.handshake_characteristics.keys()) != 10: color_print("setting to false in len") allServicesFound = False # do the magic handshake self._perform_handshake() # initialize the delegate to handle notifications self.drone_connection.setDelegate(MamboDelegate(handle_map, self.mambo, self)) def _perform_handshake(self): """ Magic handshake Need to register for notifications and write 0100 to the right handles This is sort of magic (not in the docs!) but it shows up on the forum here http://forum.developer.parrot.com/t/minimal-ble-commandsandsensors-to-send-for-take-off/1686/2 :return: nothing """ color_print("magic handshake to make the drone listen to our commandsandsensors") # Note this code snippet below more or less came from the python example posted to that forum (I adapted it to my interface) for c in self.handshake_characteristics.values(): # for some reason bluepy characteristic handle is two lower than what I need... # Need to write 0x0100 to the characteristics value handle (which is 2 higher) self.drone_connection.writeCharacteristic(c.handle + 2, struct.pack("<BB", 1, 0)) def disconnect(self): """ Disconnect the BLE connection. Always call this at the end of your programs to cleanly disconnect. :return: void """ self.drone_connection.disconnect() def _get_byte_str_from_uuid(self, uuid, byte_start, byte_end): """ Extract the specified byte string from the UUID btle object. This is an ugly hack but it was necessary because of the way the UUID object is represented and the documentation on the byte strings from Parrot. You give it the starting byte (counting from 1 since that is how their docs count) and the ending byte and it returns that as a string extracted from the UUID. It is assumed it happens before the first - in the UUID. :param uuid: btle UUID object :param byte_start: starting byte (counting from 1) :param byte_end: ending byte (counting from 1) :return: string with the requested bytes (to be used as a key in the lookup tables for services) """ uuid_str = format("%s" % uuid) idx_start = 2 * (byte_start - 1) idx_end = 2 * (byte_end) my_hex_str = uuid_str[idx_start:idx_end] return my_hex_str def send_turn_command(self, command_tuple, degrees): """ Build the packet for turning and send it :param command_tuple: command tuple from the parser :param degrees: how many degrees to turn :return: True if the command was sent and False otherwise """ self.characteristic_send_counter['SEND_WITH_ACK'] = (self.characteristic_send_counter['SEND_WITH_ACK'] + 1) % 256 packet = struct.pack("<BBBBHh", self.data_types['DATA_WITH_ACK'], self.characteristic_send_counter['SEND_WITH_ACK'], command_tuple[0], command_tuple[1], command_tuple[2], degrees) return self.send_command_packet_ack(packet) def send_auto_takeoff_command(self, command_tuple): """ Build the packet for auto takeoff and send it :param command_tuple: command tuple from the parser :return: True if the command was sent and False otherwise """ # print command_tuple self.characteristic_send_counter['SEND_WITH_ACK'] = ( self.characteristic_send_counter[ 'SEND_WITH_ACK'] + 1) % 256 packet = struct.pack("<BBBBHB", self.data_types['DATA_WITH_ACK'], self.characteristic_send_counter['SEND_WITH_ACK'], command_tuple[0], command_tuple[1], command_tuple[2], 1) return self.send_command_packet_ack(packet) def send_command_packet_ack(self, packet): """ Sends the actual packet on the ack channel. Internal function only. :param packet: packet constructed according to the command rules (variable size, constructed elsewhere) :return: True if the command was sent and False otherwise """ try_num = 0 self._set_command_received('SEND_WITH_ACK', False) while (try_num < self.max_packet_retries and not self.command_received['SEND_WITH_ACK']): color_print("sending command packet on try %d" % try_num, 2) self._safe_ble_write(characteristic=self.send_characteristics['SEND_WITH_ACK'], packet=packet) #self.send_characteristics['SEND_WITH_ACK'].write(packet) try_num += 1 color_print("sleeping for a notification", 2) #notify = self.drone.waitForNotifications(1.0) self.smart_sleep(0.5) #color_print("awake %s " % notify, 2) return self.command_received['SEND_WITH_ACK'] def send_pcmd_command(self, command_tuple, roll, pitch, yaw, vertical_movement, duration): """ Send the PCMD command with the specified roll, pitch, and yaw :param command_tuple: command tuple per the parser :param roll: :param pitch: :param yaw: :param vertical_movement: :param duration: """ start_time = time.time() while (time.time() - start_time < duration): self.characteristic_send_counter['SEND_NO_ACK'] = ( self.characteristic_send_counter['SEND_NO_ACK'] + 1) % 256 packet = struct.pack("<BBBBHBbbbbI", self.data_types['DATA_NO_ACK'], self.characteristic_send_counter['SEND_NO_ACK'], command_tuple[0], command_tuple[1], command_tuple[2], 1, roll, pitch, yaw, vertical_movement, 0) self._safe_ble_write(characteristic=self.send_characteristics['SEND_NO_ACK'], packet=packet) # self.send_characteristics['SEND_NO_ACK'].write(packet) notify = self.drone_connection.waitForNotifications(0.1) def send_noparam_command_packet_ack(self, command_tuple): """ Send a command on the ack channel - where all commandsandsensors except PCMD go, per http://forum.developer.parrot.com/t/ble-characteristics-of-minidrones/5912/2 the id of the last command sent (for use in ack) is the send counter (which is incremented before sending) Ensures the packet was received or sends it again up to a maximum number of times. :param command_tuple: 3 tuple of the command bytes. 0 padded for 4th byte :return: True if the command was sent and False otherwise """ self.characteristic_send_counter['SEND_WITH_ACK'] = (self.characteristic_send_counter['SEND_WITH_ACK'] + 1) % 256 packet = struct.pack("<BBBBH", self.data_types['DATA_WITH_ACK'], self.characteristic_send_counter['SEND_WITH_ACK'], command_tuple[0], command_tuple[1], command_tuple[2]) return self.send_command_packet_ack(packet) def send_enum_command_packet_ack(self, command_tuple, enum_value, usb_id=None): """ Send a command on the ack channel with enum parameters as well (most likely a flip). All commandsandsensors except PCMD go on the ack channel per http://forum.developer.parrot.com/t/ble-characteristics-of-minidrones/5912/2 the id of the last command sent (for use in ack) is the send counter (which is incremented before sending) :param command_tuple: 3 tuple of the command bytes. 0 padded for 4th byte :param enum_value: the enum index :return: nothing """ self.characteristic_send_counter['SEND_WITH_ACK'] = (self.characteristic_send_counter['SEND_WITH_ACK'] + 1) % 256 if (usb_id is None): packet = struct.pack("<BBBBBBI", self.data_types['DATA_WITH_ACK'], self.characteristic_send_counter['SEND_WITH_ACK'], command_tuple[0], command_tuple[1], command_tuple[2], 0, enum_value) else: color_print((self.data_types['DATA_WITH_ACK'], self.characteristic_send_counter['SEND_WITH_ACK'], command_tuple[0], command_tuple[1], command_tuple[2], 0, usb_id, enum_value), 1) packet = struct.pack("<BBBBHBI", self.data_types['DATA_WITH_ACK'], self.characteristic_send_counter['SEND_WITH_ACK'], command_tuple[0], command_tuple[1], command_tuple[2], usb_id, enum_value) return self.send_command_packet_ack(packet) def send_param_command_packet(self, command_tuple, param_tuple=None, param_type_tuple=0, ack=True): """ Send a command packet with parameters. Ack channel is optional for future flexibility, but currently commands are always send over the Ack channel so it defaults to True. Contributed by awm102 on github. Edited by Amy McGovern to work for BLE commands also. :param: command_tuple: the command tuple derived from command_parser.get_command_tuple() :param: param_tuple (optional): the parameter values to be sent (can be found in the XML files) :param: param_size_tuple (optional): a tuple of strings representing the data type of the parameters e.g. u8, float etc. (can be found in the XML files) :param: ack (optional): allows ack to be turned off if required :return: """ # Create lists to store the number of bytes and pack chars needed for parameters # Default them to zero so that if no params are provided the packet size is correct param_size_list = [0] * len(param_tuple) pack_char_list = [0] * len(param_tuple) if param_tuple is not None: # Fetch the parameter sizes. By looping over the param_tuple we only get the data # for requested parameters so a mismatch in params and types does not matter for i, param in enumerate(param_tuple): pack_char_list[i], param_size_list[i] = get_data_format_and_size(param, param_type_tuple[i]) if ack: ack_string = 'SEND_WITH_ACK' data_ack_string = 'DATA_WITH_ACK' else: ack_string = 'SEND_NO_ACK' data_ack_string = 'DATA_NO_ACK' # Construct the base packet self.characteristic_send_counter['SEND_WITH_ACK'] = (self.characteristic_send_counter['SEND_WITH_ACK'] + 1) % 256 # TODO: Amy changed this to match the BLE packet structure but needs to fully test it packet = struct.pack("<BBBBH", self.data_types[data_ack_string], self.characteristic_send_counter[ack_string], command_tuple[0], command_tuple[1], command_tuple[2]) if param_tuple is not None: # Add in the parameter values based on their sizes for i, param in enumerate(param_tuple): packet += struct.pack(pack_char_list[i], param) # TODO: Fix this to not go with ack always return self.send_command_packet_ack(packet) def _set_command_received(self, channel, val): """ Set the command received on the specified channel to the specified value (used for acks) :param channel: channel :param val: True or False :return: """ self.command_received[channel] = val def _safe_ble_write(self, characteristic, packet): """ Write to the specified BLE characteristic but first ensure the connection is valid :param characteristic: :param packet: :return: """ success = False while (not success): try: characteristic.write(packet) success = True except BTLEException: color_print("reconnecting to send packet", "WARN") self._reconnect(3) def ack_packet(self, buffer_id, packet_id): """ Ack the packet id specified by the argument on the ACK_COMMAND channel :param packet_id: the packet id to ack :return: nothing """ #color_print("ack last packet on the ACK_COMMAND channel", "INFO") self.characteristic_send_counter['ACK_COMMAND'] = (self.characteristic_send_counter['ACK_COMMAND'] + 1) % 256 packet = struct.pack("<BBB", self.data_types['ACK'], self.characteristic_send_counter['ACK_COMMAND'], packet_id) #color_print("sending packet %d %d %d" % (self.data_types['ACK'], self.characteristic_send_counter['ACK_COMMAND'], # packet_id), "INFO") self._safe_ble_write(characteristic=self.send_characteristics['ACK_COMMAND'], packet=packet) #self.send_characteristics['ACK_COMMAND'].write(packet) def smart_sleep(self, timeout): """ Sleeps the requested number of seconds but wakes up for notifications Note: NEVER use regular time.sleep! It is a blocking sleep and it will likely cause the BLE to disconnect due to dropped notifications. Always use smart_sleep instead! :param timeout: number of seconds to sleep :return: """ start_time = time.time() while (time.time() - start_time < timeout): try: notify = self.drone_connection.waitForNotifications(0.1) except: color_print("reconnecting to wait", "WARN") self._reconnect(3)
class SBrickCommunications(threading.Thread, IdleObject): def __init__(self, sbrick_addr): threading.Thread.__init__(self) IdleObject.__init__(self) self.lock = threading.RLock() self.drivingLock = threading.RLock() self.eventSend = threading.Event() self.sBrickAddr = sbrick_addr self.owner_password = None self.brickChannels = [ SBrickChannelDrive(0, self.eventSend), SBrickChannelDrive(1, self.eventSend), SBrickChannelDrive(2, self.eventSend), SBrickChannelDrive(3, self.eventSend), ] self.SBrickPeripheral = None self.stopFlag = False self.characteristicRemote = None self.need_authentication = False self.authenticated = False self.channel_config_ids = dict() def set_channel_config_id(self, channel, config_id): self.channel_config_ids[config_id] = channel self.brickChannels[channel].set_config_id(config_id) def terminate(self): self.stopFlag = True def is_driving(self): locked = self.drivingLock.acquire(False) if locked: self.drivingLock.release() return not locked def connect_to_sbrick(self, owner_password): self.owner_password = owner_password self.start() def run(self): try: monotime = 0.0 self.SBrickPeripheral = Peripheral() self.SBrickPeripheral.connect(self.sBrickAddr) service = self.SBrickPeripheral.getServiceByUUID('4dc591b0-857c-41de-b5f1-15abda665b0c') characteristics = service.getCharacteristics('02b8cbcc-0e25-4bda-8790-a15f53e6010f') for characteristic in characteristics: if characteristic.uuid == '02b8cbcc-0e25-4bda-8790-a15f53e6010f': self.characteristicRemote = characteristic if self.characteristicRemote is None: return self.emit('sbrick_connected') self.need_authentication = self.get_need_authentication() self.authenticated = not self.need_authentication if self.need_authentication: if self.password_owner is not None: self.authenticate_owner(self.password_owner) while not self.stopFlag: if self.authenticated: if monotonic.monotonic() - monotime >= 0.05: self.send_command() monotime = monotonic.monotonic() self.eventSend.wait(0.01) for channel in self.brickChannels: if channel.decrement_run_timer(): monotime = 0.0 self.drivingLock.release() # print("stop run normal") self.emit("sbrick_channel_stop", channel.channel) if channel.decrement_brake_timer(): self.drivingLock.release() # print("stop brake timer") monotime = 0.0 self.emit("sbrick_channel_stop", channel.channel) if self.authenticated: self.stop_all() self.send_command() self.SBrickPeripheral.disconnect() self.emit('sbrick_disconnected_ok') except BTLEException as ex: self.emit("sbrick_disconnected_error", ex.message) def get_channel(self, channel): if isinstance(channel, six.integer_types): return self.brickChannels[channel] if isinstance(channel, six.string_types): return self.brickChannels[self.channel_config_ids[channel]] return None def drive(self, channel, pwm, reverse, time, brake_after_time=False): with self.lock: ch = self.get_channel(channel) if ch is not None: ch.drive(pwm, reverse, time, brake_after_time) self.emit("sbrick_drive_sent", ch.channel, time) self.eventSend.set() def stop(self, channel, braked=False): with self.lock: ch = self.get_channel(channel) if ch is not None: ch.stop(braked) self.emit("sbrick_drive_sent", ch.channel, -2) self.eventSend.set() def stop_all(self): with self.lock: for channel in self.brickChannels: channel.stop() self.eventSend.set() def change_pwm(self, channel, pwm, change_reverse=False): with self.lock: ch = self.get_channel(channel) if ch is not None: ch.set_pwm(pwm, change_reverse) self.eventSend.set() def change_reverse(self, channel, reverse): with self.lock: ch = self.get_channel(channel) if ch is not None: ch.set_reverse(reverse) self.eventSend.set() def send_command(self): with self.lock: # try: drivecmd = bytearray([0x01]) brakecmd = bytearray([0x00]) for channel in self.brickChannels: drivecmd = channel.get_command_drive(drivecmd) brakecmd = channel.get_command_brake(brakecmd) if len(drivecmd) > 1: self.drivingLock.acquire() self.characteristicRemote.write(drivecmd, True) self.print_hex_string("drive sent", drivecmd) if len(brakecmd) > 1: self.characteristicRemote.write(brakecmd, True) self.print_hex_string("brake sent", brakecmd) # return True # except Exception as ex: # self.emit("sbrick_disconnected_error",ex.message) # return False def disconnect_sbrick(self): with self.lock: self.stopFlag = True @staticmethod def print_hex_string(what, strin): out = what + " -> " for chrx in strin: out = "%s %0X" % (out, chrx) print(out) def get_voltage(self): with self.lock: try: self.characteristicRemote.write(b"\x0f\x00") value = self.characteristicRemote.read() valueint = struct.unpack("<H", value)[0] return (valueint * 0.83875) / 2047.0 except BTLEException as ex: self.emit("sbrick_disconnected_error", ex.message) def get_temperature(self): with self.lock: try: self.characteristicRemote.write(b"\x0f\x0e") value = self.characteristicRemote.read() valueint = struct.unpack("<H", value)[0] return valueint / 118.85795 - 160 except BTLEException as ex: self.emit("sbrick_disconnected_error", ex.message) def get_thermal_limit(self): with self.lock: try: self.characteristicRemote.write(b'\x15') value = self.characteristicRemote.read() valueint = struct.unpack("<H", value)[0] return valueint / 118.85795 - 160 except BTLEException as ex: self.emit("sbrick_disconnected_error", ex.message) def get_watchdog_timeout(self): with self.lock: try: self.characteristicRemote.write(b'\x0e') value = self.characteristicRemote.read() return struct.unpack("<B", value)[0] * 0.1 except BTLEException as ex: self.emit("sbrick_disconnected_error", ex.message) def get_authentication_timeout(self): with self.lock: try: self.characteristicRemote.write(b'\x09') value = self.characteristicRemote.read() return struct.unpack("<B", value)[0] * 0.1 except BTLEException as ex: self.emit("sbrick_disconnected_error", ex.message) def get_power_cycle_counter(self): with self.lock: try: self.characteristicRemote.write(b'\x28') value = self.characteristicRemote.read() return struct.unpack("<I", value)[0] except BTLEException as ex: self.emit("sbrick_disconnected_error", ex.message) def get_uptime(self): with self.lock: try: self.characteristicRemote.write(b'\x29') value = self.characteristicRemote.read() seconds = struct.unpack("<I", value)[0] * 0.1 minutes = seconds // 60 hours = minutes // 60 return "%02d:%02d:%02d" % (hours, minutes % 60, seconds % 60) except BTLEException as ex: self.emit("sbrick_disconnected_error", ex.message) def get_hardware_version(self): try: return self.SBrickPeripheral.readCharacteristic(0x000c).decode("utf-8") except BTLEException as ex: self.emit("sbrick_disconnected_error", ex.message) def get_software_version(self): try: return self.SBrickPeripheral.readCharacteristic(0x000a).decode("utf-8") except BTLEException as ex: self.emit("sbrick_disconnected_error", ex.message) def get_brick_id(self): with self.lock: try: self.characteristicRemote.write(b'\x0a') value = self.characteristicRemote.read() return "%0X %0X %0X %0X %0X %0X" % ( value[0], value[1], value[2], value[3], value[4], value[5]) except BTLEException as ex: self.emit("sbrick_disconnected_error", ex.message) def get_need_authentication(self): with self.lock: try: self.characteristicRemote.write(b'\x02') value = self.characteristicRemote.read() return struct.unpack("<B", value)[0] == 1 except BTLEException as ex: self.emit("sbrick_disconnected_error", ex.message) def get_is_authenticated(self): with self.lock: try: self.characteristicRemote.write(b'\x03') value = self.characteristicRemote.read() return struct.unpack("<B", value)[0] == 1 except BTLEException as ex: self.emit("sbrick_disconnected_error", ex.message) def get_user_id(self): with self.lock: try: self.characteristicRemote.write(b'\x04') value = self.characteristicRemote.read() return struct.unpack("<B", value)[0] == 1 except BTLEException as ex: self.emit("sbrick_error", ex.message) def authenticate_owner(self, password): with self.lock: try: self.authenticated = False cmd = bytearray([0x05, 0x00]) for ch in password: cmd.append(ord(ch)) self.characteristicRemote.write(cmd) self.authenticated = True except BTLEException as ex: self.emit("sbrick_error", ex.message) def authenticate_guest(self, password): with self.lock: try: self.authenticated = False cmd = bytearray([0x05, 0x01]) for ch in password: cmd.append(ord(ch)) self.characteristicRemote.write(cmd) self.authenticated = True except BTLEException as ex: self.emit("sbrick_error", ex.message) def clear_owner_password(self): with self.lock: try: self.characteristicRemote.write(b'\x06\x00') except BTLEException as ex: self.emit("sbrick_error", ex.message) def clear_guest_password(self): with self.lock: try: self.characteristicRemote.write(b'\x06\x01') except BTLEException as ex: self.emit("sbrick_error", ex.message) def set_owner_password(self, password): with self.lock: try: cmd = bytearray([0x07, 0x00]) for ch in password: cmd.append(ord(ch)) self.characteristicRemote.write(cmd) except BTLEException as ex: self.emit("sbrick_error", ex.message) def set_guest_password(self, password): with self.lock: try: cmd = bytearray([0x07, 0x01]) for ch in password: cmd.append(ord(ch)) self.characteristicRemote.write(cmd) except BTLEException as ex: self.emit("sbrick_error", ex.message) def set_authentication_timeout(self, seconds): with self.lock: try: cmd = bytearray([0x08, seconds / 0.1]) self.characteristicRemote.write(cmd) except BTLEException as ex: self.emit("sbrick_error", ex.message)
class SbrickAPI(object): # UUID of Remote Control Commands. The Remote Control Commands characteristic allows full control over SBrick. rcc_uuid = '02b8cbcc-0e25-4bda-8790-a15f53e6010f' stop_hex = '00' drive_hex = '01' class DriveThread(Thread): def __init__(self, logger, sbrick, channel, direction, power): Thread.__init__(self) self._sbrick = sbrick self._channel = channel self._direction = direction self._power = power self._logger = logger self._stop_event = Event() self._timer_thd = None def run(self): self.drive() def drive(self): self.drive_channel() self.break_channel() def stop(self): self._stop_event.set() self._timer_thd.cancel() def times_up(self): self._logger.debug('Drive action times_up {}{}{}{}'.format( SbrickAPI.drive_hex, self._channel, self._direction, self._power)) self._stop_event.set() def reset_command(self, channel, direction, power): self._channel = channel self._direction = direction self._power = power def reset_timer(self, exec_time): if self._timer_thd: self._timer_thd.cancel() if self._stop_event: self._stop_event.clear() if MAGIC_FOREVER == exec_time: return self._timer_thd = Timer(exec_time, self.times_up) self._timer_thd.setName('timer_' + self._channel) self._timer_thd.start() def drive_channel(self): while (not self._stop_event.is_set()): drive_hex_string = SbrickAPI.drive_hex + self._channel + self._direction + self._power self.exec_command(drive_hex_string) # TODO: not need to sleep #time.sleep(0.1) time.sleep(1) def break_channel(self): stop_hex_string = SbrickAPI.stop_hex + self._channel self.exec_command(stop_hex_string) def exec_command(self, hex_string): self._logger.debug('Exec command {}'.format(hex_string)) binary = bytes.fromhex(hex_string) self._sbrick.rcc_char_write_ex(binary, reconnect_do_again=False) #self._sbrick.rcc_char_read_ex(reconnect_do_again=False) @property def stop_event(self): return self._stop_event @property def timer_thd(self): return self._timer_thd def __init__(self, logger, dev_mac): self._dev_mac = dev_mac self._logger = logger self._lock = Lock() # bluepy is not thread-safe, must use lock to protect it self._blue = Peripheral() self._rcc_char = None self._channel_thread = {'00': None, '01': None, '02': None, '03': None} def __enter__(self): self.connect() return self def __exit__(self, type, value, traceback): self.disconnect() def _construct_new_bluetooth_object(self): self._lock.acquire() self._logger.info("Construct a new bluetooth object") del self._blue self._blue = Peripheral() self._lock.release() def connect(self): try: self._lock.acquire() self._logger.info('Try to connect to SBrick ({})'.format( self._dev_mac)) # connect() is a blocking function self._blue.connect(self._dev_mac) except BTLEException as e: self._lock.release() self._logger.error('SBrick ({}): {}'.format( self._dev_mac, e.message)) if isinstance(e, BTLEDisconnectError): return False else: self._construct_new_bluetooth_object() self._logger.error('exit -1') sys.exit(-1) except Exception as e: self._lock.release() self._logger.error(e) self._construct_new_bluetooth_object() self._logger.error('exit -1') sys.exit(-1) else: self._logger.info('Connect to SBrick ({}) successfully'.format( self._dev_mac)) # Get remote control command characteristic try: self._logger.info('Get rcc characteristic') chars = self._blue.getCharacteristics(uuid=SbrickAPI.rcc_uuid) except Exception as e: self._lock.release() self._logger.error( "Failed to get SBrick characteristics ({}): {}".format( SbrickAPI.rcc_uuid, e)) self._construct_new_bluetooth_object() self._logger.error('exit -1') sys.exit(-1) else: for char in chars: if char.uuid == SbrickAPI.rcc_uuid: self._rcc_char = char # Get services information try: services = self._blue.getServices() except Exception as e: self._lock.release() self._logger.error("Failed to get SBrick services ({}): {}".format( self._dev_mac, e)) self._construct_new_bluetooth_object() self._logger.error('exit -1') sys.exit(-1) else: self._services = services self._lock.release() return True def disconnect_ex(self): # disconnect SBrick using bluetoothctl command bl_cmd = 'disconnect {}\nquit'.format(self._dev_mac) cmd = "echo -e '{}'".format(bl_cmd) p1 = subprocess.Popen(shlex.split(cmd), stdout=subprocess.PIPE) p2 = subprocess.Popen(shlex.split('bluetoothctl'), stdin=p1.stdout, stdout=subprocess.PIPE, shell=True) p1.stdout.close() p2.communicate() # wait 3 seconds for real disconnection, because `bluetoothctl #disconnect` is an asynchronous command time.sleep(3) def disconnect(self): self._lock.acquire() self._blue.disconnect() self._logger.info('Disconnect from SBrick({}) successfully'.format( self._dev_mac)) self._lock.release() def re_connect(self): self._logger.info('Re-connect to SBrick ({})'.format(self._dev_mac)) self.disconnect() return self.connect() def drive(self, channel='00', direction='00', power='f0', exec_time=1): # reset thread status when the thread is dead if self._channel_thread[ channel] and not self._channel_thread[channel].is_alive(): self._channel_thread[channel].join() self._channel_thread[channel] = None if None == self._channel_thread[channel]: # Create a thread for executing drive thd = SbrickAPI.DriveThread(self._logger, self, channel, direction, power) thd.setName('channel_' + channel) thd.reset_timer(exec_time) self._channel_thread[channel] = thd thd.start() else: self._logger.debug('Overwrite drive action') running_thd = self._channel_thread[channel] running_thd.reset_command(channel, direction, power) running_thd.reset_timer(exec_time) def stop(self, channels=['00']): # TODO: validate parameters self._logger.debug('Stop action') for channel in channels: thd = self._channel_thread[channel] if thd: thd.stop() thd.join() self._channel_thread[channel] = None def rcc_char_write_ex(self, binary, reconnect_do_again=True): # make sure _rcc_char exist self._lock.acquire() self._logger.debug( 'RCC characteristic writes binary: {}'.format(binary)) if not self._rcc_char: self._lock.release() self._construct_new_bluetooth_object() if False == self.re_connect(): return False else: self._lock.release() # write binary try: self._lock.acquire() self._rcc_char.write(binary) except BrokenPipeError as e: self._lock.release() self._logger.error('BrokerPipeError with bluepy-helper') self._logger.error('exit -1') sys.exit(-1) except BTLEException as e: self._lock.release() self._logger.error('SBrick ({}): {}'.format( self._dev_mac, e.message)) if isinstance(e, BTLEDisconnectError): self._construct_new_bluetooth_object() if False == self.re_connect(): return False if reconnect_do_again: self.rcc_char_write_ex(binary, reconnect_do_again=False) elif isinstance( e, BTLEInternalError ) and "Helper not started (did you call connect()?)" == e.message: self._construct_new_bluetooth_object() if False == self.re_connect(): return False if reconnect_do_again: self.rcc_char_write_ex(binary, reconnect_do_again=False) elif isinstance( e, BTLEException ) and "Error from bluepy-helper (badstate)" == e.message: if False == self.re_connect(): return False if reconnect_do_again: self.rcc_char_write_ex(binary, reconnect_do_again=False) else: self._construct_new_bluetooth_object() self._logger.error('exit -1') sys.exit(-1) except Exception as e: self._lock.release() self._logger.error(e) self._construct_new_bluetooth_object() self._logger.error('exit -1') sys.exit(-1) else: self._lock.release() return True def rcc_char_read_ex(self, reconnect_do_again=True): try: self._lock.acquire() out = self._rcc_char.read() except BrokenPipeError as e: self._lock.release() self._logger.error('BrokerPipeError with bluepy-helper') self._logger.error('exit -1') sys.exit(-1) except BTLEException as e: self._lock.release() self._logger.error('SBrick ({}): {}'.format( self._dev_mac, e.message)) if BTLEException.DISCONNECTED == e.code: if False == self.re_connect(): return False if reconnect_do_again: self.rcc_char_read_ex(reconnect_do_again=False) else: self._construct_new_bluetooth_object() self._logger.error('exit -1') sys.exit(-1) except Exception as e: self._lock.release() self._logger.error(e) self._construct_new_bluetooth_object() self._logger.error('exit -1') sys.exit(-1) else: self._lock.release() return out def get_info_service(self): self._logger.debug("Service information:") for s in self._services: self._logger.debug(" {service}, {uuid}".format(service=s, uuid=s.uuid)) chars = s.getCharacteristics() for c in chars: self._logger.debug(" {char}, {uuid}, {proty}".format( char=c, uuid=c.uuid, proty=c.propertiesToString())) ret = [] for s in self._services: service = {} service['description'] = "{}".format(s) service['uuid'] = s.uuid.getCommonName() service['characteristics'] = [] chars = s.getCharacteristics() for c in chars: characteristic = {} characteristic['description'] = "{}".format(c) characteristic['uuid'] = c.uuid.getCommonName() characteristic['property'] = c.propertiesToString() #characteristic['value'] = c.read() if c.supportsRead() else '' service['characteristics'].append(characteristic) ret.append(service) self.disconnect() return ret def get_info_adc(self): ret = {} # Get temperature code = bytes.fromhex('0F09') if False == self.rcc_char_write_ex(code): return ret binary = self.rcc_char_read_ex() value = struct.unpack("<H", binary)[0] self._temperature = (value / 118.85795) - 160 # Get voltage code = bytes.fromhex('0F08') if False == self.rcc_char_write_ex(code): return ret binary = self.rcc_char_read_ex() value = struct.unpack('<H', binary)[0] self._voltage = (value * 0.83875) / 2047.0 self._logger.debug("ADC information:") self._logger.debug(" Temperature = {}".format(self._temperature)) self._logger.debug(" Voltage = {}".format(self._voltage)) ret['temperature'] = self._temperature ret['voltage'] = self._voltage self.disconnect() return ret def get_info_general(self): ret = {} # Get is_authenticated code = bytes.fromhex('03') if False == self.rcc_char_write_ex(code): return ret binary = self.rcc_char_read_ex() value = struct.unpack("<B", binary)[0] self._is_auth = value # Get authentication timeout code = bytes.fromhex('09') if False == self.rcc_char_write_ex(code): return ret binary = self.rcc_char_read_ex() value = struct.unpack("<B", binary)[0] self._auth_timeout = value * 0.1 # second # Get brick ID code = bytes.fromhex('0A') if False == self.rcc_char_write_ex(code): return ret binary = self.rcc_char_read_ex() value = struct.unpack("<6B", binary) l = list(map(lambda v: "%X" % (v), list(value))) self._brick_id = ' '.join(l) # Get watchdog timeout code = bytes.fromhex('0E') if False == self.rcc_char_write_ex(code): return ret binary = self.rcc_char_read_ex() value = struct.unpack("<B", binary)[0] self._watchdog_timeout = value * 0.1 # second # Get thermal limit code = bytes.fromhex('15') if False == self.rcc_char_write_ex(code): return ret binary = self.rcc_char_read_ex() value = struct.unpack("<H", binary)[0] self._thermal_limit = (value / 118.85795) - 160 # Get PWM counter value code = bytes.fromhex('20') if False == self.rcc_char_write_ex(code): return ret binary = self.rcc_char_read_ex() value = struct.unpack("<H", binary)[0] self._pwm_counter_value = value # Get channel status code = bytes.fromhex('22') if False == self.rcc_char_write_ex(code): return ret binary = self.rcc_char_read_ex() value = struct.unpack("<7B", binary) self._channel_status = value # Get is guest password set code = bytes.fromhex('23') if False == self.rcc_char_write_ex(code): return ret binary = self.rcc_char_read_ex() value = struct.unpack("<B", binary)[0] self._is_quest_pw_set = value # Get connection parameters code = bytes.fromhex('25') if False == self.rcc_char_write_ex(code): return ret binary = self.rcc_char_read_ex() value = struct.unpack("<3H", binary) self._conn_param = value # Get release on reset code = bytes.fromhex('27') if False == self.rcc_char_write_ex(code): return ret binary = self.rcc_char_read_ex() value = struct.unpack("<B", binary)[0] self._ror = value # Get power cycle counter code = bytes.fromhex('28') if False == self.rcc_char_write_ex(code): return ret binary = self.rcc_char_read_ex() value = struct.unpack("<I", binary)[0] self._power_cycle_counter = value # Get uptime counter code = bytes.fromhex('29') if False == self.rcc_char_write_ex(code): return ret binary = self.rcc_char_read_ex() value = struct.unpack("<I", binary)[0] self._uptime_counter = value self._logger.debug("General information:") self._logger.debug(" Is authenticated: {}".format(self._is_auth)) self._logger.debug(" Authentication timeout(second): {}".format( self._auth_timeout)) self._logger.debug(" Brick ID: {}".format(self._brick_id)) self._logger.debug(" Watchdog timeout(second): {}".format( self._watchdog_timeout)) self._logger.debug(" Thermal limit: {}".format(self._thermal_limit)) self._logger.debug(" PWM counter value: {}".format( self._pwm_counter_value)) self._logger.debug(" Channel status: {}".format(self._channel_status)) self._logger.debug(" Is guest password set: {}".format( self._is_quest_pw_set)) self._logger.debug(" Connection parameters(ms): {}".format( self._conn_param)) self._logger.debug(" Release on reset: {}".format(self._ror)) self._logger.debug(" Power cycle counter: {}".format( self._power_cycle_counter)) self._logger.debug(" Uptime counter: {}".format(self._uptime_counter)) ret['is_auth'] = self._is_auth ret['auth_timeout'] = self._auth_timeout ret['brick_id'] = self._brick_id ret['watchdog_timeout'] = self._watchdog_timeout ret['thermal_limit'] = self._thermal_limit ret['is_quest_password_set'] = self._is_quest_pw_set ret['power_cycle_count'] = self._power_cycle_counter ret['uptime_count'] = self._uptime_counter self.disconnect() return ret def set_watchdog_timeout(self, timeout): """ timeout: 0.1 seconds, 1 byte. Ragne: 0 ~ 255 """ self.connect() code = bytes.fromhex('0D') + struct.pack('<B', timeout) self.rcc_char_write_ex(code) self.disconnect() @property def blue(self): return self._blue
class Blueterm(cmd.Cmd): intro = cfg['intro'] prompt = cfg['prompt'] class State(Enum): IDLE = 1 CONNECTED = 2 def __init__(self, device_index, scan_timeout): cmd.Cmd.__init__(self) self.device_index = device_index self.scan_timeout = scan_timeout self.ble_devs = set() self.ble_gatt = dict() self.chars = dict() self.state = self.State.IDLE # setup Bluetooth self.scanner = Scanner(device_index) self.periph = Peripheral(None, ADDR_TYPE_PUBLIC, device_index) self.periph.setDelegate(ShellEventHandler()) # Pla def precmd(self, line): return line def do_state(self, line): """Print current connection state """ if self.state == self.State.CONNECTED: print("Connected to {}".format(self.periph.addr)) else: print(self.state) def do_scan(self, line): """Scan for available BLE RIOT shells. Running this command will reset the cached list of available devices. usage: scan <scan timeout in sec> """ args = line.strip().split(' ') if len(args[0]) > 0: try: to = float(args[0]) except: print("error: unable to parse timeout (must be a number)") return else: to = self.scan_timeout print("Scanning now (blocking for {} seconds)...".format(to)) try: self.ble_devs = list(self.scanner.scan(to)) print("Scan complete:") self.do_list("") except: print("error: failure while scanning") return def do_list(self, line): """List all available BLE devices offering a RIOT shell """ if len(self.ble_devs) == 0: print("No BLE devices available") return for i, dev in enumerate(self.ble_devs): print("[{:2}] {}".format(i, dev.addr), end='') for (adtype, desc, value) in dev.getScanData(): if adtype == 9: print(" (Name: '{}'')".format(value), end='') print() def do_connect(self, line): args = line.strip().split(' ') if len(args[0]) == 0: print("usage: connect <device index>") return try: dev = self.ble_devs[int(args[0])] except: print("error: unable to find given device index") return try: self.periph.connect(dev.addr, dev.addrType) services = self.periph.getServices() for i, service in enumerate(services): print(" Service {:2} UUID: {} ({})".format( i, service.uuid, service.uuid.getCommonName())) chars = service.getCharacteristics() type(chars) for i, char in enumerate(chars): self.chars[char.getHandle()] = char print("{:5} Char {:2} UUID: {} ({})".format( char.getHandle(), i, char.uuid, char.uuid.getCommonName())) # if char.supportsRead(): # tmp = char.read() # print("Data: ", str(tmp)) self.state = self.State.CONNECTED except: print("error: while conneting something was bad") return def do_disconnect(self, line): """Close any open connection """ self.periph.disconnect() self.chars = dict() self.state = self.State.IDLE print(self.periph.addr) def do_read(self, line): try: handle = int(line.strip()) char = self.chars[handle] if not char.supportsRead(): print("error: characteristic is not readable") else: buf = char.read() print("out: {}".format(buf.decode('utf-8'))) except: print("usage: read <handle>") return def do_write(self, line): cmd = line.strip().partition(' ') if not cmd[2]: print("usage: write <handle> <data>") return try: handle = int(cmd[0]) char = self.chars[handle] char.write(cmd[2].encode('utf-8')) except: print("error: unable to find characteristic")
class BluefruitMonitor(threading.Thread): monitor = "OFF" txUUID = "6e400002-b5a3-f393-e0a9-e50e24dcca9e" rxUUID = "6e400003-b5a3-f393-e0a9-e50e24dcca9e" def __init__(self, mac, notificationDelgate): threading.Thread.__init__(self) self.daemon = True self.addr = mac try: self.p = Peripheral(mac, "random") self.notificationDelgate = notificationDelgate print("New BluefruitMonitor created for device: " + self.p.addr) except: print "Not connected" return None #def startMonitor(self): def run(self): print self.addr + ": In run method for BluefruitMonitor" try: self.rxh = self.p.getCharacteristics(uuid=self.rxUUID)[0] except AttributeError as a: print(str(datetime.now()) + " AttributeError: " + a.message) print( str(datetime.now()) + " Will try to connect to Peripheral...") try: self.p = Peripheral(self.addr, "random") self.notificationDelgate = self.notificationDelgate print( str(datetime.now()) + " Peripheral connected successfully!") self.rxh = self.p.getCharacteristics(uuid=self.rxUUID)[0] except BTLEException as e: print(str(datetime.now()) + " BTLEException: " + e.message) return 0 except: e2 = e = sys.exc_info()[0] print( str(datetime.now()) + " BluefruitMonitor Error on call to TX: %s" % e.message) return 0 except: e = sys.exc_info()[0] print( str(datetime.now()) + " BluefruitMonitor Error on call to TX: %s" % e.message) return 0 self.txCharacteristic = self.p.getCharacteristics(uuid=self.txUUID)[0] print("RX handle: " + str(self.rxh.getHandle())) # Note setDelegate method has been replaced by withDelegate. Change this self.p.setDelegate( BluefruitDelegate(self.rxh.getHandle(), self.addr, self.notificationDelgate)) try: # Turn on notifications. If 35 isn't your handle run hcidump in one window, bluetoothctl in another # then connect, select-atrribute for RX then set "notify on". # Inspect the hcidump log for the handle associated with "Write req" print "Configuring RX to notify me on change" self.p.writeCharacteristic(35, b"\x01\x00", withResponse=True) self.monitor = "ON" #return 0 except: e = sys.exc_info()[0] print("BluefruitMonitor Error: %s" % e) try: self.p.disconnect() except: return 0 while True: try: if self.p.waitForNotifications(1): msg = self.p.delegate.getLastMessage() if msg != 0 and msg is not None: self.txCharacteristic.write(msg) self.clearMessage() except BTLEException: print BTLEException.message return 0 # Add callback to remove device def clearMessage(self): self.p.delegate.clearMessage() def stopMonitor(self): self.monitor = "OFF" self.p.writeCharacteristic(35, '\x00', withResponse=False) # Trying some error handling...dunno if it will work def reconnect(self): try: self.p.connect(self.addr, "random") except: print BTLEException.message return 0
def __get_services_and_chars(self): for device in self.__devices_around: try: if self.__devices_around.get( device) is not None and self.__devices_around[ device].get('scanned_device') is not None: log.debug( 'Connecting to device with address: %s', self.__devices_around[device] ['scanned_device'].addr.upper()) if self.__devices_around[device].get('peripheral') is None: address_type = self.__devices_around[device][ 'device_config'].get('addrType', "public") peripheral = Peripheral( self.__devices_around[device]['scanned_device'], address_type) self.__devices_around[device][ 'peripheral'] = peripheral else: peripheral = self.__devices_around[device][ 'peripheral'] try: peripheral.connect(self.__devices_around[device] ['scanned_device']) except Exception as e: log.exception(e) services = peripheral.getServices() for service in services: if self.__devices_around[device].get( 'services') is None: log.debug( 'Building device %s map, it may take a time, please wait...', device) self.__devices_around[device]['services'] = {} service_uuid = str(service.uuid).upper() if self.__devices_around[device]['services'].get( service_uuid) is None: self.__devices_around[device]['services'][ service_uuid] = {} try: characteristics = service.getCharacteristics() except BTLEDisconnectError: self.__check_and_reconnect(device) characteristics = service.getCharacteristics() if self.__config.get('buildDevicesMap', False): for characteristic in characteristics: descriptors = [] try: self.__check_and_reconnect(device) try: descriptors = characteristic.getDescriptors( ) except BTLEDisconnectError: self.__check_and_reconnect(device) descriptors = characteristic.getDescriptors( ) except BTLEGattError as e: log.debug(e) except Exception as e: log.exception(e) characteristic_uuid = str( characteristic.uuid).upper() if self.__devices_around[device][ 'services'][service_uuid].get( characteristic_uuid ) is None: self.__devices_around[device][ 'services'][service_uuid][ characteristic_uuid] = { 'characteristic': characteristic, 'handle': characteristic.handle, 'descriptors': {} } for descriptor in descriptors: log.debug(descriptor.handle) log.debug(str(descriptor.uuid)) log.debug(str(descriptor)) self.__devices_around[device][ 'services'][service_uuid][ characteristic_uuid][ 'descriptors'][ descriptor. handle] = descriptor except BTLEDisconnectError: self.__check_and_reconnect(device) else: for characteristic in characteristics: characteristic_uuid = str( characteristic.uuid).upper() self.__devices_around[device]['services'][ service_uuid][characteristic_uuid] = { 'characteristic': characteristic, 'handle': characteristic.handle } if self.__devices_around[device]['is_new_device']: log.debug('New device %s - processing.', device) self.__devices_around[device]['is_new_device'] = False self.__new_device_processing(device) for interest_char in self.__devices_around[device][ 'interest_uuid']: for section in self.__devices_around[device][ 'interest_uuid'][interest_char]: data = self.__service_processing( device, section['section_config']) converter = section['converter'] converted_data = converter.convert(section, data) self.statistics[ 'MessagesReceived'] = self.statistics[ 'MessagesReceived'] + 1 log.debug(data) log.debug(converted_data) self.__gateway.send_to_storage( self.get_name(), converted_data) self.statistics['MessagesSent'] = self.statistics[ 'MessagesSent'] + 1 except BTLEDisconnectError: log.debug('Cannot connect to device %s', device) continue except Exception as e: log.exception(e)
import struct import time from bluepy.btle import UUID, Peripheral GPIO.setwarnings(False) detection_UUID = UUID(0x180C) p = Peripheral('f0:24:d3:40:e2:00', 'random') lcd = CharLCD(numbering_mode=GPIO.BOARD, cols=16, rows=2, pin_rs=37, pin_e=35, pins_data=[33, 31, 29, 23]) try: p.connect('f0:24:d3:40:e2:00', 'random') ch = p.getCharacteristic(uuid=detection_UUID)[0] if (ch.supportsRead()): while 1: vesselPresence = binascii.b2a_hex(ch.read()) vesselPresence = binascii.unhexlify(vesselPresence) vesselPresence = struct.unpack('f', vesselPresence)[0] display = "Detected: " + str(vesselPresence) print(display) lcd.write_string(display) lcd.cursor_pos = (1, 3) finally: p.disconnect()
def __get_services_and_chars(self): for device in self.__devices_around: try: if self.__devices_around.get( device) is not None and self.__devices_around[ device].get('scanned_device') is not None: log.debug('Connecting to device: %s', device) if self.__devices_around[device].get('peripheral') is None: address_type = self.__devices_around[device][ 'device_config'].get('addrType', "public") peripheral = Peripheral( self.__devices_around[device]['scanned_device'], address_type) self.__devices_around[device][ 'peripheral'] = peripheral else: peripheral = self.__devices_around[device][ 'peripheral'] try: log.info(peripheral.getState()) except BTLEInternalError: peripheral.connect( self.__devices_around[device]['scanned_device']) try: services = peripheral.getServices() except BTLEDisconnectError: self.__check_and_reconnect(device) services = peripheral.getServices() for service in services: if self.__devices_around[device].get( 'services') is None: log.debug( 'Building device %s map, it may take a time, please wait...', device) self.__devices_around[device]['services'] = {} service_uuid = str(service.uuid).upper() if self.__devices_around[device]['services'].get( service_uuid) is None: self.__devices_around[device]['services'][ service_uuid] = {} try: characteristics = service.getCharacteristics() except BTLEDisconnectError: self.__check_and_reconnect(device) characteristics = service.getCharacteristics() if self.__config.get('buildDevicesMap', False): for characteristic in characteristics: descriptors = [] self.__check_and_reconnect(device) try: descriptors = characteristic.getDescriptors( ) except BTLEDisconnectError: self.__check_and_reconnect(device) descriptors = characteristic.getDescriptors( ) except BTLEGattError as e: log.debug(e) except Exception as e: log.exception(e) characteristic_uuid = str( characteristic.uuid).upper() if self.__devices_around[device][ 'services'][service_uuid].get( characteristic_uuid) is None: self.__check_and_reconnect(device) self.__devices_around[device][ 'services'][service_uuid][ characteristic_uuid] = { 'characteristic': characteristic, 'handle': characteristic.handle, 'descriptors': {} } for descriptor in descriptors: log.debug(descriptor.handle) log.debug(str(descriptor.uuid)) log.debug(str(descriptor)) self.__devices_around[device][ 'services'][service_uuid][ characteristic_uuid][ 'descriptors'][ descriptor. handle] = descriptor else: for characteristic in characteristics: characteristic_uuid = str( characteristic.uuid).upper() self.__devices_around[device]['services'][ service_uuid][characteristic_uuid] = { 'characteristic': characteristic, 'handle': characteristic.handle } if self.__devices_around[device]['is_new_device']: log.debug('New device %s - processing.', device) self.__devices_around[device]['is_new_device'] = False self.__new_device_processing(device) for interest_char in self.__devices_around[device][ 'interest_uuid']: characteristics_configs_for_processing_by_methods = {} for configuration_section in self.__devices_around[ device]['interest_uuid'][interest_char]: characteristic_uuid_from_config = configuration_section[ 'section_config'].get("characteristicUUID") if characteristic_uuid_from_config is None: log.error( 'Characteristic not found in config: %s', pformat(configuration_section)) continue method = configuration_section[ 'section_config'].get('method') if method is None: log.error('Method not found in config: %s', pformat(configuration_section)) continue characteristics_configs_for_processing_by_methods[ method.upper()] = { "method": method, "characteristicUUID": characteristic_uuid_from_config } for method in characteristics_configs_for_processing_by_methods: data = self.__service_processing( device, characteristics_configs_for_processing_by_methods[ method]) for section in self.__devices_around[device][ 'interest_uuid'][interest_char]: converter = section['converter'] converted_data = converter.convert( section, data) self.statistics[ 'MessagesReceived'] = self.statistics[ 'MessagesReceived'] + 1 log.debug(data) log.debug(converted_data) self.__gateway.send_to_storage( self.get_name(), converted_data) self.statistics[ 'MessagesSent'] = self.statistics[ 'MessagesSent'] + 1 except BTLEDisconnectError: log.debug('Connection lost. Device %s', device) continue except Exception as e: log.exception(e)
class BluetoothLE: def __init__(self, deviceAddress, deviceType, addrType, mqttConnection): self.deviceAddress = deviceAddress self.deviceType = deviceType self.mqtt = mqttConnection self.addrType = addrType self.bleConnection = None self.ConnectToBLEDevice(True) def ConnectToBLEDevice(self, createNew): print "Connecting to device ", self.deviceType if createNew: self.bleConnection = Peripheral(self.deviceAddress, self.addrType) self.mqtt.setDeviceStatus(self, True) else: self.bleConnection.connect(self.deviceAddress, self.addrType) print "Connected to " + self.deviceType thread.start_new_thread(self.ReadLoop, ()) text_characters = "".join(map(chr, range(32, 127))) _null_trans = string.maketrans("", "") def isText(self, s, text_characters=text_characters, threshold=0.30): # if s contains any null, it's not text: if "\x00" in s: return False # an "empty" string is "text" (arbitrary but reasonable choice): if not s: return True # Get the substring of s made up of non-text characters t = s.translate(self._null_trans, text_characters) # s is 'text' if less than 30% of its characters are non-text ones: return len(t)/len(s) <= threshold def formatValue(self, value): #We need to determine if we have hex data or a character string #The best we can do, without hard coding characteristic ID's is to #see if the valueis alphanumeric or contains only 1 or 2 characters. #If the value is not alphanumeric or is a length of 1 or 2, assume it is hex data theValue = value if not self.isText(theValue) or len(theValue) ==1 or len(theValue) == 2: #Assume the data is hex. We now need to unpack it if len(theValue) == 1: #Assume signed char theValue = struct.unpack('b', theValue)[0] elif len(theValue) ==2: #Assume short theValue = struct.unpack('h', theValue)[0] elif len(theValue) == 4: #Assume long theValue = struct.unpack('l', theValue)[0] elif len(theValue) == 8: #Assume long long theValue = struct.unpack('q', theValue)[0] else: #Only other thing we can do theValue = theValue.encode('string-escape') return theValue def ReadLoop(self): readData = {} characteristics = self.bleConnection.getCharacteristics() for char in characteristics: if char.supportsRead(): try: value = self.formatValue(char.read()) readData[str(char.uuid.getCommonName())] = value except: print "Failed to read data for uuid ", str(char.uuid.getCommonName()) print sys.exc_info() readData["services"] = [] services = self.bleConnection.getServices() for service in services: serviceData = {} serviceData["name"] = str(service.uuid.getCommonName()) serviceChars = service.getCharacteristics() for serviceChar in serviceChars: if serviceChar.supportsRead(): try: value = self.formatValue(serviceChar.read()) serviceData[str(serviceChar.uuid.getCommonName())] = value except: print "Failed to read data for uuid ", str(serviceChar.uuid.getCommonName()) print sys.exc_info() if serviceData: readData["services"].append(serviceData) print readData message = {} message["deviceAddress"] = self.deviceAddress message["deviceType"] = self.deviceType message["readData"] = str(readData) self.mqtt.PublishMessage(json.dumps(message), "BLEReadData", None) #Disconnect the peripheral once we are done reading data self.bleConnection.disconnect() def ReadValue(self, uuid): self.bleConnection.connect(self.deviceAddress, self.addrType) characteristics = self.bleConnection.getCharacteristics(uuid=uuid)[0] readValue = self.formatValue(characteristics.read()) messageToPublish = {} messageToPublish["deviceType"] = self.deviceType messageToPublish["deviceAddress"] = self.deviceAddress messageToPublish["readData"] = readValue self.mqtt.PublishMessage(json.dumps(messageToPublish), "BLEReadData", None) self.bleConnection.disconnect() print "Successfully read data from device. Data value = ", readValue def WriteValue(self, data, uuid): self.bleConnection.connect(self.deviceAddress, self.addrType) uuidValue = UUID(uuid) characteristics = self.bleConnection.getCharacteristics(uuid=uuid)[0] characteristics.write(data, True) print "Successfully wrote data to device " + self.deviceAddress self.bleConnection.disconnect()
if(g_strMACAddress != "00:00:00:00:00:00"): #temp_uuid = UUID(0x2221) uuid_soc = UUID("D3EDE59D-0CEE-433D-85AD-81B40BC51C3E") uuid_cobid = UUID("54ED5C0A-8E0C-48D8-803C-7D20CE52DCA7") uuid_byteno = UUID("F82A561C-0259-49C0-A40D-63ADE522A7ff") #p = Peripheral("5D:44:A6:38:CB:E5", "random") #p = Peripheral(g_foundDevice.addr, "random") try: p = Peripheral() devaddr = g_strMACAddress print "try to open " + devaddr p.connect(devaddr, "public") chread_soc = p.getCharacteristics(uuid=uuid_soc)[0] chwrite_cobid = p.getCharacteristics(uuid=uuid_cobid)[0] chwrite_byteno = p.getCharacteristics(uuid=uuid_byteno)[0] print "characteristics done" #write cobid try: g_intCOBID = int(g_strCOBID, 16) lsb = "".join(chr(g_intCOBID & 0xFF)) msb = "".join(chr((g_intCOBID & 0xFF00) >> 8)) chwrite_cobid.write(lsb + msb) except: print "Error: False format in COBID string"
def worker_thread(self, log, address, wrque, que): """ Background thread that handles bluetooth sending and forwards data received via bluetooth to the queue `que`. """ mil = None message_delta_time = 0.1 # least 0.1 sec between outgoing btle messages rx = None tx = None log.debug("bluepy_ble open_mt {}".format(address)) # time.sleep(0.1) try: log.debug("per1") mil = Peripheral(address) log.debug("per2") except Exception as e: log.debug("per3") emsg = 'Failed to create BLE peripheral at {}, {}'.format( address, e) log.error(emsg) self.agent_state(que, 'offline', '{}'.format(e)) self.conn_state = False return rx, tx = self.mil_open(address, mil, que, log) time_last_out = time.time()+0.2 if rx is None or tx is None: bt_error = True self.conn_state = False else: bt_error = False self.conn_state = True while self.worker_thread_active is True: rep_err = False while bt_error is True: time.sleep(1) bt_error = False self.init = False try: mil.connect(address) except Exception as e: if rep_err is False: self.log.warning( "Reconnect failed: {} [Local bluetooth problem?]".format(e)) rep_err = True bt_error = True if bt_error is False: self.log.info( "Bluetooth reconnected to {}".format(address)) rx, tx = self.mil_open(address, mil, que, log) time_last_out = time.time()+0.2 self.init = True if wrque.empty() is False and time.time()-time_last_out > message_delta_time: msg = wrque.get() gpar = 0 for b in msg: gpar = gpar ^ ord(b) msg = msg+clp.hex2(gpar) if self.protocol_debug is True: log.debug("blue_ble write: <{}>".format(msg)) bts = "" for c in msg: bo = chr(clp.add_odd_par(c)) bts += bo btsx = bts.encode('latin1') if self.protocol_debug is True: log.debug("Sending: <{}>".format(btsx)) try: tx.write(btsx, withResponse=True) time_last_out = time.time() except Exception as e: log.error( "bluepy_ble: failed to write {}: {}".format(msg, e)) bt_error = True self.agent_state( que, 'offline', 'Connected to Bluetooth peripheral lost: {}'.format(e)) wrque.task_done() try: rx.read() mil.waitForNotifications(0.05) # time.sleep(0.1) except Exception as e: self.log.warning("Bluetooth error {}".format(e)) bt_error = True self.agent_state( que, 'offline', 'Connected to Bluetooth peripheral lost: {}'.format(e)) continue log.debug('wt-end')
#!/usr/bin/env python import binascii import struct import time from bluepy.btle import UUID, Peripheral, BTLEDisconnectError, BTLEInternalError adc_uuid = UUID(0x2A6E) p = Peripheral() p.connect("bt:ma:ca:dd:re:ss", "public") #replace with mac address of the ESP32 def main(): print("connecting...") ch = p.getCharacteristics(uuid=adc_uuid)[0] if (ch.supportsRead()): while 1: try: val = binascii.b2a_hex(ch.read()) val = binascii.unhexlify(val) val = struct.unpack('<h', val)[0] print str(val) time.sleep(.01) except BTLEDisconnectError: print "Device disconnected!" continue except BTLEInternalError: print "internal error... ignoring"
# Create a scanner with the handler as delegate scanner = Scanner().withDelegate(handler) # Start scanning. While scanning, handleDiscovery will be called whenever a new device or new data is found devs = scanner.scan(10.0) # Get HEXIWEAR's address hexi_addr = [dev for dev in devs if dev.getValueText(0x8) == 'HEXIWEAR'][0].addr # hexi_addr = '00:2A:40:08:00:10' # Create a Peripheral object with the delegate hexi = Peripheral().withDelegate(handler) # Connect to Hexiwear hexi.connect(hexi_addr) # Get the battery service battery = hexi.getCharacteristics(uuid="2a19")[0] # battery = hexi.getCharacteristics(uuid="2001")[0] # Get the client configuration descriptor and write 1 to it to enable notification battery_desc = battery.getDescriptors(forUUID=0x2902)[0] # battery_desc = battery.getDescriptors(forUUID=0x2001)[0] battery_desc.write(b"\x01", True) # Infinite loop to receive notifications while True: hexi.waitForNotifications(1.0)
def main(): bluetooth_adr = sys.argv[1].lower() port = int(sys.argv[2]) host = get_network_interface_ip_address(sys.argv[3]) #BT HCI 0 or 1 iface = int(sys.argv[4]) print "Will follow broadcasts from: ", bluetooth_adr, ". SmartThings DeviceHandler will have to be configured with IP:", host, " and port: ", port print "hci used: ", iface mac1, mac2, mac3, mac4, mac5, mac6 = bluetooth_adr.split(':') status = HFPStatus() perif = Peripheral() perif.setDelegate(HFPProDelegate(bluetooth_adr, 'init', status)) #wait for commands s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # Create a socket object s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) s.settimeout(5.0) print "Connecting", while True: try: perif.connect(bluetooth_adr, iface=iface) perif.writeCharacteristic( 0x25, "\x4d\x41\x43\x2b" + binascii.unhexlify(mac1) + binascii.unhexlify(mac2) + binascii.unhexlify(mac3) + binascii.unhexlify(mac4) + binascii.unhexlify(mac5) + binascii.unhexlify(mac6)) perif.readCharacteristic(0x2e) perif.writeCharacteristic(0x2F, b"\x01\x00", withResponse=True) break except BTLEException: print ".", print "" print "Connected to ", bluetooth_adr try: s.bind((host, port)) # Bind to the port except: while True: sleep(10) try: s.bind((host, port)) break except: continue if s != None: while True: try: # reactivate BT link by writing the Notification Characatestic every 5 sec perif.writeCharacteristic(0x2F, b"\x01\x00", withResponse=True) conn = None command = None s.listen(5) try: conn, addr = s.accept( ) # Establish connection with client. #print 'Got connection from', addr data = conn.recv(1024) print('Server received', repr(data)) #extract the command from 'GET /api/commmand/value command = re.findall('GET /api/(\S*)/', repr(data)) if command != None: print command[0] # Parse value value = re.findall('GET /api/' + command[0] + '/(\S*)', repr(data)) if value != None: #Commands if command[0] == 'fanspeed': # Fan Speed # /api/fanspeed/4 turbo # /api/fanspeed/3 allergen # /api/fanspeed/2 general_on # /api/fanspeed/1 germ # /api/fanspeed/0 off if status.getFanSpeed() != value[0]: if value[0] == 'turbo': #'turbo': sendBtCmd( perif, HFPProDelegate, binascii.unhexlify( 'a510009316000000000000000000000000000000' )) elif value[0] == 'allergen': #'allergen: sendBtCmd( perif, HFPProDelegate, binascii.unhexlify( 'a508009316000000000000000000000000000000' )) elif value[0] == 'general_on': #'general': sendBtCmd( perif, HFPProDelegate, binascii.unhexlify( 'a504009316000000000000000000000000000000' )) elif value[0] == 'germ': #'germ': if status.getFanSpeed() == 'off': sendBtCmd( perif, HFPProDelegate, binascii.unhexlify( 'a501009316000000000000000000000000000000' )) else: sendBtCmd( perif, HFPProDelegate, binascii.unhexlify( 'a502009316000000000000000000000000000000' )) elif value[0] == 'off': #'off': sendBtCmd( perif, HFPProDelegate, binascii.unhexlify( 'a501009316000000000000000000000000000000' )) else: print "Malformed/unknown value" elif command[0] == 'light': # /api/ligth/on # /api/light/medium # /api/light/off if value[0] == 'on': sendBtCmd( perif, HFPProDelegate, binascii.unhexlify( 'a500049316000000000000000000000000000000' )) elif value[0] == 'medium': sendBtCmd( perif, HFPProDelegate, binascii.unhexlify( 'a500049316000000000000000000000000000000' )) elif value[0] == 'off': sendBtCmd( perif, HFPProDelegate, binascii.unhexlify( 'a500049316000000000000000000000000000000' )) else: print "Malformed/unknown value" elif command[0] == 'timer': # /api/timer/plus # /api/timer/minus if value[0] == 'plus': sendBtCmd( perif, HFPProDelegate, binascii.unhexlify( 'a500019316000000000000000000000000000000' )) elif value[0] == 'minus': sendBtCmd( perif, HFPProDelegate, binascii.unhexlify( 'a500029316000000000000000000000000000000' )) else: print "Malformed/unknown value" elif command[0] == 'voc': # /api/voc/on # /api/voc/off if value[0] == 'on': sendBtCmd( perif, HFPProDelegate, binascii.unhexlify( 'a520009316000000000000000000000000000000' )) elif value[0] == 'off': sendBtCmd( perif, HFPProDelegate, binascii.unhexlify( 'a520009316000000000000000000000000000000' )) else: print "Malformed/unknown value" elif command[0] == 'refresh': # do nothing and just resent the last status. Maybe notification(s) were received in-between to status got update despite the Smartthings side didn't get refreshed yet. print "Refresh will be sent" else: print "Unkown command" else: print "Malformed value" #else: # print "Malformed command" if conn != None: conn.send( 'HTTP/1.1 200 OK\nContent-Type: application/json\n\n' + status.status()) conn.close() except socket.timeout: #not received anything. perif.waitForNotifications(1.0) pass except BTLEException as e: print 'Error on line {}'.format(sys.exc_info()[-1].tb_lineno) print e while True: try: print "Trying to reach again ", bluetooth_adr perif.connect(bluetooth_adr, iface=iface) perif.writeCharacteristic( 0x25, "\x4d\x41\x43\x2b" + binascii.unhexlify(mac1) + binascii.unhexlify(mac2) + binascii.unhexlify(mac3) + binascii.unhexlify(mac4) + binascii.unhexlify(mac5) + binascii.unhexlify(mac6)) perif.readCharacteristic(0x2e) perif.writeCharacteristic(0x2F, b"\x01\x00", withResponse=True) print "Re-connected BT-LE target" break except: pass
# if isNewDev: # print ("Discovered device", dev.addr) # elif isNewData: # print ("Received new data from", dev.addr) scanner = Scanner()#.withDelegate(ScanDelegate()) scanner.scan(10.0) devices = scanner.getDevices() for i, device in enumerate(devices): print(f"\n~~~~~~ Device {i} ~~~~~~") print(f"Device Address: {device.addr}, RSSI: {device.rssi}, Connectable: {device.connectable}") for (adtype, desc, value) in device.getScanData(): print (f"AD Type {adtype}, Type Name: {desc}, Value: {value}") print("\n>>>>>>>>>> Connect to Arduino Nano 33 BLE and read characteristics <<<<<<<<<<\n") arduino = Peripheral(addrType=ADDR_TYPE_PUBLIC, iface=None) for device in devices: if device.addr == "e9:be:2d:f0:66:70": arduino.connect(device) for characteristic in arduino.getCharacteristics(): print(f"Handle: {characteristic.handle}, UUID Name: {UUID.getCommonName(characteristic.uuid)}, Value Handle: {characteristic.valHandle}, Properties: {characteristic.propertiesToString()}") if characteristic.supportsRead(): print(f"{characteristic.uuid} : {characteristic.read().decode()}") #print(arduino.readCharacteristic(12).decode()) #print(int.from_bytes(arduino.readCharacteristic(12), sys.byteorder)) #use this if receiving bytes #print(arduino.readCharacteristic(13)) #print(arduino.readCharacteristic(22).decode('utf-8'))
class SHT31(): def __init__(self, addr = None, iface = None): self.loggedDataReadout = {'Temp' : {}, 'Humi': {}} self.loggedData = {'Temp' : {}, 'Humi': {}} self.__loggerInterval = 0 self.__loggingReadout = False self.__peripheral = Peripheral(addr, 'random', iface) if addr is not None: self.__peripheral.setDelegate(SHT31Delegate(self)) self.__prepareGadget() def __prepareGadget(self): self.__characteristics = {} # READ self.__characteristics['SystemId'] = self.__peripheral.getCharacteristics(uuid=UUID(0x2A23))[0] # READ self.__characteristics['ManufacturerNameString'] = self.__peripheral.getCharacteristics(uuid=UUID(0x2A29))[0] # READ self.__characteristics['ModelNumberString'] = self.__peripheral.getCharacteristics(uuid=UUID(0x2A24))[0] # READ self.__characteristics['SerialNumberString'] = self.__peripheral.getCharacteristics(uuid=UUID(0x2A25))[0] # READ self.__characteristics['HardwareRevisionString'] = self.__peripheral.getCharacteristics(uuid=UUID(0x2A27))[0] # READ self.__characteristics['FirmwareRevisionString'] = self.__peripheral.getCharacteristics(uuid=UUID(0x2A26))[0] # READ self.__characteristics['SoftwareRevisionString'] = self.__peripheral.getCharacteristics(uuid=UUID(0x2A28))[0] # READ WRITE self.__characteristics['DeviceName'] = self.__peripheral.getCharacteristics(uuid=UUID("00002a00-0000-1000-8000-00805f9b34fb"))[0] # READ NOTIFY self.__characteristics['Battery'] = self.__peripheral.getCharacteristics(uuid=UUID(0x2A19))[0] # WRITE self.__characteristics['SyncTimeMs'] = self.__peripheral.getCharacteristics(uuid=UUID("0000f235-b38d-4985-720e-0f993a68ee41"))[0] # READ WRITE self.__characteristics['OldestTimeStampMs'] = self.__peripheral.getCharacteristics(uuid=UUID("0000f236-b38d-4985-720e-0f993a68ee41"))[0] # READ WRITE self.__characteristics['NewestTimeStampMs'] = self.__peripheral.getCharacteristics(uuid=UUID("0000f237-b38d-4985-720e-0f993a68ee41"))[0] # WRITE NOTIFY self.__characteristics['StartLoggerDownload'] = self.__peripheral.getCharacteristics(uuid=UUID("0000f238-b38d-4985-720e-0f993a68ee41"))[0] # READ WRITE self.__characteristics['LoggerIntervalMs'] = self.__peripheral.getCharacteristics(uuid=UUID("0000f239-b38d-4985-720e-0f993a68ee41"))[0] # READ NOTIFY self.__characteristics['Humidity'] = self.__peripheral.getCharacteristics(uuid=UUID("00001235-b38d-4985-720e-0f993a68ee41"))[0] # READ NOTIFY self.__characteristics['Temperature'] = self.__peripheral.getCharacteristics(uuid=UUID("00002235-b38d-4985-720e-0f993a68ee41"))[0] if self.readFirmwareRevisionString() == '1.3': # Error in the documentation/firmware of 1.3 runnumber does not start with 0 it starts with 1, therefore insert an offset here self.__peripheral.delegate.offset = 1 def connect(self, addr, iface=None): self.__peripheral.setDelegate(SHT31Delegate(self)) self.__peripheral.connect(addr, 'random', iface) self.__prepareGadget() def disconnect(self): self.__peripheral.disconnect() def __readCharacteristcAscii(self, name): return self.__characteristics[name].read().decode('ascii') def readDeviceName(self): return self.__readCharacteristcAscii('DeviceName') def setDeviceName(self, name): return self.__characteristics['DeviceName'].write(name.encode('ascii')) def readTemperature(self): return struct.unpack('f', self.__characteristics['Temperature'].read())[0] def setTemperatureNotification(self, enabled): tmp = 1 if enabled else 0 self.__peripheral.delegate.enabledNotifications['Temp'] = enabled self.__setTemperatureNotification(tmp) def __setTemperatureNotification(self, byte): self.__peripheral.writeCharacteristic(self.__characteristics['Temperature'].valHandle+2, int(byte).to_bytes(1, byteorder = 'little')) def readHumidity(self): return struct.unpack('f', self.__characteristics['Humidity'].read())[0] def setHumidityNotification(self, enabled): tmp = 1 if enabled else 0 self.__peripheral.delegate.enabledNotifications['Humi'] = enabled self.__setHumidityNotification(tmp) def __setHumidityNotification(self, byte): self.__peripheral.writeCharacteristic(self.__characteristics['Humidity'].valHandle+2, int(byte).to_bytes(1, byteorder = 'little')) def readBattery(self): return int.from_bytes(self.__characteristics['Battery'].read(), byteorder='little') def setSyncTimeMs(self, timestamp = None): timestampMs = timestamp if timestamp else int(round(time.time() * 1000)) self.__characteristics['SyncTimeMs'].write(timestampMs.to_bytes(8, byteorder='little')) def readOldestTimestampMs(self): return int.from_bytes(self.__characteristics['OldestTimeStampMs'].read(), byteorder='little') def setOldestTimestampMs(self, value): self.__characteristics['OldestTimeStampMs'].write(value.to_bytes(8, byteorder='little')) def readNewestTimestampMs(self): return int.from_bytes(self.__characteristics['NewestTimeStampMs'].read(), byteorder='little') def setNewestTimestampMs(self, value): self.__characteristics['NewestTimeStampMs'].write(value.to_bytes(8, byteorder='little')) def readLoggerIntervalMs(self): return int.from_bytes(self.__characteristics['LoggerIntervalMs'].read(), byteorder='little') def setLoggerIntervalMs(self, interval): oneMonthInMs = (30 * 24 * 60 * 60 * 1000) interval = 1000 if interval < 1000 else oneMonthInMs if interval > oneMonthInMs else interval self.__characteristics['LoggerIntervalMs'].write((int(interval)).to_bytes(4, byteorder='little')) def readLoggedDataInterval(self, startMs = None, stopMs = None): self.setSyncTimeMs() time.sleep(0.1) # Sleep a bit to enable the gadget to set the SyncTime; otherwise 0 is read when readNewestTimestampMs is used self.__setTemperatureNotification(1) self.__setHumidityNotification(1) if startMs is not None: self.setOldestTimestampMs(startMs) else: self.setOldestTimestampMs(0) if stopMs is not None: self.setNewestTimestampMs(stopMs) # else: # self.setNewestTimestampMs(0) tmpNewestTimestamp = self.readNewestTimestampMs() #print(tmpNewestTimestamp) self.__peripheral.delegate.prepareLoggerReadout(self.readLoggerIntervalMs(), tmpNewestTimestamp) self.__characteristics['StartLoggerDownload'].write((1).to_bytes(1, byteorder='little')) def waitForNotifications(self, timeout): return self.__peripheral.waitForNotifications(timeout) def isLogReadoutInProgress(self): return self.__peripheral.delegate.loggingReadout def readSystemId(self): return self.__characteristics['SystemId'].read() def readManufacturerNameString(self): return self.__readCharacteristcAscii('ManufacturerNameString') def readModelNumberString(self): return self.__readCharacteristcAscii('ModelNumberString') def readSerialNumberString(self): return self.__readCharacteristcAscii('SerialNumberString') def readHardwareRevisionString(self): return self.__readCharacteristcAscii('HardwareRevisionString') def readFirmwareRevisionString(self): return self.__readCharacteristcAscii('FirmwareRevisionString') def readSoftwareRevisionString(self): return self.__readCharacteristcAscii('SoftwareRevisionString')
class GForceProfile(): def __init__(self): self.device = Peripheral() self.state = BluetoothDeviceState.disconnected self.cmdCharacteristic = None self.notifyCharacteristic = None self.timer = None self.cmdMap = {} self.mtu = None self.cmdForTimeout = -1 self.incompleteCmdRespPacket = [] self.lastIncompleteCmdRespPacketId = 0 self.incompleteNotifPacket = [] self.lastIncompleteNotifPacketId = 0 self.onData = None self.lock = threading.Lock() self.send_queue = queue.Queue(maxsize=20) def getCharacteristic(self, device, uuid): ches = device.getCharacteristics() for ch in ches: if uuid == str(ch.uuid): return ch else: continue # Establishes a connection to the Bluetooth Device. def connect(self, addr): self.device.connect(addr) print('connection succeeded') # set mtu MTU = self.device.setMTU(200) self.mtu = MTU['mtu'][0] # self.device.setMTU(self.mtu) # print('mtu:{}'.format(self.mtu)) self.state = BluetoothDeviceState.connected self.cmdCharacteristic = self.getCharacteristic(self.device, CMD_NOTIFY_CHAR_UUID) self.notifyCharacteristic = self.getCharacteristic( self.device, DATA_NOTIFY_CHAR_UUID) # Listen cmd self.setNotify(self.cmdCharacteristic, True) # Open the listening thread self.device.setDelegate(MyDelegate(self)) # Connect the bracelet with the strongest signal def connectByRssi(self): scanner = Scanner() devices = scanner.scan(10.0) rssi_devices = {} for dev in devices: print("Device %s (%s), RSSI=%d dB" % (dev.addr, dev.addrType, dev.rssi)) for (_, desc, value) in dev.getScanData(): print(" %s = %s" % (desc, value)) if (value == SERVICE_GUID): rssi_devices[dev.rssi] = dev.addr rssi = rssi_devices.keys() dev_addr = rssi_devices[max(rssi)] # connect the bracelet self.device.connect(dev_addr) print('connection succeeded') # set mtu MTU = self.device.setMTU(2000) self.mtu = MTU['mtu'][0] # self.device.setMTU(self.mtu) # print('mtu:{}'.format(self.mtu)) self.state = BluetoothDeviceState.connected self.cmdCharacteristic = self.getCharacteristic(self.device, CMD_NOTIFY_CHAR_UUID) self.notifyCharacteristic = self.getCharacteristic( self.device, DATA_NOTIFY_CHAR_UUID) # Listen cmd self.setNotify(self.cmdCharacteristic, True) # Open the listening thread self.device.setDelegate(MyDelegate(self)) # Enable a characteristic's notification def setNotify(self, Chara, swich): if swich: setup_data = b"\x01\x00" else: setup_data = b"\x00\x00" setup_handle = Chara.getHandle() + 1 self.device.writeCharacteristic( setup_handle, setup_data, withResponse=False) def scan(self, timeout): scanner = Scanner() devices = scanner.scan(timeout) gforce_scan = [] i = 1 for dev in devices: for (_, _, value) in dev.getScanData(): if (value == SERVICE_GUID): gforce_scan.append([i, dev.getValueText( 9), dev.addr, dev.rssi, str(dev.connectable)]) i += 1 return gforce_scan # Disconnect from device def disconnect(self): if self.timer != None: self.timer.cancel() self.timer = None # Close the listenThread if self.state == BluetoothDeviceState.disconnected: return True else: self.device.disconnect() self.state == BluetoothDeviceState.disconnected # Set data notification flag def setDataNotifSwitch(self, flags, cb, timeout): # Pack data data = [] data.append(CommandType['CMD_SET_DATA_NOTIF_SWITCH']) data.append(0xFF & (flags)) data.append(0xFF & (flags >> 8)) data.append(0xFF & (flags >> 16)) data.append(0xFF & (flags >> 24)) data = bytes(data) def temp(resp, respData): if cb != None: cb(resp) # Send data return self.sendCommand(ProfileCharType.PROF_DATA_CMD, data, True, temp, timeout) # def switchToOAD(self,cb,timeout): # # Pack data # data = [] # data.append(CommandType['CMD_SWITCH_TO_OAD']) # data = bytes(data) # def temp(resp,respData): # if cb != None: # cb(resp,None) # # Send data # return self.sendCommand(ProfileCharType.PROF_DATA_CMD,data,True,temp,timeout) def powerOff(self, timeout): # Pack data data = [] data.append(CommandType['CMD_POWEROFF']) data = bytes(data) def temp(resp, respData): pass # Send data return self.sendCommand(ProfileCharType.PROF_DATA_CMD, data, True, temp, timeout) def systemReset(self, timeout): # Pack data data = [] data.append(CommandType['CMD_SYSTEM_RESET']) data = bytes(data) def temp(resp, respData): pass # Send data return self.sendCommand(ProfileCharType.PROF_DATA_CMD, data, True, temp, timeout) def setMotor(self, switchStatus, cb, timeout): data = [] data.append(CommandType['CMD_MOTOR_CONTROL']) tem = 0x01 if switchStatus else 0x00 data.append(tem) data = bytes(data) def temp(resp, respData): if cb != None: cb(resp) # send data return self.sendCommand(ProfileCharType.PROF_DATA_CMD, data, True, temp, timeout) def setLED(self, switchStatus, cb, timeout): data = [] data.append(CommandType['CMD_LED_CONTROL_TEST']) tem = 0x01 if switchStatus else 0x00 data.append(tem) data = bytes(data) def temp(resp, respData): if cb != None: cb(resp) # send data return self.sendCommand(ProfileCharType.PROF_DATA_CMD, data, True, temp, timeout) # Get controller firmware version def setLogLevel(self, logLevel, cb, timeout): # Pack data data = [] data.append(CommandType['CMD_SET_LOG_LEVEL']) data.append(0xFF & logLevel) data = bytes(data) def temp(resp, respData): if cb != None: cb(resp) # Send data return self.sendCommand(ProfileCharType.PROF_DATA_CMD, data, True, temp, timeout) # Set Emg Raw Data Config def setEmgRawDataConfig(self, sampRate, channelMask, dataLen, resolution, cb, timeout): # Pack data data = b'' data += struct.pack('<H', sampRate) data += struct.pack('<H', channelMask) data += struct.pack('<B', dataLen) data += struct.pack('<B', resolution) data += struct.pack('<B', CommandType['CMD_SET_EMG_RAWDATA_CONFIG']) def temp(resp, raspData): if cb != None: cb(resp) # Send data return self.sendCommand(ProfileCharType.PROF_DATA_CMD, data, True, temp, timeout) # Get Emg Raw Data Config def getEmgRawDataConfig(self, cb, timeout): # Pack data data = [] data.append(CommandType['CMD_GET_EMG_RAWDATA_CONFIG']) data = bytes(data) def temp(resp, respData): if cb != None: if resp != ResponseResult['RSP_CODE_SUCCESS']: cb(resp, None, None, None, None) elif len(respData) == 6: sampRate, channelMask, dataLen, resolution = struct.unpack_from( '@HHBB', respData) cb(resp, sampRate, channelMask, dataLen, resolution) return self.sendCommand(ProfileCharType.PROF_DATA_CMD, data, True, temp, timeout) def getFeatureMap(self, cb, timeout): # Pack data data = [] data.append(CommandType['CMD_GET_FEATURE_MAP']) data = bytes(data) def temp(resp, respData): if cb != None: if resp != ResponseResult['RSP_CODE_SUCCESS']: cb(resp) elif len(respData) == 4: featureMap = struct.unpack('@I', respData)[0] cb(resp, featureMap) return self.sendCommand(ProfileCharType.PROF_DATA_CMD, data, True, temp, timeout) # Get controller firmware version def getControllerFirmwareVersion(self, cb, timeout): # Pack data data = [] data.append(CommandType['CMD_GET_FW_REVISION']) data = bytes(data) def temp(resp, respData): if cb != None: if resp != ResponseResult['RSP_CODE_SUCCESS']: cb(resp) else: if len(respData) > 4: firmwareVersion = respData.decode('ascii') else: firmwareVersion = '' for i in respData: firmwareVersion += str(i)+'.' firmwareVersion = firmwareVersion[0:len( firmwareVersion)] cb(resp, firmwareVersion) return self.sendCommand(ProfileCharType.PROF_DATA_CMD, data, True, temp, timeout) def sendCommand(self, type, data, hasResponse, cb, timeout): if hasResponse and cb != None: cmd = data[0] self.lock.acquire() if cmd in self.cmdMap.keys(): self.lock.release() return GF_RET_CODE.GF_ERROR_DEVICE_BUSY self.cmdMap[cmd] = CommandCallbackTableEntry( cmd, datetime.now()+timedelta(milliseconds=timeout), cb) self._refreshTimer() self.lock.release() if type == ProfileCharType.PROF_DATA_CMD: if self.cmdCharacteristic == None: return GF_RET_CODE.GF_ERROR_BAD_STATE else: if len(data) > self.mtu: contentLen = self.mtu - 2 packetCount = (len(data)+contentLen-1)//contentLen startIndex = 0 buf = [] for i in range(packetCount-1, 0, -1): buf.append(CommandType['CMD_PARTIAL_DATA']) buf.append(i) buf += data[startIndex:startIndex+contentLen] startIndex += contentLen self.send_queue.put_nowait(buf) buf.clear() # Packet end buf.append(CommandType['CMD_PARTIAL_DATA']) buf.append(0) buf += data[startIndex:] self.send_queue.put_nowait(buf) else: self.send_queue.put_nowait(data) return GF_RET_CODE.GF_SUCCESS else: return GF_RET_CODE.GF_ERROR_BAD_PARAM # Refresh time,need external self.lock def _refreshTimer(self): def cmp_time(cb): return cb._timeoutTime if self.timer != None: self.timer.cancel() self.timer = None cmdlist = self.cmdMap.values() if len(cmdlist) > 0: cmdlist = sorted(cmdlist, key=cmp_time) # Process timeout entries timeoutTime = None listlen = len(cmdlist) for i in range(listlen): timeoutTime = cmdlist[0]._timeoutTime print('_'*40) print('系统时间:', datetime.now()) print('超时时间:', timeoutTime) print('\ncmd: {0}, timeout: {1}'.format( hex(cmdlist[0]._cmd), timeoutTime < datetime.now())) print('_'*40) if timeoutTime > datetime.now(): self.cmdForTimeout = cmdlist[0]._cmd ms = int((timeoutTime.timestamp() - datetime.now().timestamp())*1000) if ms <= 0: ms = 1 self.timer = threading.Timer(ms/1000, self._onTimeOut) self.timer.start() break cmd = cmdlist.pop(0) if cmd._cb != None: cmd._cb(ResponseResult['RSP_CODE_TIMEOUT'], None) def startDataNotification(self, onData): self.onData = onData try: self.setNotify(self.notifyCharacteristic, True) success = True except: success = False if success: return GF_RET_CODE.GF_SUCCESS else: return GF_RET_CODE.GF_ERROR_BAD_STATE def stopDataNotification(self): try: self.setNotify(self.notifyCharacteristic, False) success = True except: success = False if success: return GF_RET_CODE.GF_SUCCESS else: return GF_RET_CODE.GF_ERROR_BAD_STATE def handleDataNotification(self, data, onData): fullPacket = [] if len(data) >= 2: if data[0] == NotifDataType['NTF_PARTIAL_DATA']: if self.lastIncompleteNotifPacketId != 0 and self.lastIncompleteNotifPacketId != data[1]+1: print('Error:lastIncompleteNotifPacketId:{0},current packet id:{1}'.format( self.lastIncompleteNotifPacketId, data[1])) # How to do with packet loss? # Must validate packet len in onData callback! if self.lastIncompleteNotifPacketId == 0 or self.lastIncompleteNotifPacketId > data[1]: # Only accept packet with smaller packet num self.lastIncompleteNotifPacketId = data[1] self.incompleteNotifPacket += data[2:] if self.lastIncompleteNotifPacketId == 0: fullPacket = self.incompleteNotifPacket self.incompleteNotifPacket = [] else: fullPacket = data if len(fullPacket) > 0: onData(fullPacket) # Command notification callback def _onResponse(self, data): print('_onResponse: data=', data) fullPacket = [] if len(data) >= 2: if data[0] == ResponseResult['RSP_CODE_PARTIAL_PACKET']: if self.lastIncompleteCmdRespPacketId != 0 and self.lastIncompleteCmdRespPacketId != data[1] + 1: print('Error: _lastIncompletePacketId:{0}, current packet id:{1}' .format(self.lastIncompleteCmdRespPacketId, data[1])) if (self.lastIncompleteCmdRespPacketId == 0 or self.lastIncompleteCmdRespPacketId > data[1]): self.lastIncompleteCmdRespPacketId = data[1] self.incompleteCmdRespPacket += data[2:] print('_incompleteCmdRespPacket 等于 ', self.incompleteCmdRespPacket) if self.lastIncompleteCmdRespPacketId == 0: fullPacket = self.incompleteCmdRespPacket self.incompleteCmdRespPacket = [] else: fullPacket = data if fullPacket != None and len(fullPacket) >= 2: resp = fullPacket[0] cmd = fullPacket[1] # Delete command callback table entry & refresh timer's timeout self.lock.acquire() if cmd > 0 and self.cmdMap.__contains__(cmd): cb = self.cmdMap[cmd]._cb del self.cmdMap[cmd] self._refreshTimer() if cb != None: cb(resp, fullPacket[2:]) self.lock.release() # Timeout callback function def _onTimeOut(self): print('_onTimeOut: _cmdForTimeout={0}, time={1}'.format( self.cmdForTimeout, datetime.now())) # Delete command callback table entry & refresh timer's timeout cb = None self.lock.acquire() if self.cmdForTimeout > 0 and self.cmdMap.__contains__(self.cmdForTimeout): cb = self.cmdMap[self.cmdForTimeout]._cb del self.cmdMap[self.cmdForTimeout] self._refreshTimer() self.lock.release() if cb != None: cb(ResponseResult['RSP_CODE_TIMEOUT'], None)