Beispiel #1
1
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
Beispiel #2
0
    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
Beispiel #3
0
 def connect(self, address):
     Peripheral.connect(self, address)
     if self.__get_char():
         self.__init_seq()
         self.update()
         return True
     else:
         self.disconnect()
         return False
Beispiel #4
0
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()
Beispiel #6
0
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
Beispiel #7
0
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)
Beispiel #8
0
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()
Beispiel #9
0
                              (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}")

# 生の測定データを変換
Beispiel #10
0
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
Beispiel #11
0
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
Beispiel #12
0

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)
Beispiel #13
0
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)
Beispiel #15
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
Beispiel #16
0
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)
Beispiel #18
0
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
Beispiel #19
0
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")
Beispiel #20
0
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)
Beispiel #22
0
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()
Beispiel #23
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: %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()
Beispiel #25
0
        
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"
Beispiel #26
0
    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')
Beispiel #27
0
#!/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"
Beispiel #28
0
# 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)
Beispiel #29
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
Beispiel #30
0
#         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')
Beispiel #32
0
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)