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
class Nuimo:

    SERVICE_UUIDS = [
        UUID('0000180f-0000-1000-8000-00805f9b34fb'), # Battery
        UUID('f29b1525-cb19-40f3-be5c-7241ecb82fd2'), # Sensors
        UUID('f29b1523-cb19-40f3-be5c-7241ecb82fd1')  # LED Matrix
    ]

    CHARACTERISTIC_UUIDS = {
        UUID('00002a19-0000-1000-8000-00805f9b34fb') : 'BATTERY',
        UUID('f29b1529-cb19-40f3-be5c-7241ecb82fd2') : 'BUTTON',
        UUID('f29b1528-cb19-40f3-be5c-7241ecb82fd2') : 'ROTATION',
        UUID('f29b1527-cb19-40f3-be5c-7241ecb82fd2') : 'SWIPE',
        UUID('f29b1526-cb19-40f3-be5c-7241ecb82fd2') : 'FLY',
        UUID('f29b1524-cb19-40f3-be5c-7241ecb82fd1') : 'LED_MATRIX'
    }

    NOTIFICATION_CHARACTERISTIC_UUIDS = [
        #'BATTERY', # Uncomment only if you are not using the iOS emulator (iOS does't support battery updates without authentication)
        'BUTTON',
        'ROTATION',
        'SWIPE',
        'FLY']

    # Notification data
    NOTIFICATION_ON  = struct.pack("BB", 0x01, 0x00)
    NOTIFICATION_OFF = struct.pack("BB", 0x00, 0x00)

    def __init__(self, macAddress):
        self.macAddress = macAddress

    def set_delegate(self, delegate):
        self.delegate = delegate

    def connect(self):
        self.peripheral = Peripheral(self.macAddress, addrType='random')
        # Retrieve all characteristics from desires services and map them from their UUID
        characteristics = list(itertools.chain(*[self.peripheral.getServiceByUUID(uuid).getCharacteristics() for uuid in Nuimo.SERVICE_UUIDS]))
        characteristics = dict((c.uuid, c) for c in characteristics)
        # Store each characteristic's value handle for each characteristic name
        self.characteristicValueHandles = dict((name, characteristics[uuid].getHandle()) for uuid, name in Nuimo.CHARACTERISTIC_UUIDS.items())
        # Subscribe for notifications
        for name in Nuimo.NOTIFICATION_CHARACTERISTIC_UUIDS:
            self.peripheral.writeCharacteristic(self.characteristicValueHandles[name] + 1, Nuimo.NOTIFICATION_ON, True)
        self.peripheral.setDelegate(self.delegate)

    def waitForNotifications(self):
        self.peripheral.waitForNotifications(1.0)

    def displayLedMatrix(self, matrix, timeout, brightness = 1.0):
        matrix = '{:<81}'.format(matrix[:81])
        bytes = list(map(lambda leds: reduce(lambda acc, led: acc + (1 << led if leds[led] not in [' ', '0'] else 0), range(0, len(leds)), 0), [matrix[i:i+8] for i in range(0, len(matrix), 8)]))
        self.peripheral.writeCharacteristic(self.characteristicValueHandles['LED_MATRIX'], struct.pack('BBBBBBBBBBBBB', bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7], bytes[8], bytes[9], bytes[10], max(0, min(255, int(255.0 * brightness))), max(0, min(255, int(timeout * 10.0)))), True)
Beispiel #3
0
p_device.setDelegate(MyDelegate())

svc = p_device.getServiceByUUID(info['service_uuid'])
for ch in svc.getCharacteristics():
    print(str(ch))
    print("Handle is  : " + str(ch.getHandle()))
    print("Property is: " + ch.propertiesToString())

char = svc.getCharacteristics(forUUID=info['notify_uuid'])[0]
for desc in char.getDescriptors():
    print("^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^")
    print("descriptors: ", desc)
    print("common name: ", desc.uuid.getCommonName())

#setup_data = b"\x01\x00"
notify_handle = char.getHandle() + 2
print("notify_handle is  : " + str(notify_handle))
p_device.writeCharacteristic(notify_handle,
                             struct.pack('<bb', 0x01, 0x00),
                             withResponse=True)

i = 0
while True:
    if p_device.waitForNotifications(1.0):
        i += 1
        if (i > 1000):
            break
        continue

    print("Waiting...")
Beispiel #4
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)
class BleDevice:
    def __init__(self, addr, thing_id):
        self.addr = addr
        self.thing_id = thing_id

    def connect(self):
        print("--Connect")
        print(" -Connecting to Peripheral:", self.addr)
        self.per = Peripheral(self.addr)
        print(" -Connected to Peripheral:", self.addr)
        print()

    def disconnect(self):
        print("--Disconnect")
        print(" -Disconnecting from Peripheral:", self.addr)
        self.per.disconnect()
        print(" -Disconnected from Peripheral:", self.addr)

    def setup(self):
        print("--Setup")

        self.per.setMTU(112)
        print(" -MTU set to 112 bytes (109+3 bytes)")

        self.svc = self.per.getServiceByUUID(
            "2ea78970-7d44-44bb-b097-26183f402400")

        self.data_ch = self.svc.getCharacteristics(
            "2ea78970-7d44-44bb-b097-26183f402410")[0]
        self.data_ch_hnd = self.data_ch.getHandle()
        self.per.writeCharacteristic(self.data_ch_hnd + 1, b"\x01\00", True)
        print(" -Enabled notifications on characteristic 0x2410")

        self.ctrl_ch = self.svc.getCharacteristics(
            "2ea78970-7d44-44bb-b097-26183f402409")[0]
        self.ctrl_ch_hnd = self.ctrl_ch.getHandle()
        self.ctrl_ch.write(b"\x01", True)
        print(" -Wrote 0x01 to characteristic 0x2409")

        self.per.withDelegate(MyDelegate(self))
        print()

    def set_handler(self, handler):
        self.handler = handler

    def print_svc_char(self):
        print("--Services and Characteristics")
        services = self.per.getServices()
        for svc in services:
            print(svc)
            print(svc.uuid)
            print()
            characteristics = svc.getCharacteristics()
            for char in characteristics:
                print("\t", char)
                print("\t", char.uuid)
                print("\t Handle:", char.getHandle())
                print("\t", char.propertiesToString())
                print()

    def get_data(self, mins):
        print("--Reports")
        t_end = time.time() + 60 * mins
        while time.time() < t_end:
            if self.per.waitForNotifications(1.0):
                continue
            print(" -Waiting...")
        print()
Beispiel #6
0
class LightBlueBean(DefaultDelegate):
    # https://github.com/PunchThrough/bean-documentation/blob/master/app_message_types.md
    MSG_ID_SERIAL_DATA = 0x0000
    MSG_ID_BT_SET_ADV = 0x0500
    MSG_ID_BT_SET_CONN = 0x0502
    MSG_ID_BT_SET_LOCAL_NAME = 0x0504
    MSG_ID_BT_SET_PIN = 0x0506
    MSG_ID_BT_SET_TX_PWR = 0x0508
    MSG_ID_BT_GET_CONFIG = 0x0510
    MSG_ID_BT_ADV_ONOFF = 0x0512
    MSG_ID_BT_SET_SCRATCH = 0x0514
    MSG_ID_BT_GET_SCRATCH = 0x0515
    MSG_ID_BT_RESTART = 0x0520
    MSG_ID_GATING = 0x0550
    MSG_ID_BL_CMD = 0x1000
    MSG_ID_BL_FW_BLOCK = 0x1001
    MSG_ID_BL_STATUS = 0x1002
    MSG_ID_CC_LED_WRITE = 0x2000
    MSG_ID_CC_LED_WRITE_ALL = 0x2001
    MSG_ID_CC_LED_READ_ALL = 0x2002
    MSG_ID_CC_LED_DATA = 0x2082
    MSG_ID_CC_ACCEL_READ = 0x2010
    MSG_ID_CC_ACCEL_DATA = 0x2090
    MSG_ID_CC_TEMP_READ = 0x2011
    MSG_ID_CC_TEMP_DATA = 0x2091
    MSG_ID_CC_BATT_READ = 0x2015
    MSG_ID_CC_BATT_DATA = 0x2095
    MSG_ID_AR_SET_POWER = 0x3000
    MSG_ID_AR_GET_CONFIG = 0x3006
    MSG_ID_DB_LOOPBACK = 0xFE00
    MSG_ID_DB_COUNTER = 0xFE01

    def __init__(self, mac):
        self.conn = Peripheral(mac)
        self.conn.setDelegate(self)
        self.count = 0
        self.buffin = [None] * 10
        self.got1 = False
        print('connected')

        self.service = self.conn.getServiceByUUID(_LBN_UUID(0x10))
        self.serial = self.service.getCharacteristics(_LBN_UUID(0x11))[0]

        #print(self.serial.propertiesToString())

        # Turn on notificiations
        self.conn.writeCharacteristic(0x2f, '\x01\x00', False)

        i = 0
        while True:
            #print(self.serial.read())
            self.write("a" * 60)
            #self.write("a" * 5)
            #self.sendCmd(LightBlueBean.MSG_ID_CC_ACCEL_READ)

            self.conn.waitForNotifications(1)
            time.sleep(1)
        self.conn.disconnect()

    def write(self, data):
        self.sendCmd(LightBlueBean.MSG_ID_SERIAL_DATA, data)

    def sendCmd(self, cmd, data=""):
        # https://github.com/PunchThrough/bean-documentation/blob/master/serial_message_protocol.md
        gst = struct.pack("!BxH", len(data) + 2, cmd) + data
        crc = struct.pack("<H", crc16(gst, 0xFFFF))
        gst += crc

        gt_qty = len(gst) / 19
        if len(gst) % 19 != 0:
            gt_qty += 1

        #amnt = len(gst) / gt_qty
        optimal_packet_size = 19

        for ch in xrange(0, gt_qty):
            data = gst[:optimal_packet_size]
            gst = gst[optimal_packet_size:]

            gt = 0
            if ch == 0:
                gt = 0x80
            gt |= self.count << 5
            gt |= gt_qty - ch - 1

            gt = struct.pack("B", gt) + data

            #print("<", hexdump(gt))
            self.serial.write(gt)
            #time.sleep(0.1)

        self.count = (self.count + 1) % 4

    def writeRaw(self, data):
        self.conn.writeCharacteristic(0x0b, data, False)

    def handleNotification(self, cHandle, data):
        #print(">", hexdump(data))
        gt = struct.unpack("B", data[0])[0]
        #gt_cntr = gt & 0x60
        gt_left = gt & 0x1F

        if gt & 0x80:
            self.got1 = True
            self.buffin = self.buffin[:gt_left + 1]

        self.buffin[gt_left] = data[1:]

        if self.got1 and not self.buffin.count(None):
            #print("Got ", len(self.buffin), "packets")
            self.buffin.reverse()
            self.buffin = ''.join(self.buffin)

            crc_ = crc16(self.buffin[:-2], 0xFFFF)
            dlen, cmd = struct.unpack("!BxH", self.buffin[:4])
            crc = struct.unpack("<H", self.buffin[-2:])[0]
            if crc == crc_:
                print(self.buffin[4:-2])
            else:
                print("CRC check failure")

            self.buffin = [None] * 10
            self.got1 = False
Beispiel #7
0
class Bluepy(DefaultDelegate):
    def __init__(self):
        DefaultDelegate.__init__(self)

        self._peripheral_address = None
        self._peripheral_address_type = btle.ADDR_TYPE_PUBLIC
        self._peripheral = None

        self._scanner = Scanner().withDelegate(self)
        self._devicesScanned = []

        self._service_uuid = "b9e875c0-1cfa-11e6-b797-0002a5d5c51b"
        self._char_read_uuid = "1ed9e2c0-266f-11e6-850b-0002a5d5c51b"
        self._char_write_uuid = "0c68d100-266f-11e6-b388-0002a5d5c51b"

        self._descs = None
        self._svc = None

        self._ch_read = None
        self._ch_write = None

        self.selectedSensor = None
        self.deviceId = None

        self.lastValue = {}
        self.lastValue['value'] = 0
        self.lastValue['datetime'] = "oi"

    def setSensor(self, sensor):
        self.selectedSensor = sensor

    def setDeviceId(self, id):
        self.deviceId = id

    def reset(self):
        self._peripheral = None
        self._descs = None
        self._svc = None

        self._ch_read = None
        self._ch_write = None

    def scan(self, time=3):

        devices = self._scanner.scan(time)

        for dev in devices:
            print("Device " + dev.addr + " (" + dev.addrType + "), RSSI=" +
                  str(dev.rssi) + " dB")
            for (adtype, desc, value) in dev.getScanData():
                if desc == "Complete Local Name":
                    self._devicesScanned.append({
                        'name': value,
                        'id': dev.addr
                    })

    def findXDKAddress(self):
        for dev in self._devicesScanned:
            if (dev['name'].startswith(self.deviceId)
                    or dev['id'].startswith(self.deviceId)):
                self._peripheral_address = dev['id']

        return self._peripheral_address

    def handleNotification(self, cHandle, data):

        data = re.findall(r'\d+', str(data))[0]

        self.lastValue['value'] = data
        self.lastValue['datetime'] = datetime.utcnow().strftime(
            '%Y-%m-%d %H:%M:%S.%f')[:-3]

        print("Received data: " + str(data))

        #self.lastValue = int(data)

    def handleDiscovery(self, dev, isNewDev, isNewData):
        if isNewDev:
            print("Discovered device " + dev.addr)
        elif isNewData:
            print("Received new data from" + dev.addr)

    def connect(self):

        triesCounter = 0

        while self._peripheral == None:

            try:
                print('\nConnecting...')
                self._peripheral = Peripheral(self._peripheral_address,
                                              "random")
                if (self._peripheral != None):
                    print('Connected!')

            except BTLEException as e:
                triesCounter = triesCounter + 1
                print("Number of tries: " + str(triesCounter))
                print("Trying to connect again after 5s\n")
                print(e)
                time.sleep(5)

    def setDelegate(self):
        self._peripheral.setDelegate(self)

    def discoverSvc(self):
        while self._svc == None or self._descs == None or self._ch_read == None or self._ch_write == None:
            print("\nFinding service...")
            self._svc = self._peripheral.getServiceByUUID(self._service_uuid)
            print("Finding descriptors...")
            self._descs = self._svc.getDescriptors()
            print("Finding read characteristic...")
            self._ch_read = self._svc.getCharacteristics(
                self._char_read_uuid)[0]
            print("Finding write characteristic...\n")
            self._ch_write = self._svc.getCharacteristics(
                self._char_write_uuid)[0]

        print("All characteristics found!!!\n")

    def enableSensor(self):
        print("Turning sensor on...\n")
        self._ch_write.write(b'\x73\x74\x61\x72\x74')
        time.sleep(1.0)

        if (self.selectedSensor == "temperature"):
            self._ch_write.write(
                b'\x74\x65\x6d\x70\x65\x72\x61\x74\x75\x72\x65')

        elif (self.selectedSensor == "light"):
            self._ch_write.write(b'\x6c\x69\x67\x68\x74')

        elif (self.selectedSensor == "humidity"):
            self._ch_write.write(b'\x68\x75\x6d\x69\x64\x69\x74\x79')

        elif (self.selectedSensor == "pressure"):
            self._ch_write.write(b'\x70\x72\x65\x73\x73\x75\x72\x65')

        elif (self.selectedSensor == "noise"):
            self._ch_write.write(b'\x6e\x6f\x69\x73\x65')

        time.sleep(1.0)

    def disableSensor(self):
        print("Turning sensor off...\n")
        self._ch_write.write(b'\x73\x74\x6f\x70')
        time.sleep(1.0)

    def disconnect(self):
        print('Disconnecting...')

        self._peripheral.disconnect()

        self._peripheral = None
        print('Disconnected!\n')

    def readValues(self):
        print("Reading values...\n")

        count = 0
        while True:
            if self._peripheral.waitForNotifications(5.0):
                count = count + 1
                continue

    def BluetoothFlow(self):

        self.scan(3)

        xdk_address = self.findXDKAddress()

        print("\nXDK MAC: " + xdk_address)

        disconnectCounter = 0
        active = True

        while active == True:
            try:
                self.reset()

                self.connect()

                self.setDelegate()

                self.discoverSvc()

                self.enableSensor()

                try:
                    self.readValues()
                except BTLEException as e:
                    print(e)
                    disconnectCounter = disconnectCounter + 1
                    print("\nDisconnection number: " + str(disconnectCounter))
                    print("\nTrying to reconnect after 5s...")
                    time.sleep(5.0)

                except KeyboardInterrupt:
                    active = False
                    print("\nStopping...")

            except BTLEException as e:
                print(e)

        self.disableSensor()
        self.disconnect()

    def getLastValue(self):
        return (self.lastValue)
def main():
    # uuid definition
    targetDevice = ""
    targetUUID   = UUID("08590f7e-db05-467e-8757-72f6f66666d4")
    # targetUUID   = UUID(0x2a2b)
    serviceUUID  = UUID("e20a39f4-73f5-4bc4-a12f-17d1ad666661")

    # scanning for Bluetooth LE device
    # P.S. root permission is needed
    print "scanning started..."
    scanner = Scanner().withDelegate(ScanDelegate())
    devices = scanner.scan(5)

    print "\n\nscanning completed...\n found %d device(s)\n" % len(devices)

    for dev in devices:
        print "Device %s (%s), RSSI=%d dB" % (dev.addr, dev.addrType, dev.rssi)
        for (adtype, desc, value) in dev.getScanData():
            print "  %s = %s" % (desc, value)

        try:
            p = Peripheral(dev.addr, "random")
            ch = p.getCharacteristics(uuid=targetUUID)
            if len(ch) > 0:
                print "the desired target found. the address is", dev.addr
                targetDevice = dev.addr
        except:
            # print "Unexpected error:", sys.exc_info()[0]
            print "Unable to connect"
            print " "
        finally:
            p.disconnect()

    # scanning completed, now continue to connect to device
    if targetDevice == "":
        # the target is not found. end.
        print "no target was found."
    else:
        # the target found, continue to subscribe.
        print "\n\nthe target device is ", targetDevice
        print "now try to subscribe..."

        try:
            # try to get the handle first
            p = Peripheral(targetDevice, "random")
            p.setDelegate(NotificationDelegate())
            # svc = p.getServiceByUUID(serviceUUID)
            ch = p.getCharacteristics(uuid=targetUUID)[0] # svc.getCharacteristics(targetUUID)[0]
            handle = ch.getHandle()
            print handle
            ch.write(struct.pack('<bb', 0x01, 0x00))
            # ch.write(bytes('aa', 'utf-8'))
            # p.writeCharacteristic(handle, struct.pack('<bb', 0x01, 0x00), True)

            print

            # Main loop
            while True:
                if p.waitForNotifications(5):
                    # handleNotification() was called
                    continue

                print "Waiting..."
                # Perhaps do something else here
        # except:
        #     print "Unexpected error:", sys.exc_info()[0]
        finally:
            p.disconnect()
Beispiel #9
0
class sphero_mini:
    def __init__(self, MACAddr, verbosity=4, user_delegate=None):
        '''
        initialize class instance and then build collect BLE sevices and characteristics.
        Also sends text string to Anti-DOS characteristic to prevent returning to sleep,
        and initializes notifications (which is what the sphero uses to send data back to
        the client).
        '''
        self.verbosity = verbosity  # 0 = Silent,
        # 1 = Connection/disconnection only
        # 2 = Init messages
        # 3 = Recieved commands
        # 4 = Acknowledgements
        self.sequence = 1
        self.v_batt = None  # will be updated with battery voltage when sphero.getBatteryVoltage() is called
        self.firmware_version = [
        ]  # will be updated with firware version when sphero.returnMainApplicationVersion() is called

        if self.verbosity > 0:
            print("[INFO] Connecting to", MACAddr)
        self.p = Peripheral(MACAddr, "random")  # connect

        if self.verbosity > 1:
            print("[INIT] Initializing")

        # Subscribe to notifications
        self.sphero_delegate = MyDelegate(
            self, user_delegate
        )  # Pass a reference to this instance when initializing
        self.p.setDelegate(self.sphero_delegate)

        if self.verbosity > 1:
            print("[INIT] Read all characteristics and descriptors")
        # Get characteristics and descriptors
        self.API_V2_characteristic = self.p.getCharacteristics(
            uuid="00010002-574f-4f20-5370-6865726f2121")[0]
        self.AntiDOS_characteristic = self.p.getCharacteristics(
            uuid="00020005-574f-4f20-5370-6865726f2121")[0]
        self.DFU_characteristic = self.p.getCharacteristics(
            uuid="00020002-574f-4f20-5370-6865726f2121")[0]
        self.DFU2_characteristic = self.p.getCharacteristics(
            uuid="00020004-574f-4f20-5370-6865726f2121")[0]
        self.API_descriptor = self.API_V2_characteristic.getDescriptors(
            forUUID=0x2902)[0]
        self.DFU_descriptor = self.DFU_characteristic.getDescriptors(
            forUUID=0x2902)[0]

        # The rest of this sequence was observed during bluetooth sniffing:
        # Unlock code: prevent the sphero mini from going to sleep again after 10 seconds
        if self.verbosity > 1:
            print("[INIT] Writing AntiDOS characteristic unlock code")
        self.AntiDOS_characteristic.write("usetheforce...band".encode(),
                                          withResponse=True)

        # Enable DFU notifications:
        if self.verbosity > 1:
            print("[INIT] Configuring DFU descriptor")
        self.DFU_descriptor.write(struct.pack('<bb', 0x01, 0x00),
                                  withResponse=True)

        # No idea what this is for. Possibly a device ID of sorts? Read request returns '00 00 09 00 0c 00 02 02':
        if self.verbosity > 1:
            print("[INIT] Reading DFU2 characteristic")
        _ = self.DFU2_characteristic.read()

        # Enable API notifications:
        if self.verbosity > 1:
            print("[INIT] Configuring API dectriptor")
        self.API_descriptor.write(struct.pack('<bb', 0x01, 0x00),
                                  withResponse=True)

        self.wake()

        # Finished initializing:
        if self.verbosity > 1:
            print("[INIT] Initialization complete\n")

    def disconnect(self):
        if self.verbosity > 0:
            print("[INFO] Disconnecting")

        self.p.disconnect()

    def wake(self):
        '''
        Bring device out of sleep mode (can only be done if device was in sleep, not deep sleep).
        If in deep sleep, the device should be connected to USB power to wake.
        '''
        if self.verbosity > 2:
            print("[SEND {}] Waking".format(self.sequence))

        self._send(characteristic=self.API_V2_characteristic,
                   devID=deviceID['powerInfo'],
                   commID=powerCommandIDs["wake"],
                   payload=[])  # empty payload

        self.getAcknowledgement("Wake")

    def sleep(self, deepSleep=False):
        '''
        Put device to sleep or deep sleep (deep sleep needs USB power connected to wake up)
        '''
        if deepSleep:
            sleepCommID = powerCommandIDs["deepSleep"]
            print("Going into deep sleep. Connect power to wake.")
        else:
            sleepCommID = powerCommandIDs["sleep"]
        self._send(characteristic=self.API_V2_characteristic,
                   devID=deviceID['powerInfo'],
                   commID=sleepCommID,
                   payload=[])  #empty payload

    def setLEDColor(self, red=None, green=None, blue=None):
        '''
        Set device LED color based on RGB vales (each can  range between 0 and 0xFF)
        '''
        if self.verbosity > 2:
            print("[SEND {}] Setting main LED colour to [{}, {}, {}]".format(
                self.sequence, red, green, blue))

        self._send(
            characteristic=self.API_V2_characteristic,
            devID=deviceID['userIO'],  # 0x1a
            commID=userIOCommandIDs["allLEDs"],  # 0x0e
            payload=[0x00, 0x0e, red, green, blue])

        self.getAcknowledgement("LED/backlight")

    def setBackLEDIntensity(self, brightness=None):
        '''
        Set device LED backlight intensity based on 0-255 values

        NOTE: this is not the same as aiming - it only turns on the LED
        '''
        if self.verbosity > 2:
            print("[SEND {}] Setting backlight intensity to {}".format(
                self.sequence, brightness))

        self._send(characteristic=self.API_V2_characteristic,
                   devID=deviceID['userIO'],
                   commID=userIOCommandIDs["allLEDs"],
                   payload=[0x00, 0x01, brightness])

        self.getAcknowledgement("LED/backlight")

    def roll(self, speed=None, heading=None):
        '''
        Start to move the Sphero at a given direction and speed.
        heading: integer from 0 - 360 (degrees)
        speed: Integer from 0 - 255

        Note: the zero heading should be set at startup with the resetHeading method. Otherwise, it may
        seem that the sphero doesn't honor the heading argument
        '''
        if self.verbosity > 2:
            print("[SEND {}] Rolling with speed {} and heading {}".format(
                self.sequence, speed, heading))

        if abs(speed) > 255:
            print(
                "WARNING: roll speed parameter outside of allowed range (-255 to +255)"
            )

        if speed < 0:
            speed = -1 * speed + 256  # speed values > 256 in the send packet make the spero go in reverse

        speedH = (speed & 0xFF00) >> 8
        speedL = speed & 0xFF
        headingH = (heading & 0xFF00) >> 8
        headingL = heading & 0xFF
        self._send(characteristic=self.API_V2_characteristic,
                   devID=deviceID['driving'],
                   commID=drivingCommands["driveWithHeading"],
                   payload=[speedL, headingH, headingL, speedH])

        self.getAcknowledgement("Roll")

    def resetHeading(self):
        '''
        Reset the heading zero angle to the current heading (useful during aiming)
        Note: in order to manually rotate the sphero, you need to call stabilization(False).
        Once the heading has been set, call stabilization(True).
        '''
        if self.verbosity > 2:
            print("[SEND {}] Resetting heading".format(self.sequence))

        self._send(characteristic=self.API_V2_characteristic,
                   devID=deviceID['driving'],
                   commID=drivingCommands["resetHeading"],
                   payload=[])  #empty payload

        self.getAcknowledgement("Heading")

    def returnMainApplicationVersion(self):
        '''
        Sends command to return application data in a notification
        '''
        if self.verbosity > 2:
            print("[SEND {}] Requesting firmware version".format(
                self.sequence))

        self._send(self.API_V2_characteristic,
                   devID=deviceID['systemInfo'],
                   commID=SystemInfoCommands['mainApplicationVersion'],
                   payload=[])  # empty

        self.getAcknowledgement("Firmware")

    def getBatteryVoltage(self):
        '''
        Sends command to return battery voltage data in a notification.
        Data printed to console screen by the handleNotifications() method in the MyDelegate class.
        '''
        if self.verbosity > 2:
            print("[SEND {}] Requesting battery voltage".format(self.sequence))

        self._send(self.API_V2_characteristic,
                   devID=deviceID['powerInfo'],
                   commID=powerCommandIDs['batteryVoltage'],
                   payload=[])  # empty

        self.getAcknowledgement("Battery")

    def stabilization(self, stab=True):
        '''
        Sends command to turn on/off the motor stabilization system (required when manually turning/aiming the sphero)
        '''
        if stab == True:
            if self.verbosity > 2:
                print("[SEND {}] Enabling stabilization".format(self.sequence))
            val = 1
        else:
            if self.verbosity > 2:
                print("[SEND {}] Disabling stabilization".format(
                    self.sequence))
            val = 0
        self._send(self.API_V2_characteristic,
                   devID=deviceID['driving'],
                   commID=drivingCommands['stabilization'],
                   payload=[val])

        self.getAcknowledgement("Stabilization")

    def wait(self, delay):
        '''
        This is a non-blocking delay command. It is similar to time.sleep(), except it allows asynchronous 
        notification handling to still be performed.
        '''
        start = time.time()
        while (1):
            self.p.waitForNotifications(0.001)
            if time.time() - start > delay:
                break

    def _send(self, characteristic=None, devID=None, commID=None, payload=[]):
        '''
        A generic "send" method, which will be used by other methods to send a command ID, payload and
        appropriate checksum to a specified device ID. Mainly useful because payloads are optional,
        and can be of varying length, to convert packets to binary, and calculate and send the
        checksum. For internal use only.

        Packet structure has the following format (in order):

        - Start byte: always 0x8D
        - Flags byte: indicate response required, etc
        - Virtual device ID: see sphero_constants.py
        - Command ID: see sphero_constants.py
        - Sequence number: Seems to be arbitrary. I suspect it is used to match commands to response packets (in which the number is echoed).
        - Payload: Could be varying number of bytes (incl. none), depending on the command
        - Checksum: See below for calculation
        - End byte: always 0xD8

        '''
        sendBytes = [
            sendPacketConstants["StartOfPacket"],
            sum([flags["resetsInactivityTimeout"], flags["requestsResponse"]]),
            devID, commID, self.sequence
        ] + payload  # concatenate payload list

        self.sequence += 1  # Increment sequence number, ensures we can identify response packets are for this command
        if self.sequence > 255:
            self.sequence = 0

        # Compute and append checksum and add EOP byte:
        # From Sphero docs: "The [checksum is the] modulo 256 sum of all the bytes
        #                   from the device ID through the end of the data payload,
        #                   bit inverted (1's complement)"
        # For the sphero mini, the flag bits must be included too:
        checksum = 0
        for num in sendBytes[1:]:
            checksum = (
                checksum + num
            ) & 0xFF  # bitwise "and to get modulo 256 sum of appropriate bytes
        checksum = 0xff - checksum  # bitwise 'not' to invert checksum bits
        sendBytes += [checksum,
                      sendPacketConstants["EndOfPacket"]]  # concatenate

        # Convert numbers to bytes
        output = b"".join([x.to_bytes(1, byteorder='big') for x in sendBytes])

        #send to specified characteristic:
        characteristic.write(output, withResponse=True)

    def getAcknowledgement(self, ack):
        #wait up to 10 secs for correct acknowledgement to come in, including sequence number!
        start = time.time()
        while (1):
            self.p.waitForNotifications(1)
            if self.sphero_delegate.notification_seq == self.sequence - 1:  # use one less than sequence, because _send function increments it for next send.
                if self.verbosity > 3:
                    print("[RESP {}] {}".format(
                        self.sequence - 1,
                        self.sphero_delegate.notification_ack))
                self.sphero_delegate.clear_notification()
                break
            elif self.sphero_delegate.notification_seq >= 0:
                print(
                    "Unexpected ACK. Expected: {}/{}, received: {}/{}".format(
                        ack, self.sequence,
                        self.sphero_delegate.notification_ack.split()[0],
                        self.sphero_delegate.notification_seq),
                    file=sys.stderr)
            if time.time() > start + 10:
                print("Timeout waiting for acknowledgement: {}/{}".format(
                    ack, self.sequence),
                      file=sys.stderr)
                break

# =======================================================================
# The following functions are experimental:
# =======================================================================

    def configureCollisionDetection(
            self,
            xThreshold=50,
            yThreshold=50,
            xSpeed=50,
            ySpeed=50,
            deadTime=50,  # in 10 millisecond increments
            method=0x01,  # Must be 0x01        
            callback=None):
        '''
        Appears to function the same as other Sphero models, however speed settings seem to have no effect. 
        NOTE: Setting to zero seems to cause bluetooth errors with the Sphero Mini/bluepy library - set to 
        255 to make it effectively disabled.

        deadTime disables future collisions for a short period of time to avoid repeat triggering by the same
        event. Set in 10ms increments. So if deadTime = 50, that means the delay will be 500ms, or half a second.
        
        From Sphero docs:
        
            xThreshold/yThreshold: An 8-bit settable threshold for the X (left/right) and Y (front/back) axes 
            of Sphero.

            xSpeed/ySpeed: An 8-bit settable speed value for the X and Y axes. This setting is ranged by the 
            speed, then added to xThreshold, yThreshold to generate the final threshold value.
        '''

        if self.verbosity > 2:
            print("[SEND {}] Configuring collision detection".format(
                self.sequence))

        self._send(
            self.API_V2_characteristic,
            devID=deviceID['sensor'],
            commID=sensorCommands['configureCollision'],
            payload=[method, xThreshold, xSpeed, yThreshold, ySpeed, deadTime])

        self.collision_detection_callback = callback

        self.getAcknowledgement("Collision")

    def configureSensorStream(self):  # Use default values
        '''
        Send command to configure sensor stream using default values as found during bluetooth 
        sniffing of the Sphero Edu app.

        Must be called after calling configureSensorMask()
        '''
        bitfield1 = 0b00000000  # Unknown function - needs experimenting
        bitfield2 = 0b00000000  # Unknown function - needs experimenting
        bitfield3 = 0b00000000  # Unknown function - needs experimenting
        bitfield4 = 0b00000000  # Unknown function - needs experimenting

        if self.verbosity > 2:
            print("[SEND {}] Configuring sensor stream".format(self.sequence))

        self._send(self.API_V2_characteristic,
                   devID=deviceID['sensor'],
                   commID=sensorCommands['configureSensorStream'],
                   payload=[bitfield1, bitfield1, bitfield1, bitfield1])

        self.getAcknowledgement("Sensor")

    def configureSensorMask(
            self,
            sample_rate_divisor=0x25,  # Must be > 0
            packet_count=0,
            IMU_pitch=False,
            IMU_roll=False,
            IMU_yaw=False,
            IMU_acc_x=False,
            IMU_acc_y=False,
            IMU_acc_z=False,
            IMU_gyro_x=False,
            IMU_gyro_y=False,
            IMU_gyro_z=False):
        '''
        Send command to configure sensor mask using default values as found during bluetooth 
        sniffing of the Sphero Edu app. From experimentation, it seems that these are he functions of each:
        
        Sampling_rate_divisor. Slow data EG: Set to 0x32 to the divide data rate by 50. Setting below 25 (0x19) causes 
                bluetooth errors        
        
        Packet_count: Select the number of packets to transmit before ending the stream. Set to zero to stream infinitely
        
        All IMU bool parameters: Toggle transmission of that value on or off (e.g. set IMU_acc_x = True to include the 
                X-axis accelerometer readings in the sensor stream)
        '''

        # Construct bitfields based on function parameters:
        IMU_bitfield1 = (IMU_pitch << 2) + (IMU_roll << 1) + IMU_yaw
        IMU_bitfield2 = ((IMU_acc_y<<7) + (IMU_acc_z<<6) + (IMU_acc_x<<5) + \
                         (IMU_gyro_y<<4) + (IMU_gyro_x<<2) + (IMU_gyro_z<<2))

        if self.verbosity > 2:
            print("[SEND {}] Configuring sensor mask".format(self.sequence))

        self._send(
            self.API_V2_characteristic,
            devID=deviceID['sensor'],
            commID=sensorCommands['sensorMask'],
            payload=[
                0x00,  # Unknown param - altering it seems to slow data rate. Possibly averages multiple readings?
                sample_rate_divisor,
                packet_count,  # Packet count: select the number of packets to stop streaming after (zero = infinite)
                0b00,  # Unknown param: seems to be another accelerometer bitfield? Z-acc, Y-acc
                IMU_bitfield1,
                IMU_bitfield2,
                0b00
            ]
        )  # reserved, Position?, Position?, velocity?, velocity?, Y-gyro, timer, reserved

        self.getAcknowledgement("Mask")
        '''
        Since the sensor values arrive as unlabelled lists in the order that they appear in the bitfields above, we need 
        to create a list of sensors that have been configured.Once we have this list, then in the default_delegate class, 
        we can get sensor values as attributes of the sphero_mini class.
        e.g. print(sphero.IMU_yaw) # displays the current yaw angle
        '''

        # Initialize dictionary with sensor names as keys and their bool values (set by the user) as values:
        availableSensors = {
            "IMU_pitch": IMU_pitch,
            "IMU_roll": IMU_roll,
            "IMU_yaw": IMU_yaw,
            "IMU_acc_y": IMU_acc_y,
            "IMU_acc_z": IMU_acc_z,
            "IMU_acc_x": IMU_acc_x,
            "IMU_gyro_y": IMU_gyro_y,
            "IMU_gyro_x": IMU_gyro_x,
            "IMU_gyro_z": IMU_gyro_z
        }

        # Create list of of only sensors that have been "activated" (set as true in the method arguments):
        self.configured_sensors = [
            name for name in availableSensors if availableSensors[name] == True
        ]

    def sensor1(self):  # Use default values
        '''
        Unknown function. Observed in bluetooth sniffing. 
        '''
        self._send(self.API_V2_characteristic,
                   devID=deviceID['sensor'],
                   commID=sensorCommands['sensor1'],
                   payload=[0x01])

        self.getAcknowledgement("Sensor1")

    def sensor2(self):  # Use default values
        '''
        Unknown function. Observed in bluetooth sniffing. 
        '''
        self._send(self.API_V2_characteristic,
                   devID=deviceID['sensor'],
                   commID=sensorCommands['sensor2'],
                   payload=[0x00])

        self.getAcknowledgement("Sensor2")
Beispiel #10
0
class OpenBCIGanglion(object):
    """
    Handle a connection to an OpenBCI board.

    Args:
      port: MAC address of the Ganglion Board. "None" to attempt auto-detect.
      aux: enable on not aux channels (i.e. switch to 18bit mode if set)
      impedance: measures impedance when start streaming
      timeout: in seconds, if set will try to disconnect / reconnect after a period without new data
       -- should be high if impedance check
      max_packets_to_skip: will try to disconnect / reconnect after too many packets are skipped
      baud, filter_data, daisy: Not used, for compatibility with v3
    """

    def __init__(self, port=None, baud=0, filter_data=False,
                 scaled_output=True, daisy=False, log=True, aux=False, impedance=False, timeout=2,
                 max_packets_to_skip=20):
        # unused, for compatibility with Cyton v3 API
        self.daisy = False
        # these one are used
        self.log = log  # print_incoming_text needs log
        self.aux = aux
        self.streaming = False
        self.timeout = timeout
        self.max_packets_to_skip = max_packets_to_skip
        self.scaling_output = scaled_output
        self.impedance = impedance

        # might be handy to know API
        self.board_type = "ganglion"

        print("Looking for Ganglion board")
        if port == None:
            port = self.find_port()
        self.port = port  # find_port might not return string

        self.connect()

        self.streaming = False
        # number of EEG channels and (optionally) accelerometer channel
        self.eeg_channels_per_sample = 4
        self.aux_channels_per_sample = 3
        self.imp_channels_per_sample = 5
        self.read_state = 0
        self.log_packet_count = 0
        self.packets_dropped = 0
        self.time_last_packet = 0

        # Disconnects from board when terminated
        atexit.register(self.disconnect)

    def getBoardType(self):
        """ Returns the version of the board """
        return self.board_type

    def setImpedance(self, flag):
        """ Enable/disable impedance measure """
        self.impedance = bool(flag)

    def connect(self):
        """ Connect to the board and configure it. Note: recreates various objects upon call. """
        print("Init BLE connection with MAC: " + self.port)
        print("NB: if it fails, try with root privileges.")
        self.gang = Peripheral(self.port, 'random')  # ADDR_TYPE_RANDOM

        print("Get mainservice...")
        self.service = self.gang.getServiceByUUID(BLE_SERVICE)
        print("Got:" + str(self.service))

        print("Get characteristics...")
        self.char_read = self.service.getCharacteristics(BLE_CHAR_RECEIVE)[0]
        print("receive, properties: " + str(self.char_read.propertiesToString()) +
              ", supports read: " + str(self.char_read.supportsRead()))

        self.char_write = self.service.getCharacteristics(BLE_CHAR_SEND)[0]
        print("write, properties: " + str(self.char_write.propertiesToString()) +
              ", supports read: " + str(self.char_write.supportsRead()))

        self.char_discon = self.service.getCharacteristics(BLE_CHAR_DISCONNECT)[0]
        print("disconnect, properties: " + str(self.char_discon.propertiesToString()) +
              ", supports read: " + str(self.char_discon.supportsRead()))

        # set delegate to handle incoming data
        self.delegate = GanglionDelegate(self.scaling_output)
        self.gang.setDelegate(self.delegate)

        # enable AUX channel
        if self.aux:
            print("Enabling AUX data...")
            try:
                self.ser_write(b'n')
            except Exception as e:
                print("Something went wrong while enabling aux channels: " + str(e))

        print("Turn on notifications")
        # nead up-to-date bluepy, cf https://github.com/IanHarvey/bluepy/issues/53
        self.desc_notify = self.char_read.getDescriptors(forUUID=0x2902)[0]
        try:
            self.desc_notify.write(b"\x01")
        except Exception as e:
            print("Something went wrong while trying to enable notification: " + str(e))

        print("Connection established")

    def init_streaming(self):
        """ Tell the board to record like crazy. """
        try:
            if self.impedance:
                print("Starting with impedance testing")
                self.ser_write(b'z')
            else:
                self.ser_write(b'b')
        except Exception as e:
            print("Something went wrong while asking the board to start streaming: " + str(e))
        self.streaming = True
        self.packets_dropped = 0
        self.time_last_packet = timeit.default_timer()

    def find_port(self):
        """Detects Ganglion board MAC address
        If more than 1 around, will select first. Needs root privilege.
        """

        print("Try to detect Ganglion MAC address. "
              "NB: Turn on bluetooth and run as root for this to work!"
              "Might not work with every BLE dongles.")
        scan_time = 5
        print("Scanning for 5 seconds nearby devices...")

        #   From bluepy example
        class ScanDelegate(DefaultDelegate):
            def __init__(self):
                DefaultDelegate.__init__(self)

            def handleDiscovery(self, dev, isNewDev, isNewData):
                if isNewDev:
                    print("Discovered device: " + dev.addr)
                elif isNewData:
                    print("Received new data from: " + dev.addr)

        scanner = Scanner().withDelegate(ScanDelegate())
        devices = scanner.scan(scan_time)

        nb_devices = len(devices)
        if nb_devices < 1:
            print("No BLE devices found. Check connectivity.")
            return ""
        else:
            print("Found " + str(nb_devices) + ", detecting Ganglion")
            list_mac = []
            list_id = []

            for dev in devices:
                # "Ganglion" should appear inside the "value" associated
                # to "Complete Local Name", e.g. "Ganglion-b2a6"
                for (adtype, desc, value) in dev.getScanData():
                    if desc == "Complete Local Name" and value.startswith("Ganglion"):
                        list_mac.append(dev.addr)
                        list_id.append(value)
                        print("Got Ganglion: " + value +
                              ", with MAC: " + dev.addr)
                        break
        nb_ganglions = len(list_mac)

        if nb_ganglions < 1:
            print("No Ganglion found ;(")
            raise OSError('Cannot find OpenBCI Ganglion MAC address')

        if nb_ganglions > 1:
            print("Found " + str(nb_ganglions) + ", selecting first")

        print("Selecting MAC address " + list_mac[0] + " for " + list_id[0])
        return list_mac[0]

    def ser_write(self, b):
        """Access serial port object for write"""
        self.char_write.write(b)

    def ser_read(self):
        """Access serial port object for read"""
        return self.char_read.read()

    def ser_inWaiting(self):
        """ Slightly different from Cyton API, return True if ASCII messages are incoming."""
        # FIXME: might have a slight problem with thread because of notifications...
        if self.delegate.receiving_ASCII:
            # in case the packet indicating the end of the message drops, we use a 1s timeout
            if timeit.default_timer() - self.delegate.time_last_ASCII > 2:
                self.delegate.receiving_ASCII = False
        return self.delegate.receiving_ASCII

    def getSampleRate(self):
        return SAMPLE_RATE

    def getNbEEGChannels(self):
        """Will not get new data on impedance check."""
        return self.eeg_channels_per_sample

    def getNbAUXChannels(self):
        """Might not be used depending on the mode."""
        return self.aux_channels_per_sample

    def getNbImpChannels(self):
        """Might not be used depending on the mode."""
        return self.imp_channels_per_sample

    def start_streaming(self, callback, lapse=-1):
        """
        Start handling streaming data from the board. Call a provided callback
        for every single sample that is processed

        Args:
          callback: A callback function or a list of functions that will receive a single argument
                    of the OpenBCISample object captured.
        """
        if not self.streaming:
            self.init_streaming()

        start_time = timeit.default_timer()

        # Enclose callback funtion in a list if it comes alone
        if not isinstance(callback, list):
            callback = [callback]

        while self.streaming:
            # should the board get disconnected and we could not wait for notification
            # anymore, a reco should be attempted through timeout mechanism
            try:
                # at most we will get one sample per packet
                self.waitForNotifications(1. / self.getSampleRate())
            except Exception as e:
                print("Something went wrong while waiting for a new sample: " + str(e))
            # retrieve current samples on the stack
            samples = self.delegate.getSamples()
            self.packets_dropped = self.delegate.getMaxPacketsDropped()
            if samples:
                self.time_last_packet = timeit.default_timer()
                for call in callback:
                    for sample in samples:
                        call(sample)

            if (lapse > 0 and timeit.default_timer() - start_time > lapse):
                self.stop()
            if self.log:
                self.log_packet_count = self.log_packet_count + 1

            # Checking connection -- timeout and packets dropped
            self.check_connection()

    def waitForNotifications(self, delay):
        """ Allow some time for the board to receive new data. """
        self.gang.waitForNotifications(delay)

    def test_signal(self, signal):
        """ Enable / disable test signal """
        if signal == 0:
            self.warn("Disabling synthetic square wave")
            try:
                self.char_write.write(b']')
            except Exception as e:
                print("Something went wrong while setting signal: " + str(e))
        elif signal == 1:
            self.warn("Eisabling synthetic square wave")
            try:
                self.char_write.write(b'[')
            except Exception as e:
                print("Something went wrong while setting signal: " + str(e))
        else:
            self.warn(
                "%s is not a known test signal. Valid signal is 0-1" % signal)

    def set_channel(self, channel, toggle_position):
        """ Enable / disable channels """
        try:
            # Commands to set toggle to on position
            if toggle_position == 1:
                if channel is 1:
                    self.ser.write(b'!')
                if channel is 2:
                    self.ser.write(b'@')
                if channel is 3:
                    self.ser.write(b'#')
                if channel is 4:
                    self.ser.write(b'$')
            # Commands to set toggle to off position
            elif toggle_position == 0:
                if channel is 1:
                    self.ser.write(b'1')
                if channel is 2:
                    self.ser.write(b'2')
                if channel is 3:
                    self.ser.write(b'3')
                if channel is 4:
                    self.ser.write(b'4')
        except Exception as e:
            print("Something went wrong while setting channels: " + str(e))

    """
  
    Clean Up (atexit)
  
    """

    def stop(self):
        print("Stopping streaming...")
        self.streaming = False
        # connection might be already down here
        try:
            if self.impedance:
                print("Stopping with impedance testing")
                self.ser_write(b'Z')
            else:
                self.ser_write(b's')
        except Exception as e:
            print("Something went wrong while asking the board to stop streaming: " + str(e))
        if self.log:
            logging.warning('sent <s>: stopped streaming')

    def disconnect(self):
        if (self.streaming == True):
            self.stop()
        print("Closing BLE..")
        try:
            self.char_discon.write(b' ')
        except Exception as e:
            print("Something went wrong while asking the board to disconnect: " + str(e))
        # should not try to read/write anything after that, will crash
        try:
            self.gang.disconnect()
        except Exception as e:
            print("Something went wrong while shutting down BLE link: " + str(e))
        logging.warning('BLE closed')

    """
  
        SETTINGS AND HELPERS
  
    """

    def warn(self, text):
        if self.log:
            # log how many packets where sent succesfully in between warnings
            if self.log_packet_count:
                logging.info('Data packets received:' +
                             str(self.log_packet_count))
                self.log_packet_count = 0
            logging.warning(text)
        print("Warning: %s" % text)

    def check_connection(self):
        """ Check connection quality in term of lag and number of packets drop.
         Reinit connection if necessary.
         FIXME: parameters given to the board will be lost.
         """
        # stop checking when we're no longer streaming
        if not self.streaming:
            return
        # check number of dropped packets and duration without new packets, deco/reco if too large
        if self.packets_dropped > self.max_packets_to_skip:
            self.warn("Too many packets dropped, attempt to reconnect")
            self.reconnect()
        elif self.timeout > 0 and timeit.default_timer() - self.time_last_packet > self.timeout:
            self.warn("Too long since got new data, attempt to reconnect")
            # if error, attempt to reconect
            self.reconnect()

    def reconnect(self):
        """ In case of poor connection, will shut down and relaunch everything.
        FIXME: parameters given to the board will be lost."""
        self.warn('Reconnecting')
        self.stop()
        self.disconnect()
        self.connect()
        self.init_streaming()
Beispiel #11
0
  print "Info, trying to connect to:", sys.argv[1]
  p = Peripheral(sys.argv[1])
  print "Info, connected and turning notify and sensor on!"

  ch = p.getCharacteristics(uuid=config_uuid)[0]
  ch.write(sensorOn, withResponse=True)
  
  # With bluepy if you need to read or write a Descriptor you have to 
  # access it using Peripheral readCharacteristic or writeCharacteristic 
  # and the appropriate handle
  p.setDelegate( MyDelegate("keys") )
  p.writeCharacteristic(notifyHnd, notifyOn)

  while True:
    try:
      if p.waitForNotifications(1.0):
        # handleNotification() was called
        if keys & 0x01:
          print "Info, Right Button"
        if keys & 0x02:
          print "Info, Left Button"
        if keys & 0x04:
          print "Info, Reed Switch"
    
    except KeyboardInterrupt:
      print "exiting..."
      ctrl_c=True
      
except:
  if not ctrl_c:
    print "Fatal, unexpected error!"
            err = e
            break  # forを抜ける
        print('read  Notify Config =', hex(hnd), val.hex())
        if val != data:
            err = 'Notifications Verify Error'
            print('ERROR:', err)
            break  # forを抜ける
    if err is not None:
        del p
        continue  # スキャンへ戻る

    # GATT処理部4.Notify待ち受け
    print('Waiting for Notify...')
    while True:
        try:
            notified = p.waitForNotifications(interval)
        except btle.BTLEDisconnectError as e:
            print('ERROR:', e)
            break
        if notified:
            notified_val = myDelegate.value()
            if type(notified_val) is bytes and len(notified_val) > 0:
                s = notified_val.decode()
                if len(s) >= 2 and s[0:2] == 'AT':
                    print('VSSPPを切断します')
                    p.disconnect()
                    del p
                    break  # whileを抜ける
                print('    Value =', s)

################################################################################
Beispiel #13
0
class MiKettle(object):
    """"
    A class to control mi kettle device.
    """
    def __init__(self,
                 mac,
                 product_id,
                 cache_timeout=600,
                 retries=3,
                 iface=None,
                 token=None):
        """
        Initialize a Mi Kettle for the given MAC address.
        """
        _LOGGER.debug('Init Mikettle with mac %s and pid %s', mac, product_id)

        self._mac = mac
        self._reversed_mac = MiKettle.reverseMac(mac)

        self._cache = None
        self._cache_timeout = timedelta(seconds=cache_timeout)
        self._last_read = None
        self.retries = retries
        self.ble_timeout = 10
        self.lock = Lock()

        self._product_id = product_id
        self._iface = iface
        # Generate token if not supplied
        if token is None:
            token = MiKettle.generateRandomToken()
        self._token = token

    def connect(self):
        try:
            if self._p.getState() == "conn":
                _LOGGER.debug("already connected")
                return
        except Exception as e:
            if "Unexpected response (ntfy)" == str(e):
                _LOGGER.debug("already connected")
                return
            elif "'MiKettle' object has no attribute '_p'" == str(e):
                pass
            elif "Helper not started (did you call connect()?)" == str(e):
                pass
            else:
                raise e
        _LOGGER.debug("connecting")
        self.authed = False
        self._p = Peripheral(deviceAddr=self._mac, iface=self._iface)
        self._p.setDelegate(self)

    def name(self):
        """Return the name of the device."""
        self.connect()
        self.auth()
        name = self._p.readCharacteristic(_HANDLE_READ_NAME)

        if not name:
            raise Exception("Could not read NAME using handle %s"
                            " from Mi Kettle %s" %
                            (_HANDLE_READ_NAME, self._mac))
        return ''.join(chr(n) for n in name)

    def firmware_version(self):
        """Return the firmware version."""
        self.connect()
        self.auth()
        firmware_version = self._p.readCharacteristic(
            _HANDLE_READ_FIRMWARE_VERSION)

        if not firmware_version:
            raise Exception("Could not read FIRMWARE_VERSION using handle %s"
                            " from Mi Kettle %s" %
                            (_HANDLE_READ_FIRMWARE_VERSION, self._mac))
        return ''.join(chr(n) for n in firmware_version)

    def parameter_value(self, parameter, read_cached=True):
        """Return a value of one of the monitored paramaters.
        This method will try to retrieve the data from cache and only
        request it by bluetooth if no cached value is stored or the cache is
        expired.
        This behaviour can be overwritten by the "read_cached" parameter.
        """
        # Use the lock to make sure the cache isn't updated multiple times
        with self.lock:
            if (read_cached is False) or \
                    (self._last_read is None) or \
                    (datetime.now() - self._cache_timeout > self._last_read):
                self.fill_cache()
            else:
                _LOGGER.debug("Using cache (%s < %s)",
                              datetime.now() - self._last_read,
                              self._cache_timeout)

        if self.cache_available():
            return self._cache[parameter]
        else:
            raise Exception("Could not read data from MiKettle %s" % self._mac)

    def fill_cache(self):
        """Fill the cache with new data from the sensor."""
        _LOGGER.debug('Filling cache with new sensor data.')
        try:
            _LOGGER.debug('Connect')
            self.connect()
            _LOGGER.debug('Auth')
            self.auth()
            _LOGGER.debug('Subscribe')
            self.subscribeToData()
            _LOGGER.debug('Wait for data')
            self._p.waitForNotifications(self.ble_timeout)
            # If a sensor doesn't work, wait 5 minutes before retrying
        except Exception as error:
            _LOGGER.debug('Error %s', error)
            self._last_read = datetime.now() - self._cache_timeout + \
                timedelta(seconds=300)
            return

    def clear_cache(self):
        """Manually force the cache to be cleared."""
        self._cache = None
        self._last_read = None

    def cache_available(self):
        """Check if there is data in the cache."""
        return self._cache is not None

    def _parse_data(self, data):
        """Parses the byte array returned by the sensor."""
        res = dict()
        res[MI_ACTION] = MI_ACTION_MAP[int(data[0])]
        res[MI_MODE] = MI_MODE_MAP[int(data[1])]
        res[MI_SET_TEMPERATURE] = int(data[4])
        res[MI_CURRENT_TEMPERATURE] = int(data[5])
        res[MI_KW_TYPE] = MI_KW_TYPE_MAP[int(data[6])]
        res[MI_KW_TIME] = MiKettle.bytes_to_int(data[7:8])
        return res

    @staticmethod
    def bytes_to_int(bytes):
        result = 0
        for b in bytes:
            result = result * 256 + int(b)

        return result

    def auth(self):
        if self.authed:
            return
        auth_service = self._p.getServiceByUUID(_UUID_SERVICE_KETTLE)
        auth_descriptors = auth_service.getDescriptors()

        self._p.writeCharacteristic(_HANDLE_AUTH_INIT, _KEY1, "true")

        auth_descriptors[1].write(_SUBSCRIBE_TRUE, "true")

        self._p.writeCharacteristic(
            _HANDLE_AUTH,
            MiKettle.cipher(
                MiKettle.mixA(self._reversed_mac, self._product_id),
                self._token), "true")

        self._p.waitForNotifications(10.0)

        self._p.writeCharacteristic(_HANDLE_AUTH,
                                    MiKettle.cipher(self._token, _KEY2),
                                    "true")

        self._p.readCharacteristic(_HANDLE_VERSION)
        self.authed = True

    def subscribeToData(self):
        controlService = self._p.getServiceByUUID(_UUID_SERVICE_KETTLE_DATA)
        controlDescriptors = controlService.getDescriptors()
        controlDescriptors[3].write(_SUBSCRIBE_TRUE, "true")

    # TODO: Actually generate random token instead of static one
    @staticmethod
    def generateRandomToken() -> bytes:
        return bytes([
            0x01, 0x5C, 0xCB, 0xA8, 0x80, 0x0A, 0xBD, 0xC1, 0x2E, 0xB8, 0xED,
            0x82
        ])

    @staticmethod
    def reverseMac(mac) -> bytes:
        parts = mac.split(":")
        reversedMac = bytearray()
        leng = len(parts)
        for i in range(1, leng + 1):
            reversedMac.extend(bytearray.fromhex(parts[leng - i]))
        return reversedMac

    @staticmethod
    def mixA(mac, productID) -> bytes:
        return bytes([
            mac[0], mac[2], mac[5], (productID & 0xff), (productID & 0xff),
            mac[4], mac[5], mac[1]
        ])

    @staticmethod
    def mixB(mac, productID) -> bytes:
        return bytes([
            mac[0], mac[2], mac[5], ((productID >> 8) & 0xff), mac[4], mac[0],
            mac[5], (productID & 0xff)
        ])

    @staticmethod
    def _cipherInit(key) -> bytes:
        perm = bytearray()
        for i in range(0, 256):
            perm.extend(bytes([i & 0xff]))
        keyLen = len(key)
        j = 0
        for i in range(0, 256):
            j += perm[i] + key[i % keyLen]
            j = j & 0xff
            perm[i], perm[j] = perm[j], perm[i]
        return perm

    @staticmethod
    def _cipherCrypt(input, perm) -> bytes:
        index1 = 0
        index2 = 0
        output = bytearray()
        for i in range(0, len(input)):
            index1 = index1 + 1
            index1 = index1 & 0xff
            index2 += perm[index1]
            index2 = index2 & 0xff
            perm[index1], perm[index2] = perm[index2], perm[index1]
            idx = perm[index1] + perm[index2]
            idx = idx & 0xff
            outputByte = input[i] ^ perm[idx]
            output.extend(bytes([outputByte & 0xff]))

        return output

    @staticmethod
    def cipher(key, input) -> bytes:
        perm = MiKettle._cipherInit(key)
        return MiKettle._cipherCrypt(input, perm)

    def handleNotification(self, cHandle, data):
        if cHandle == _HANDLE_AUTH:
            if (MiKettle.cipher(
                    MiKettle.mixB(self._reversed_mac, self._product_id),
                    MiKettle.cipher(
                        MiKettle.mixA(self._reversed_mac, self._product_id),
                        data)) != self._token):
                raise Exception("Authentication failed.")
        elif cHandle == _HANDLE_STATUS:
            _LOGGER.debug("Status update:")
            if data is None:
                return

            _LOGGER.debug("Parse data: %s", data)
            self._cache = self._parse_data(data)
            _LOGGER.debug("data parsed %s", self._cache)

            if self.cache_available():
                self._last_read = datetime.now()
            else:
                # If a sensor doesn't work, wait 5 minutes before retrying
                self._last_read = datetime.now() - self._cache_timeout + \
                    timedelta(seconds=300)
        else:
            _LOGGER.error("Unknown notification from handle: %s with Data: %s",
                          cHandle, data.hex())
Beispiel #14
0
class BluepyBackend(AbstractBackend):
    """Backend for Miflora using the bluepy library."""
    def __init__(self, adapter: str = 'hci0', address_type: str = 'public'):
        """Create new instance of the backend."""
        super(BluepyBackend, self).__init__(adapter)
        self.address_type = address_type
        self._peripheral = None

    @wrap_exception
    def connect(self, mac: str):
        """Connect to a device."""
        from bluepy.btle import Peripheral
        match_result = re.search(r'hci([\d]+)', self.adapter)
        if match_result is None:
            raise BluetoothBackendException(
                'Invalid pattern "{}" for BLuetooth adpater. '
                'Expetected something like "hci0".'.format(self.adapter))
        iface = int(match_result.group(1))
        self._peripheral = Peripheral(mac,
                                      iface=iface,
                                      addrType=self.address_type)

    @wrap_exception
    def disconnect(self):
        """Disconnect from a device if connected."""
        if self._peripheral is None:
            return

        self._peripheral.disconnect()
        self._peripheral = None

    @wrap_exception
    def read_handle(self, handle: int) -> bytes:
        """Read a handle from the device.

        You must be connected to do this.
        """
        if self._peripheral is None:
            raise BluetoothBackendException('not connected to backend')
        return self._peripheral.readCharacteristic(handle)

    @wrap_exception
    def write_handle(self, handle: int, value: bytes):
        """Write a handle from the device.

        You must be connected to do this.
        """
        if self._peripheral is None:
            raise BluetoothBackendException('not connected to backend')
        return self._peripheral.writeCharacteristic(handle, value, True)

    @wrap_exception
    def wait_for_notification(self, handle: int, delegate,
                              notification_timeout: float):
        if self._peripheral is None:
            raise BluetoothBackendException('not connected to backend')
        self.write_handle(handle, self._DATA_MODE_LISTEN)
        self._peripheral.withDelegate(delegate)
        return self._peripheral.waitForNotifications(notification_timeout)

    @staticmethod
    def check_backend() -> bool:
        """Check if the backend is available."""
        try:
            import bluepy.btle  # noqa: F401 #pylint: disable=unused-import
            return True
        except ImportError as importerror:
            _LOGGER.error('bluepy not found: %s', str(importerror))
        return False

    @staticmethod
    @wrap_exception
    def scan_for_devices(timeout: float) -> List[Tuple[str, str]]:
        """Scan for bluetooth low energy devices.

        Note this must be run as root!"""
        from bluepy.btle import Scanner

        scanner = Scanner()
        result = []
        for device in scanner.scan(timeout):
            result.append((device.addr, device.getValueText(9)))
        return result
Beispiel #15
0
class BLE:

    def __init__(self, bmac, t, iface=0):
        self.device = None
        self.bmac = bmac
        self.type = t
        self.iface = iface

    def get_peripheral_device(self):
        return self.device

    ## CONNECTION BEGIN

    def connect(self):
       self.device = Peripheral(self.bmac, self.type, self.iface)
       print_ok("connected")

    def disconnect(self):
        if self.device:
            self.device.disconnect()
            self.device = None
            print_info("\nDisconnected")

    ## CONNECTION END

    ## CHARACTERISTICS BEGIN
            
    def get_characteristics(self):
        try:
            return self.device.getCharacteristics()
        except:
            return self._get_characteristics_aux()
    
    def _get_characteristics_aux(self):
        try:
            characteristics = []
            for service in self.device.services:
                for ch in service.getCharacteristics():
                    characteristics.append(ch)
            return characteristics
        except:
            return None
    
    def get_characteristic_by_uuid(self, uuid):
        try:
            return self.device.getCharacteristics(uuid=uuid)[0]
        except:
            return self._get_characteristic_by_uuid_aux(uuid)

    def _get_characteristic_by_uuid_aux(self, uuid):
        try:
            for ch in self._get_characteristics_aux():
                if ch.uuid == uuid:
                    return ch
        except:
            return None

    ## CHARACTERISTICS END
        
    ## READ BEGIN

    def read_specific_characteristic(self, uuid):
        ch = self.get_characteristic_by_uuid(uuid)
        if ch:
            self._print_char(ch)

    def read_all_characteristics(self):
        characts = self.get_characteristics()
        if characts:
            self._print_characteristics(characts)

    
    def _print_characteristics(self, characteristics, uuid=None):
        for ch in characteristics:
            try:
                self._print_char(ch)
            except:
                pass

    def _print_char(self, ch):
        print_info(f"<b>{ch.uuid.getCommonName()}</b>")
        print_info(f"|_ uuid: {ch.uuid}")
        handle = ch.handle
        print_info(f"|_ handle: {hex(handle)} ({handle})")
        if (ch.supportsRead()):
            try:
                data = ch.read()
                data_decode = data.decode(errors="ignore")
                if data_decode:
                    data = f"{data_decode}"
                print_info(f"|_ value: {data}")
            except:
                try:
                    print_info(f"|_ value: {data}")
                except:
                    print_info("|_ value: <ansired>Couldn't read</ansired>")
        print_info(f"|_ properties: {ch.propertiesToString()}")

    ## READ END

    ## WRITE BEGIN

    def write_data(self, data, uuid):
        try:
            characteristics = self.get_characteristics()
            for ch in characteristics:
                if ch.uuid == uuid:
                    if self._is_writable(ch.propertiesToString()):
                        print_ok("Good! It's writable!")
                        try:
                            ch.write(data)
                            print_ok("Done!")
                        except:
                            print_error("It has not been written")
                    else:
                        print_error("It is not writable")
        except:
            pass

    def _is_writable(self, properties):
        return "WRITE" in properties

    ## WRITE END
    

    ## SUBSCRIBE BEGIN

    def subscribe(self):
        while True:
            try:
                self.device.waitForNotifications(1.0)
            except KeyboardInterrupt:
                print("Module Interrupted")
                return True
            except BTLEDisconnectError:
                print_error("Device disconnected...")
            except:
                self.disconnect()

    def set_delegate(self, delegate):
        self.device.setDelegate(delegate())
Beispiel #16
0
                    ):  #      but is not done due to a Bluez/BluePy bug :(
                    print(
                        "Client Characteristic Configuration found at handle 0x"
                        + format(descriptor.handle, "02X"))
                    read_cccd = descriptor.handle
                    #spp_cccd = descriptor.handle
                    p.writeCharacteristic(read_cccd,
                                          struct.pack('<bb', 0x01, 0x00))
                    #p.writeCharacteristic(spp_cccd, struct.pack('<bb', 0x01, 0x00))
                    #print("pack ", struct.pack('<bb', 0x01, 0x00))

            #############################################
            # BLE message loop
            #############################################
            while True:
                if p.waitForNotifications(5.0):
                    # handleNotification() was called
                    continue
                    #pass

                # p.writeCharacteristic(hButtonCCC, struct.pack('<bb', 0x01, 0x00))
                #print("waiting ... ")
                write_char.write(str.encode("Hello GTO \n"))
                #read_char.read()
                #spp_send_data.write(str.encode("spp send data\n"))
                #spp_send_data.read()
                print("OK ---> Send write data!")

    finally:
        p.disconnect()
Beispiel #17
0
class BTConnectedDevice(object):
    """Handles connecting and commands for bluetooth devices"""
    peripheral = None
    console_write_characteristic = None
    auth_characteristic = None
    console_enable_char = None
    batt_char = None
    delegate = None

    def connect(self, addr):
        """Connect to the device with the given address"""
        self.peripheral = Peripheral(str(addr), "random")
        self.peripheral.setMTU(272)  # 256 16 for control or headers etc.
        self.delegate = BLEConsoleDelegate()
        self.peripheral.withDelegate(self.delegate)
        self.print_chars_and_handles()
        #self.auth_characteristic = self.get_char("6E400005-B5A3-F393-E0A9-E50E24DCCA9E")
        #self.auth_characteristic.write(struct.pack("16s", "iwanttobeajiobit"), False)
        self.console_enable_char = self.get_char(
            "6E400004-B5A3-F393-E0A9-E50E24DCCA9E")
        self.console_enable_char.write(struct.pack('16s', "iwanttobeajiobit"),
                                       False)

        self.console_write_characteristic = self.get_char(
            "6E400002-B5A3-F393-E0A9-E50E24DCCA9E")
        self.peripheral.waitForNotifications(1)

    def disconnect(self):
        """Disconnect from the bluetooth device"""
        self.peripheral.disconnect()

    def connect_strongest_mfg(self):
        """Connect to the strongest mfg signal"""
        returned_devices = self.scan_for_bt_devices(BTDeviceTypes.MFG)
        strongest_rssi_dev = returned_devices[0]
        self.connect(strongest_rssi_dev.addr)

    def connect_strongest_app(self):
        """Connect to the stronest app signal"""
        returned_devices = self.scan_for_bt_devices(BTDeviceTypes.ANY)
        strongest_rssi_dev = returned_devices[0]
        sleep(1)
        self.connect(strongest_rssi_dev.addr)

    def connect_advertised_name(self, ad_name):
        """Connect to the strongest signal with a given advertised name"""
        returned_devices = self.scan_for_bt_devices(BTDeviceTypes.ANY)
        for device in returned_devices:
            device_name = self.get_device_name(device)
            if device_name == ad_name:
                sleep(1)
                self.connect(device.addr)
                return True
        bt_info_print(
            "could not find advertising name {} in scan".format(ad_name))
        return False

    def scan_for_bt_devices(self, device_type):
        """Scan for Bluetooth devices"""
        scanner = Scanner()

        devices = scanner.scan()

        # keep only desired kind of bt device names
        correct_type_devices = self.get_devices_typed(devices, device_type)

        #sort list by RSSI
        correct_type_devices.sort(key=lambda x: x.rssi, reverse=True)

        for count, entry in enumerate(correct_type_devices):
            bt_info_print(
                "{index}:RSSI - {rssi}, Device Name - {devicename}, Address - {address}"
                .format(index=(count + 1),
                        rssi=entry.rssi,
                        devicename=self.get_device_name(entry),
                        address=entry.addr))

        return correct_type_devices

    def get_devices_typed(self, devlist, device_type):
        """Filter a list of devices using a given device type"""
        desired_devices = list()
        for entry in devlist:
            device_name = self.get_device_name(entry)
            if (device_name != None):
                if ((device_type == BTDeviceTypes.ANY)
                        or (device_type == BTDeviceTypes.MFG
                            and len(device_name) == 12)
                        or (device_type == BTDeviceTypes.APP
                            and device_name == "Jiobit")):
                    desired_devices.append(entry)
        return desired_devices

    def get_device_name(self, dev):
        """Return the name of the given device"""
        devname = None
        try:
            for (sdid, _, val) in dev.getScanData():
                if (sdid == 9):
                    devname = val
        except:
            return None
        return devname

    def send_console_cmd(self, command_str):
        """Send a bluetooth console command"""
        command_str = command_str + "\r\n"
        self.console_write_characteristic.write(command_str)
        #self.peripheral.waitForNotifications(1)

    def send_cmd_wait_resp_time(self, command_str, wait_sec):
        """Send a bluetooth console command and wait for a response"""
        command_str = command_str + "\r\n"
        self.console_write_characteristic.write(command_str)
        #sleep(wait_sec)
        for _ in range(0, wait_sec):
            self.peripheral.waitForNotifications(1)
        return_str = self.delegate.print_clear_console()
        return return_str

    def get_char(self, uuid):
        """Get the characteristic object associated with the given UUID"""
        characteristic = self.peripheral.getCharacteristics(uuid=uuid)[0]
        return characteristic

    def print_chars_and_handles(self):
        """
        Print all characteristics and handles for the connected device

        Also, populate some members of the delegate
        """
        characteristics = self.peripheral.getCharacteristics()
        for characteristic in characteristics:
            uuid = str(characteristic.uuid)
            handle = characteristic.getHandle()
            hex_handle = hex(handle)
            bt_info_print("characteristic " + uuid + " handle: " + hex_handle)
            if (uuid == CONSOLE_BIT_TX_UUID):
                self.delegate.console_bit_tx_handle = int(handle)
            if (uuid == MSGPACK_APPEND_UUID):
                self.delegate.msgpack_append_handle = int(handle)
            if (uuid == MSGPACK_DONE_UUID):
                self.delegate.msgpack_append_handle = int(handle)

    def read_batt_char(self):
        """Read the connected device's battery characteristic"""
        if (self.batt_char is None):
            self.batt_char = self.get_char(
                "00002A19-0000-1000-8000-00805F9B34FB")
        if (self.batt_char.supportsRead()):
            return str(self.batt_char.read())
        else:
            bt_info_print("does not support read")
            return None
Beispiel #18
0
class Yeelight(DefaultDelegate):
    WRITE_CHAR_UUID = b"aa7d3f34"  # -2d4f-41e0-807f-52fbf8cf7443"

    COMMAND_STX = "43"
    COMMAND_ETX = "00"

    AUTH_CMD = "67"
    AUTH_ON = "02"

    POWER_CMD = "40"
    POWER_ON = "01"
    POWER_OFF = "02"

    COLOR_CMD = "41"
    RGB_MODE = "65"

    BRIGHT_CMD = "42"

    COLORTEMP_CMD = "43"
    TEMP_MODE = "65"

    STATUS_CMD = "44"

    COLORFLOW_CMD = "4a"

    SLEEP_CMD = "7f03"

    def __init__(self, address):
        DefaultDelegate.__init__(self)
        self.__address = address
        self.__connect()

    # Override
    def handleNotification(self, handle, data):
        if handle == 21:
            format = (
                '!xx' # 4345 header
                'B' # switch: 01=on 02=off
                'B' # mode: 01=rgb 02=warm
                'BBBx' # RGB
                'B' # Brightness
                'H' # temp 2byte 1700 ~ 6500
                'xxxxxxx'
            )
            (switch, mode, r, g, b,
             brightness, temp) = struct.unpack(format, data)
            if switch != 4:
                self._switch = switch
                self._mode = mode
                self._rgb = '{:02x}{:02x}{:02x}'.format(r, g, b)
                self._temp = temp
                self._brightness = brightness

    def disconnect(self, *args, **kwargs):
        return self.__peripheral.disconnect(*args, **kwargs)

    def __connect(self):
        self.__peripheral = Peripheral(self.__address)
        self.__peripheral.setDelegate(self)
        characteristics = self.__peripheral.getCharacteristics()
        self.__ch = next(iter(filter(lambda x: binascii.b2a_hex(x.uuid.binVal)
                           .startswith(self.WRITE_CHAR_UUID),
                           characteristics)))

        # Register notification
        self.__peripheral.writeCharacteristic(
            0x16,
            binascii.a2b_hex('0100'))

        # Auth
        self.__write_cmd(
            self.COMMAND_STX +
            self.AUTH_CMD +
            self.AUTH_ON +
            self.COMMAND_ETX * 15)

        # Get status
        self.__request_status()

    def __write_cmd(self, value):
        for _ in range(3):
            try:
                self.__ch.write(binascii.a2b_hex(value))
                self.__peripheral.waitForNotifications(1.0)
            except BTLEException as e:
                error = e
                self.__connect()
            else:
                break
        else:
            raise error

    def __request_status(self):
        self.__write_cmd(
            self.COMMAND_STX +
            self.STATUS_CMD +
            self.COMMAND_ETX * 16
        )

    @property
    def switch(self):
        self.update_status()
        return self._switch

    @property
    def brightness(self):
        self.update_status()
        return self._brightness

    @property
    def temp(self):
        self.update_status()
        return self._temp

    @property
    def rgb(self):
        self.update_status()
        return self._rgb

    @property
    def mode(self):
        self.update_status()
        return self._mode

    def poweron(self):
        self.__write_cmd(
            self.COMMAND_STX +
            self.POWER_CMD +
            self.POWER_ON +
            self.COMMAND_ETX * 15)

    def poweroff(self):
        self.__write_cmd(
            self.COMMAND_STX +
            self.POWER_CMD +
            self.POWER_OFF +
            self.COMMAND_ETX * 15)

    def set_rgb(self, rgb):
        self.__write_cmd(
            self.COMMAND_STX +
            self.COLOR_CMD +
            rgb + '00' +
            self.RGB_MODE +
            self.COMMAND_ETX * 11)

    def set_brightness(self, value):
        self.__write_cmd(
            self.COMMAND_STX +
            self.BRIGHT_CMD +
            ('%02x' % value) +
            self.COMMAND_ETX * 15)
        # Bypass bug
        self.__request_status()

    def set_temp(self, value):
        if not 1700 <= value <= 6500 and 0.0 <= value <= 1.0:
            value = int(1700 + value * 4800)

        self.__write_cmd(
            self.COMMAND_STX +
            self.COLORTEMP_CMD +
            ('%04x' % value) +
            self.TEMP_MODE +
            self.COMMAND_ETX * 13)

    def set_sleep(self, minute):
        self.__write_cmd(
            self.COMMAND_STX +
            self.SLEEP_CMD +
            ('%02x' % minute) +
            self.COMMAND_ETX * 14
        )

    def update_status(self):
        self.__peripheral.waitForNotifications(0.01)
Beispiel #19
0
class LowerLayer(object):
    states = [
        {'name': 'disconnected'}, # no state is present with the device
        {'name': 'connected', 'on_enter': 'on_enter_connected', 'timeout': 5.0, 'on_timeout': 'on_enter_connected'}, # connected on BLE level
        {'name': 'send', 'on_enter': 'on_enter_send'}, # send a pdu
        {'name': 'wait_ack', 'on_enter': 'on_enter_wait_ack', 'timeout': 5.0, 'on_timeout': 'on_timeout_wait_ack'},
        {'name': 'wait_answer', 'on_enter': 'on_enter_wait_answer', 'timeout': 5.0, 'on_timeout': 'on_timeout_wait_answer'},
        {'name': 'error', 'on_enter': 'on_enter_error'}, # error state without any further operation
        {'name': 'disconnect'}, # error state without any further operation
    ]

    transitions = [
        {
            'trigger': 'ev_connected',
            'source': 'disconnected',
            'dest': 'connected'
        },
        {
            'trigger': 'ev_enqueue_message',
            'source': 'connected',
            'dest': 'send'
        },
        {
            'trigger': 'ev_send_fragment',
            'source': 'send',
            'dest': 'wait_ack'
        },
        {
            'trigger': 'ev_ack_received',
            'source': 'wait_ack',
            'dest': 'send'
        },
        {
            'trigger': 'ev_finished', # when everything was sent and received in queue
            'source': 'send',
            'dest': 'wait_answer'
        },
        {
            'trigger': 'ev_received', # when everything was sent and received in queue
            'source': 'wait_answer',
            'dest': 'connected'
        },
        {
            'trigger': 'ev_nothing_to_send', # when reaching state send, but nothing to send
            'source': 'send',
            'dest': 'connected'
        },
        {
            'trigger': 'ev_disconnect',
            'source': '*',
            'dest': 'disconnect'
        },
        {
            'trigger': 'ev_error',
            'source': '*',
            'dest': 'error'
        },
    ]

    def __init__(self, mac):
        self.state = None
        self.machine = TimeoutMachine(self,
                                      states=LowerLayer.states,
                                      transitions=LowerLayer.transitions,
                                      initial='disconnected')

        self.timeout = 1
        # should it raise Exception on invalid data?
        self.ignore_invalid = False

        # ble
        self._mac = mac
        self._ble_node = Peripheral()
        self._ble_node.setDelegate(self)
        # the ble service
        self._ble_service = None
        # the ble characteristic on the self._service
        self._ble_recv = None
        self._ble_send = None

        self._recv_fragments = []
        self._recv_fragment_index = 0
        self._recv_fragment_try = 1

        self._send_fragments = []
        self._send_fragment_index = 0
        self._send_fragment_try = 1

        self._send_messages = Queue()
        self._control = Queue()

        # The receive callback of the user
        self._recv_cb = None
        # The error callback of the user
        self._error_cb = None

        self._running = True
        self._thread = threading.Thread(target=self.work, name="lowerlayer")
        self._thread.start()

    def handleNotification(self, handle, data):
        """ called by the ble stack """

        LOG.info("Received on %x data : %s", handle, data)
        if not self._ble_recv:
            return
        if handle != self._ble_recv.getHandle():
            return
        if not data:
            return

        # TODO: split between Fragment/FragmentAck. Are there more messages to receive here?
        try:
            fragment = Fragment.decode(data)
        except InvalidData:
            if self.ignore_invalid:
                return
            raise

        # FragmentAck?
        # FIXME: hack
        if fragment.status == 0x80 and fragment.payload[0] == 0x00:
            if fragment.payload[1] != self._send_fragments[self._send_fragment_index][0]:
                LOG.err("Received unknown FragmentAck")
                return
            else:
                LOG.info("Received correct FragmentAck")
            self.ev_ack_received()
            return

        self.ev_received()

        self._recv_fragments += [data]
        message, self._recv_fragments = decode_fragment(self._recv_fragments)

        # this is not the last fragment, send an ack
        if not message:
            LOG.debug("Sending FragmentAck")
            self._send_pdu(FragmentAck(fragment.status).encode())
            return

        if len(message) > 1:
            raise RuntimeError("To many messages received")

        # try to decode message
        message = message[0]
        raw = message
        message_type = message[0]
        LOG.info("Received message type 0x%x", message_type)
        try:
            if not message_type in MESSAGES:
                self._error("Can not find Message")
                return
            message_cls = MESSAGES[message_type]
            message = message_cls.decode(message)
            LOG.info("Received decoded message %s <- %s", message, raw.hex())
        except Exception as exp:
            LOG.info("Receive exception %s", exp)
            if self.ignore_invalid:
                return

        if self._recv_cb:
            self._recv_cb(message)

    def _send_pdu(self, pdu):
        """ send a pdu (a byte array) """
        if not self._ble_send:
            raise RuntimeError("Can not send a message without a Connection")

        return self._ble_send.write(pdu, True)

    def _error(self, error):
        if self._error_cb:
            self._error_cb(error)

    def on_enter_connected(self):
        # reset send fragments
        self._send_fragments = []
        self._send_fragment_index = 0
        self._send_fragment_try = 1


    def on_enter_send(self):
        """ send the next fragment """
        # TODO: set timeout
        if not self._send_fragments:
            LOG.debug("OnSend: No fragment, try generating fragments")
            if not self._send_messages.empty():
                self._send_fragments = encode_fragment(self._send_messages.get())
                self._send_fragment_index = -1
                LOG.debug("OnSend: Encoded %d fragments", len(self._send_fragments))
            else:
                # No message or fragment left
                self.ev_nothing_to_send()
                return

        self._send_fragment_index += 1
        self._send_fragment_try = 0

        LOG.debug("send_fragment %d %d", len(self._send_fragments), self._send_fragment_index)

        if len(self._send_fragments) <= self._send_fragment_index + 1:
            self._send_pdu(self._send_fragments[self._send_fragment_index])
            # last message
            self.ev_finished()
        else:
            # when not the last message, we're expecting an FragmentAck
            self.ev_send_fragment()
            self._send_pdu(self._send_fragments[self._send_fragment_index])

    def on_timeout_wait_ack(self):
        # resend
        if self._send_fragment_try <= 3:
            self._send_pdu(self._send_fragments[self._send_fragment_index])
        else:
            self._error("Lock is not sending FragmentAcks!")
            self.ev_error()

    def on_timeout_wait_answer(self):
        """ when waiting for an answer, we might even have to re-send the last fragment """
        LOG.error("Timeout occured in wait answer, resending last fragment")
        if self._send_fragment_try <= 3:
            self._send_pdu(self._send_fragments[self._send_fragment_index])

    def on_enter_wait_answer(self):
        self._recv_fragment_index = 0
        self._recv_fragment_try = 1

    def on_enter_disconnect(self):
        self._ble_node.disconnect()
        self._ble_service = None
        self._ble_send = None
        self._ble_recv = None
        del self._ble_node
        self._ble_node = None

    def on_enter_wait_ack(self):
        pass

    def _connect(self):
        self._ble_node.connect(self._mac)
        self._ble_node.getServices()
        self._ble_service = self._ble_node.getServiceByUUID(LOCK_SERVICE)
        self._ble_send = self._ble_service.getCharacteristics(LOCK_SEND_CHAR)[0]
        self._ble_recv = self._ble_service.getCharacteristics(LOCK_RECV_CHAR)[0]
        self.ev_connected()

    def work(self):
        """ runs in a seperate thread """
        try:
            while self._running:
                if not self._control.empty():
                    control, payload = self._control.get()
                    if control == MSG_CONNECT:
                        LOG.debug("Connecting to BLE")
                        self._connect()
                    elif control == MSG_DISCONNECT:
                        LOG.debug("Disconnecting to BLE")
                        self.ev_disconnect()
                        break
                    elif control == MSG_SEND:
                        LOG.debug("Sending message to BLE")
                        self._send_messages.put(payload)
                        self.ev_enqueue_message()
                if self.state != "disconnected":
                    self._ble_node.waitForNotifications(self.timeout)
                else:
                    time.sleep(0.1)
        except Exception as e:
            self._error("Exception occured %s" % e)

    # user api functions
    def disconnect(self):
        self._control.put((MSG_DISCONNECT, None))

    def connect(self):
        self._control.put((MSG_CONNECT, None))

    def send(self, message):
        """ send messages. "Big" (> 31byte) messages must be splitted into multiple fragments """
        self._control.put((MSG_SEND, message))

    def set_on_receive(self, callback):
        """ sets the callback when a message has been received.
        The callback must have the signature callback(message), while message is a list of byte of one message. """
        self._recv_cb = callback

    def set_on_error(self, callback):
        """ sets the callback when a message has been received.
        The callback must have the signature callback(error). """
        self._error_cb = callback
Beispiel #20
0
class BtlePeripheral():
    def __init__(self, addr, producer, loop, receive_uuid = 0x2221, send_uuid = 0x2222, security = False):
        self._addr = addr
        self._producer = producer
        self._receive_uuid = receive_uuid
        self._send_uuid = send_uuid
        self._loop = loop
        self._security = security
        self._p = None

    def start(self):
        # init btle ElementReader
        el = BtleNode(self._producer.onBtleData, None, None)
        em = ElementReader(el)

        class MyDelegate(DefaultDelegate):
            def __init__(self):
                DefaultDelegate.__init__(self)
                
            def handleNotification(self, cHandle, data):
                # TODO: this should handle incorrect format caused by packet losses
                try:
                    em.onReceivedData(data[2:])
                except ValueError as e:
                    print "Decoding value error: " + str(e)
                # connect ble
        
        while not self._p:
            try:
                self._p = Peripheral(self._addr, "random")
            except BTLEException as e:
                print "Failed to connect: " + str(e) + "; trying again"
                self._p = None

        # tell rfduino we are ready for notifications
        self._p.setDelegate(MyDelegate())

        self._p.writeCharacteristic(self._p.getCharacteristics(uuid = self._receive_uuid)[0].valHandle + 1, "\x01\x00")
        self._loop.create_task(self.btleNotificationListen())
        
        if self._security:
            # send our public key if configured to do so
            print "security on, sending our public key"
            interest = self._producer.makePublicKeyInterest()
            # write characteristics
            data = interest.wireEncode().toRawStr()
            num_fragments = int(math.ceil(float(len(data)) / 18))
            print "length of data: " + str(len(data)) + "; number of fragments: " + str(num_fragments)
            current = 0
            for i in range(0, num_fragments - 1):
                fragment = struct.pack(">B", i) + struct.pack(">B", num_fragments) + data[current:current + 18]
                current += 18
                self._p.writeCharacteristic(self._p.getCharacteristics(uuid = self._send_uuid)[0].valHandle, fragment)
                print " ".join(x.encode('hex') for x in fragment)
            fragment = struct.pack(">B", num_fragments - 1) + struct.pack(">B", num_fragments) + data[current:]
            self._p.writeCharacteristic(self._p.getCharacteristics(uuid = self._send_uuid)[0].valHandle, fragment)

            print " ".join(x.encode('hex') for x in fragment)

    @asyncio.coroutine
    def btleNotificationListen(self):
        try:
            while True:
                if self._p.waitForNotifications(0.2):
                    pass
                time.sleep(0.01)
                yield None
        except BTLEException as e:
            print("Btle exception: " + str(e) + "; try to restart")
            self._p = None
            self.start()    
Beispiel #21
0
from bluepy.btle import Peripheral, UUID, DefaultDelegate
import struct


class MyDelegate(DefaultDelegate):
    def __init__(self):
        DefaultDelegate.__init__(self)

    def handleNotification(self, handle, data):
        if handle == readings.valHandle:
            print(*struct.unpack('ffff', data), flush=True)


p = Peripheral("3C:71:BF:CB:1E:42")
p.setDelegate(MyDelegate())

readings = next(c for c in p.getCharacteristics()
                if c.uuid == UUID("59462f12-9543-9999-12c8-58b459a2712d"))

# enable notification
p.writeCharacteristic(readings.valHandle + 1, (1).to_bytes(2,
                                                           byteorder='little'))

while p.waitForNotifications(1.0):
    pass

print("Error, a second passed with no notification received")
Beispiel #22
0
try:
    print('attempting to connect')
    bms = Peripheral(args.BLEaddress, addrType="public")
except BTLEException as ex:
    time.sleep(10)
    print('2nd try connect')
    bms = Peripheral(args.BLEaddress, addrType="public")
except BTLEException as ex:
    print('cannot connect')
    exit()
else:
    print('connected ', args.BLEaddress)

atexit.register(disconnect)
mqtt = paho.Client("control3")  #create and connect mqtt client
mqtt.connect(broker, port)
bms.setDelegate(MyDelegate())  # setup bt delegate for notifications

# write empty data to 0x15 for notification request   --  address x03 handle for info & x04 handle for cell voltage
# using waitForNotifications(5) as less than 5 seconds has caused some missed notifications
while True:
    result = bms.writeCharacteristic(0x15, b'\xdd\xa5\x03\x00\xff\xfd\x77',
                                     False)  # write x03 w/o response cell info
    bms.waitForNotifications(5)
    result = bms.writeCharacteristic(
        0x15, b'\xdd\xa5\x04\x00\xff\xfc\x77',
        False)  # write x04 w/o response cell voltages
    bms.waitForNotifications(5)
    time.sleep(z)
Beispiel #23
0
class OpenBCIGanglion(object):
    """
  Handle a connection to an OpenBCI board.

  Args:
    port: MAC address of the Ganglion Board. "None" to attempt auto-detect.
    aux: enable on not aux channels (i.e. switch to 18bit mode if set)
    impedance: measures impedance when start streaming
    timeout: in seconds, if set will try to disconnect / reconnect after a period without new data -- should be high if impedance check
    max_packets_to_skip: will try to disconnect / reconnect after too many packets are skipped
    baud, filter_data, daisy: Not used, for compatibility with v3
  """
    def __init__(self,
                 port=None,
                 baud=0,
                 filter_data=False,
                 scaled_output=True,
                 daisy=False,
                 log=True,
                 aux=False,
                 impedance=False,
                 timeout=2,
                 max_packets_to_skip=20):
        # unused, for compatibility with Cyton v3 API
        self.daisy = False
        # these one are used
        self.log = log  # print_incoming_text needs log
        self.aux = aux
        self.streaming = False
        self.timeout = timeout
        self.max_packets_to_skip = max_packets_to_skip
        self.scaling_output = scaled_output
        self.impedance = impedance

        # might be handy to know API
        self.board_type = "ganglion"

        print("Looking for Ganglion board")
        if port == None:
            port = self.find_port()
        self.port = port  # find_port might not return string

        self.connect()

        self.streaming = False
        # number of EEG channels and (optionally) accelerometer channel
        self.eeg_channels_per_sample = 4
        self.aux_channels_per_sample = 3
        self.imp_channels_per_sample = 5
        self.read_state = 0
        self.log_packet_count = 0
        self.packets_dropped = 0
        self.time_last_packet = 0

        # Disconnects from board when terminated
        atexit.register(self.disconnect)

    def getBoardType(self):
        """ Returns the version of the board """
        return self.board_type

    def setImpedance(self, flag):
        """ Enable/disable impedance measure """
        self.impedance = bool(flag)

    def connect(self):
        """ Connect to the board and configure it. Note: recreates various objects upon call. """
        print("Init BLE connection with MAC: " + self.port)
        print("NB: if it fails, try with root privileges.")
        self.gang = Peripheral(self.port, 'random')  # ADDR_TYPE_RANDOM

        print("Get mainservice...")
        self.service = self.gang.getServiceByUUID(BLE_SERVICE)
        print("Got:" + str(self.service))

        print("Get characteristics...")
        self.char_read = self.service.getCharacteristics(BLE_CHAR_RECEIVE)[0]
        print("receive, properties: " +
              str(self.char_read.propertiesToString()) + ", supports read: " +
              str(self.char_read.supportsRead()))

        self.char_write = self.service.getCharacteristics(BLE_CHAR_SEND)[0]
        print("write, properties: " +
              str(self.char_write.propertiesToString()) + ", supports read: " +
              str(self.char_write.supportsRead()))

        self.char_discon = self.service.getCharacteristics(
            BLE_CHAR_DISCONNECT)[0]
        print("disconnect, properties: " +
              str(self.char_discon.propertiesToString()) +
              ", supports read: " + str(self.char_discon.supportsRead()))

        # set delegate to handle incoming data
        self.delegate = GanglionDelegate(self.scaling_output)
        self.gang.setDelegate(self.delegate)

        # enable AUX channel
        if self.aux:
            print("Enabling AUX data...")
            try:
                self.ser_write(b'n')
            except Exception as e:
                print("Something went wrong while enabling aux channels: " +
                      str(e))

        print("Turn on notifications")
        # nead up-to-date bluepy, cf https://github.com/IanHarvey/bluepy/issues/53
        self.desc_notify = self.char_read.getDescriptors(forUUID=0x2902)[0]
        try:
            self.desc_notify.write(b"\x01")
        except Exception as e:
            print(
                "Something went wrong while trying to enable notification: " +
                str(e))

        print("Connection established")

    def init_streaming(self):
        """ Tell the board to record like crazy. """
        try:
            if self.impedance:
                print("Starting with impedance testing")
                self.ser_write(b'z')
            else:
                self.ser_write(b'b')
        except Exception as e:
            print(
                "Something went wrong while asking the board to start streaming: "
                + str(e))
        self.streaming = True
        self.packets_dropped = 0
        self.time_last_packet = timeit.default_timer()

    def find_port(self):
        """Detects Ganglion board MAC address -- if more than 1 around, will select first. Needs root privilege."""

        print(
            "Try to detect Ganglion MAC address. NB: Turn on bluetooth and run as root for this to work! Might not work with every BLE dongles."
        )
        scan_time = 5
        print("Scanning for 5 seconds nearby devices...")

        #   From bluepy example
        class ScanDelegate(DefaultDelegate):
            def __init__(self):
                DefaultDelegate.__init__(self)

            def handleDiscovery(self, dev, isNewDev, isNewData):
                if isNewDev:
                    print("Discovered device: " + dev.addr)
                elif isNewData:
                    print("Received new data from: " + dev.addr)

        scanner = Scanner().withDelegate(ScanDelegate())
        devices = scanner.scan(scan_time)

        nb_devices = len(devices)
        if nb_devices < 1:
            print("No BLE devices found. Check connectivity.")
            return ""
        else:
            print("Found " + str(nb_devices) + ", detecting Ganglion")
            list_mac = []
            list_id = []

            for dev in devices:
                # "Ganglion" should appear inside the "value" associated to "Complete Local Name", e.g. "Ganglion-b2a6"
                for (adtype, desc, value) in dev.getScanData():
                    if desc == "Complete Local Name" and value.startswith(
                            "Ganglion"):
                        list_mac.append(dev.addr)
                        list_id.append(value)
                        print("Got Ganglion: " + value + ", with MAC: " +
                              dev.addr)
                        break
        nb_ganglions = len(list_mac)

        if nb_ganglions < 1:
            print("No Ganglion found ;(")
            raise OSError('Cannot find OpenBCI Ganglion MAC address')

        if nb_ganglions > 1:
            print("Found " + str(nb_ganglions) + ", selecting first")

        print("Selecting MAC address " + list_mac[0] + " for " + list_id[0])
        return list_mac[0]

    def ser_write(self, b):
        """Access serial port object for write"""
        self.char_write.write(b)

    def ser_read(self):
        """Access serial port object for read"""
        return self.char_read.read()

    def ser_inWaiting(self):
        """ Slightly different from Cyton API, return True if ASCII messages are incoming."""
        # FIXME: might have a slight problem with thread because of notifications...
        if self.delegate.receiving_ASCII:
            # in case the packet indicating the end of the message drops, we use a 1s timeout
            if timeit.default_timer() - self.delegate.time_last_ASCII > 2:
                self.delegate.receiving_ASCII = False
        return self.delegate.receiving_ASCII

    def getSampleRate(self):
        return SAMPLE_RATE

    def getNbEEGChannels(self):
        """Will not get new data on impedance check."""
        return self.eeg_channels_per_sample

    def getNbAUXChannels(self):
        """Might not be used depending on the mode."""
        return self.aux_channels_per_sample

    def getNbImpChannels(self):
        """Might not be used depending on the mode."""
        return self.imp_channels_per_sample

    def start_streaming(self, callback, lapse=-1):
        """
    Start handling streaming data from the board. Call a provided callback
    for every single sample that is processed

    Args:
      callback: A callback function -- or a list of functions -- that will receive a single argument of the
          OpenBCISample object captured.
    """
        if not self.streaming:
            self.init_streaming()

        start_time = timeit.default_timer()

        # Enclose callback funtion in a list if it comes alone
        if not isinstance(callback, list):
            callback = [callback]

        while self.streaming:
            # should the board get disconnected and we could not wait for notification anymore, a reco should be attempted through timeout mechanism
            try:
                # at most we will get one sample per packet
                self.waitForNotifications(1. / self.getSampleRate())
            except Exception as e:
                print("Something went wrong while waiting for a new sample: " +
                      str(e))
            # retrieve current samples on the stack
            samples = self.delegate.getSamples()
            self.packets_dropped = self.delegate.getMaxPacketsDropped()
            if samples:
                self.time_last_packet = timeit.default_timer()
                for call in callback:
                    for sample in samples:
                        call(sample)

            if (lapse > 0 and timeit.default_timer() - start_time > lapse):
                self.stop()
            if self.log:
                self.log_packet_count = self.log_packet_count + 1

            # Checking connection -- timeout and packets dropped
            self.check_connection()

    def waitForNotifications(self, delay):
        """ Allow some time for the board to receive new data. """
        self.gang.waitForNotifications(delay)

    def test_signal(self, signal):
        """ Enable / disable test signal """
        if signal == 0:
            self.warn("Disabling synthetic square wave")
            try:
                self.char_write.write(b']')
            except Exception as e:
                print("Something went wrong while setting signal: " + str(e))
        elif signal == 1:
            self.warn("Eisabling synthetic square wave")
            try:
                self.char_write.write(b'[')
            except Exception as e:
                print("Something went wrong while setting signal: " + str(e))
        else:
            self.warn("%s is not a known test signal. Valid signal is 0-1" %
                      (signal))

    def set_channel(self, channel, toggle_position):
        """ Enable / disable channels """
        try:
            #Commands to set toggle to on position
            if toggle_position == 1:
                if channel is 1:
                    self.ser.write(b'!')
                if channel is 2:
                    self.ser.write(b'@')
                if channel is 3:
                    self.ser.write(b'#')
                if channel is 4:
                    self.ser.write(b'$')
            #Commands to set toggle to off position
            elif toggle_position == 0:
                if channel is 1:
                    self.ser.write(b'1')
                if channel is 2:
                    self.ser.write(b'2')
                if channel is 3:
                    self.ser.write(b'3')
                if channel is 4:
                    self.ser.write(b'4')
        except Exception as e:
            print("Something went wrong while setting channels: " + str(e))

    """

  Clean Up (atexit)

  """

    def stop(self):
        print("Stopping streaming...")
        self.streaming = False
        # connection might be already down here
        try:
            if self.impedance:
                print("Stopping with impedance testing")
                self.ser_write(b'Z')
            else:
                self.ser_write(b's')
        except Exception as e:
            print(
                "Something went wrong while asking the board to stop streaming: "
                + str(e))
        if self.log:
            logging.warning('sent <s>: stopped streaming')

    def disconnect(self):
        if (self.streaming == True):
            self.stop()
        print("Closing BLE..")
        try:
            self.char_discon.write(b' ')
        except Exception as e:
            print(
                "Something went wrong while asking the board to disconnect: " +
                str(e))
        # should not try to read/write anything after that, will crash
        try:
            self.gang.disconnect()
        except Exception as e:
            print("Something went wrong while shutting down BLE link: " +
                  str(e))
        logging.warning('BLE closed')

    """

      SETTINGS AND HELPERS

  """

    def warn(self, text):
        if self.log:
            #log how many packets where sent succesfully in between warnings
            if self.log_packet_count:
                logging.info('Data packets received:' +
                             str(self.log_packet_count))
                self.log_packet_count = 0
            logging.warning(text)
        print("Warning: %s" % text)

    def check_connection(self):
        """ Check connection quality in term of lag and number of packets drop. Reinit connection if necessary. FIXME: parameters given to the board will be lost."""
        # stop checking when we're no longer streaming
        if not self.streaming:
            return
        #check number of dropped packets and duration without new packets, deco/reco if too large
        if self.packets_dropped > self.max_packets_to_skip:
            self.warn("Too many packets dropped, attempt to reconnect")
            self.reconnect()
        elif self.timeout > 0 and timeit.default_timer(
        ) - self.time_last_packet > self.timeout:
            self.warn("Too long since got new data, attempt to reconnect")
            #if error, attempt to reconect
            self.reconnect()

    def reconnect(self):
        """ In case of poor connection, will shut down and relaunch everything. FIXME: parameters given to the board will be lost."""
        self.warn('Reconnecting')
        self.stop()
        self.disconnect()
        self.connect()
        self.init_streaming()
Beispiel #24
0
        print("%s=%s" % (desc, value))
number = input('Enter your device number: ')
print('Device', number)
print(devices[number].addr)

print('Connecting...')
dev = Peripheral(devices[number].addr, 'random')
#print('Services...')
#for svc in dev.services:
#print(str(svc))

try:
    #testService=dev.getServiceByUUID(UUID(0xa004))
    #for ch in testService.getCharacteristics():
    #print(str(ch))
    ch = dev.getCharacteristics(uuid=UUID(0xa005))[0]
    if (ch.supportsRead()):
        print(ch.read())

    for i in range(10):
        enable_notify(dev, 0xa001)
        if dev.waitForNotifications(5.0):
            # handleNotification() was called
            print("Button pressed")
            continue

        print "Waiting..."
        # Perhaps do something else here

finally:
    dev.disconnect()
Beispiel #25
0
class MicroBotPush:
    class UUID():
        SVC1831 = '00001831-0000-1000-8000-00805f9b34fb'
        CHR2A90 = '00002a90-0000-1000-8000-00805f9b34fb'
        CHR2A98 = '00002a98-0000-1000-8000-00805f9b34fb'
        CHR2A89 = '00002a89-0000-1000-8000-00805f9b34fb' # 1.1.22.2
        SVC1821 = '00001821-0000-1000-8000-00805f9b34fb'
        CHR2A11 = '00002a11-0000-1000-8000-00805f9b34fb'
        CHR2A12 = '00002a12-0000-1000-8000-00805f9b34fb'
        CHR2A35 = '00002a35-0000-1000-8000-00805f9b34fb'

    class MbpDelegate(DefaultDelegate):
        def __init__(self, params):
            DefaultDelegate.__init__(self)
            self.token = None
            self.bdaddr = None

        def handleNotification(self, cHandle, data):
            if cHandle == 0x31:
                tmp = binascii.b2a_hex(data)
                bdaddr = tmp[12:14]+ \
                         tmp[10:12]+ \
                         tmp[8:10]+ \
                         tmp[6:8]+ \
                         tmp[4:6]+ \
                         tmp[2:4]
                self.bdaddr = bdaddr.decode()
                print("notify: ack with bdaddr")
            elif cHandle == 0x2e:
                token = binascii.b2a_hex(data)[2:2+32]
                self.token = token.decode()
                print("notify: ack with token")
                # vulnerable protocol!
            elif cHandle == 0x17: # 1.1.22.2
                tmp = binascii.b2a_hex(data)[4:4+36]
                if b'0f0101' == tmp[:6] or b'0f0102' == tmp[:6]:
                    bdaddr = tmp[6:6+12]
                    self.bdaddr = bdaddr.decode()
                elif b'1fff' == tmp[0:4] and b'0000000000000000000000' != tmp[6:6+22] and b'00000000' == tmp[28:36]:
                    token = tmp[4:4+32]
                    self.token = token.decode()
                    print("notify: ack with token")
            else:
                print("notify: unknown")

        def getToken(self):
            return self.token

        def getBdaddr(self):
            return self.bdaddr
    # end of class

    def __init__(self, bdaddr, config, newproto, is_server):
        self.bdaddr = bdaddr
        self.retry = 5
        self.token = None
        self.p = None
        self.handler = None
        self.config = expanduser(config)
        self.__loadToken()
        self.newproto = newproto
        self.depth = 50
        self.duration = 0
        self.mode = 0
        self.is_server = is_server
        self.socket = None
        self.socket_path = "/var/tmp/microbot-"+re.sub('[^a-f0-9]', '', bdaddr.lower())

    def connect(self, init=False):
        if self.is_server == False:
            self.socket = self.__connectToServer()
            if self.socket:
                return

        retry = self.retry
        while True:
            try:
                print("connecting...\r", end='')
                self.p = Peripheral(self.bdaddr, "random")
                self.handler = MicroBotPush.MbpDelegate(0) 
                self.p.setDelegate(self.handler)
                print("connected    ")
                self.__setToken(init)
                break
            except BTLEException:
                if retry == 0:
                    print("failed to connect")
                    break
                retry = retry - 1 
                sleep(1)

    def __connectToServer(self):
        self.socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
        try:
            self.socket.connect(self.socket_path)
            print("connected to server")
            return self.socket
        except ConnectionRefusedError:
            return None
        except FileNotFoundError:
            return None

    def runServer(self):
        if self.is_server == False:
            return

        print("server mode")
        s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
        try:
            os.remove(self.socket_path)
        except (FileNotFoundError, PermissionError):
            pass

        s.bind(self.socket_path)
        s.listen(1)
        os.chmod(self.socket_path, 0o777)
        while True:
            self.connect()

            while True:
                try:
                    connection, address = s.accept()
                    msg = connection.recv(1024)
                    param = json.loads(msg.decode('UTF-8'))
                    print(param)
                    self.depth = param['depth']
                    self.duration = param['duration']
                    self.mode = param['mode']
                    res = self.push(param['setparams'])
                    msg = json.dumps({"result": res}).encode('UTF-8')
                    connection.send(msg)
                    if res == False:
                        break
                except BrokenPipeError:
                    print('broken pipe error')
                except KeyboardInterrupt:
                    os.remove(self.socket_path)
                    self.disconnect()
                    sys.exit('exit')

            self.disconnect()

    def disconnect(self):
        if self.is_server == False:
            if self.socket:
                self.socket.close()
                return

        if self.p == None:
            return
        try:
            self.p.disconnect()
            self.p = None
            print("disconnected")
        except BTLEException:
            print("failed to disconnect")

    def __loadToken(self):
        config = configparser.ConfigParser()
        config.read(self.config)
        bdaddr = self.bdaddr.lower().replace(':', '')
        if config.has_option('tokens', bdaddr):
            self.token = config.get('tokens', bdaddr)

    def __storeToken(self):
        config = configparser.ConfigParser()
        config.read(self.config)
        if not config.has_section('tokens'):
            config.add_section('tokens')
        bdaddr = self.bdaddr.lower().replace(':', '')
        config.set('tokens', bdaddr, self.token)
        os.umask(0)
        with open(os.open(self.config, os.O_CREAT | os.O_WRONLY, 0o600), 'w') as file:
            config.write(file)

    def hasToken(self):
        if self.token == None:
            return False
        else:
            return True

    def __initToken(self):
        try:
            if self.newproto:
                s = self.p.getServiceByUUID(MicroBotPush.UUID.SVC1831)
                c = s.getCharacteristics(MicroBotPush.UUID.CHR2A89)[0]
                self.p.writeCharacteristic(c.getHandle()+1, b'\x01\x00', True)
                id = self.__randomid(16)
                c.write(binascii.a2b_hex(id+"00010040e20100fa01000700000000000000"), True)
                c.write(binascii.a2b_hex(id+"0fffffffffffffffffffffffffff"+self.__randomid(32)), True)
            else:
                s = self.p.getServiceByUUID(MicroBotPush.UUID.SVC1831)
                c = s.getCharacteristics(MicroBotPush.UUID.CHR2A98)[0]
                self.p.writeCharacteristic(c.getHandle()+1, b'\x01\x00')
                c.write(binascii.a2b_hex("00000167"+"00"*16))
        except BTLEException:
            print("failed to init token")

        while True:
            bdaddr = self.handler.getBdaddr()
            if bdaddr != None:
                break
            if self.p.waitForNotifications(1.0):
                continue
            print("waiting...\r", end='')

    def __setToken(self, init):
        if init:
            self.__initToken()
        else:
            if self.hasToken():
                try:
                    if self.newproto:
                        s = self.p.getServiceByUUID(MicroBotPush.UUID.SVC1831)
                        c = s.getCharacteristics(MicroBotPush.UUID.CHR2A89)[0]
                        self.p.writeCharacteristic(c.getHandle()+1, b'\x01\x00', True)
                        s = self.p.getServiceByUUID(MicroBotPush.UUID.SVC1831)
                        c = s.getCharacteristics(MicroBotPush.UUID.CHR2A89)[0]
                        id = self.__randomid(16)
                        c.write(binascii.a2b_hex(id+"00010000000000fa0000070000000000decd"), True)
                        c.write(binascii.a2b_hex(id+"0fff"+self.token), True)
                    else:
                        s = self.p.getServiceByUUID(MicroBotPush.UUID.SVC1831)
                        c = s.getCharacteristics(MicroBotPush.UUID.CHR2A98)[0]
                        c.write(binascii.a2b_hex("00000167"+self.token))
                except BTLEException:
                    print("failed to set token")

    def getToken(self):
        if self.p == None:
            return
        try:
            if self.newproto:
                s = self.p.getServiceByUUID(MicroBotPush.UUID.SVC1831)
                c = s.getCharacteristics(MicroBotPush.UUID.CHR2A89)[0]
                self.p.writeCharacteristic(c.getHandle()+1, b'\x01\x00', True)
                rstr = " "+self.__randomstr(32)+"\x00"*7
                id = self.__randomid(16)
                c.write(binascii.a2b_hex(id+"00010040e20101fa01000000000000000000"), True)
                c.write(binascii.a2b_hex(id+"0fffffffffffffffffff0000000000000000"), True)
            else:
                s = self.p.getServiceByUUID(MicroBotPush.UUID.SVC1831)
                c = s.getCharacteristics(MicroBotPush.UUID.CHR2A90)[0]
                self.p.writeCharacteristic(c.getHandle()+1, b'\x01\x00')
                rstr = " "+self.__randomstr(32)+"\x00"*7
                c.write(rstr.encode())
        except BTLEException:
            print("failed to request token")

        print('touch the button to get a token')

        while True:
            token = self.handler.getToken()
            if token != None:
                break
            if self.p.waitForNotifications(1.0):
                continue
            print("waiting...\r", end='')

        #print(token)
        self.token = token
        self.__storeToken()

    def setDepth(self, depth):
        if self.newproto:
            self.depth = depth
        else:
            if self.p == None:
                return
            else:
                s = self.p.getServiceByUUID(MicroBotPush.UUID.SVC1821)
                c = s.getCharacteristics(MicroBotPush.UUID.CHR2A35)[0]
                c.write(binascii.a2b_hex('{:02x}'.format(depth)))

    def setDuration(self, duration):
        if self.newproto:
            self.duration = duration

    def setMode(self, mode):
        if self.newproto:
            if 'normal' == mode:
                self.mode = 0
            elif 'invert' == mode:
                self.mode = 1
            elif 'toggle' == mode:
                self.mode = 2

    def __push_server(self, setparams):
        msg = json.dumps({"depth": self.depth, "duration": self.duration, "mode": self.mode, "newproto": self.newproto, "setparams": setparams}).encode('UTF-8')
        self.socket.send(msg)
        msg = self.socket.recv(1024)
        param = json.loads(msg.decode('UTF-8'))
        return param['result']

    def push(self, setparams):
        if self.is_server == False:
            if self.socket:
                res = self.__push_server(setparams)
                return res

        if self.p == None:
            return False
        retry = self.retry
        while True:
            try:
                if self.newproto:
                    s = self.p.getServiceByUUID(MicroBotPush.UUID.SVC1831)
                    c = s.getCharacteristics(MicroBotPush.UUID.CHR2A89)[0]
                    id = self.__randomid(16)
                    if setparams:
                        c.write(binascii.a2b_hex(id+"000100000008030001000a0000000000decd"), True)
                        c.write(binascii.a2b_hex(id+"0fff"+'{:02x}'.format(self.mode)+"000000"+"000000000000000000000000"), True)
                        c.write(binascii.a2b_hex(id+"000100000008040001000a0000000000decd"), True)
                        c.write(binascii.a2b_hex(id+"0fff"+'{:02x}'.format(self.depth)+"000000"+"000000000000000000000000"), True)
                        c.write(binascii.a2b_hex(id+"000100000008050001000a0000000000decd"), True)
                        c.write(binascii.a2b_hex(id+"0fff"+self.duration.to_bytes(4,"little").hex()+"000000000000000000000000"), True)
                        return True
                    else:
                        c.write(binascii.a2b_hex(id+"000100000008020000000a0000000000decd"), True)
                        c.write(binascii.a2b_hex(id+"0fffffffffff000000000000000000000000"), True)
                        return True
                else:
                    s = self.p.getServiceByUUID(MicroBotPush.UUID.SVC1821)
                    c = s.getCharacteristics(MicroBotPush.UUID.CHR2A11)[0]
                    c.write(b'\x01')
                    return True
            except BTLEDisconnectError:
                if retry == 0:
                    print("failed to push by disconnect error")
                    return False
                retry = retry - 1 
                sleep(1)
            except BTLEException:
                print("failed to push by exception")
                return False

    def __randomstr(self, n):
       randstr = [random.choice(string.printable) for i in range(n)]
       return ''.join(randstr)

    def __randomid(self, bits):
       fmtstr = '{:'+'{:02d}'.format(int(bits/4))+'x}'
       return fmtstr.format(random.randrange(2**bits))
Beispiel #26
0
class OpenBCIGanglion(object):
    """ OpenBCIGanglion handles the connection to an OpenBCI Ganglion board.

    The OpenBCIGanglion class interfaces with the Cyton Dongle and the Cyton board to parse the data received and output it to Python as a OpenBCISample object.

    Args:
        mac: A string representing the Ganglion board mac address. It should be a string comprising six hex bytes separated by colons, e.g. "11:22:33:ab:cd:ed". If no mac address specified, a connection will be stablished with the first Ganglion found (Will need root privilages).

        max_packets_skipped: An integer specifying how many packets can be dropped before attempting to reconnect.
    """
    def __init__(self, mac=None, sample_rate=None, max_packets_skipped=15):
        if not mac:
            self.mac_address = self.find_mac()
        else:
            self.mac_address = mac
        self.max_packets_skipped = max_packets_skipped
        self.streaming = False
        self.board_type = 'Ganglion'
        self.sample_rate = sample_rate

        atexit.register(self.disconnect)

        self.connect()

    def write_command(self, command):
        """Sends string command to the Ganglion board."""
        self.char_write.write(str.encode(command))

    def connect(self):
        """Establishes connection with the specified Ganglion board."""
        self.ganglion = Peripheral(self.mac_address, 'random')

        self.service = self.ganglion.getServiceByUUID(BLE_SERVICE)

        self.char_read = self.service.getCharacteristics(BLE_CHAR_RECEIVE)[0]

        self.char_write = self.service.getCharacteristics(BLE_CHAR_SEND)[0]

        self.char_discon = self.service.getCharacteristics(
            BLE_CHAR_DISCONNECT)[0]

        self.ble_delegate = GanglionDelegate(self.max_packets_skipped)
        self.ganglion.setDelegate(self.ble_delegate)

        self.desc_notify = self.char_read.getDescriptors(forUUID=0x2902)[0]

        try:
            self.desc_notify.write(b"\x01")
        except Exception as e:
            print(
                "Something went wrong while trying to enable notification: " +
                str(e))
            sys.exit(2)

        print("Connection established")

    def disconnect(self):
        """Disconnets from the Ganglion board."""
        if self.streaming:
            self.stop_stream()

        self.char_discon.write(b' ')
        self.ganglion.disconnect()

    def find_mac(self):
        """Finds and returns the mac address of the first Ganglion board found"""
        scanner = Scanner()
        devices = scanner.scan(5)

        if len(devices) < 1:
            raise OSError(
                'No nearby Devices found. Make sure your Bluetooth Connection is on.'
            )

        else:
            gang_macs = []
            for dev in devices:
                for adtype, desc, value in dev.getScanData():
                    if desc == 'Complete Local Name' and value.startswith(
                            'Ganglion'):
                        gang_macs.append(dev.addr)
                        print(value)

        if len(gang_macs) < 1:
            raise OSError('Cannot find OpenBCI Ganglion Mac address.')
        else:
            print("Connecting to Ganglion with mac address: " + gang_macs[0])
            return gang_macs[0]

    def stop_stream(self):
        """Stops Ganglion Stream."""
        self.streaming = False
        self.write_command('s')

    def start_stream(self, callback):
        """Start handling streaming data from the Ganglion board. Call a provided callback for every single sample that is processed."""
        if not self.streaming:
            self.streaming = True
            self.dropped_packets = 0
            self.write_command('b')

        if not isinstance(callback, list):
            callback = [callback]

        while self.streaming:
            try:
                self.ganglion.waitForNotifications(1. / SAMPLE_RATE)
            except Exception as e:
                print(e)
                print('Something went wrong')
                sys.exit(1)

            samples = self.ble_delegate.getSamples()
            if samples:
                #for sample in samples:
                for call in callback:
                    call(samples)  # call with block samples in 1 go

    def getSampleRate(self):
        return self.sample_rate
Beispiel #27
0
class MicroBotPush:
    class UUID():
        SVC1831 = '00001831-0000-1000-8000-00805f9b34fb'
        CHR2A90 = '00002a90-0000-1000-8000-00805f9b34fb'
        CHR2A98 = '00002a98-0000-1000-8000-00805f9b34fb'
        SVC1821 = '00001821-0000-1000-8000-00805f9b34fb'
        CHR2A11 = '00002a11-0000-1000-8000-00805f9b34fb'
        CHR2A12 = '00002a12-0000-1000-8000-00805f9b34fb'
        CHR2A35 = '00002a35-0000-1000-8000-00805f9b34fb'

    class MbpDelegate(DefaultDelegate):
        def __init__(self, params):
            DefaultDelegate.__init__(self)
            self.token = None
            self.bdaddr = None

        def handleNotification(self, cHandle, data):
            if cHandle == 0x31:
                tmp = binascii.b2a_hex(data)
                bdaddr = tmp[12:14]+ \
                         tmp[10:12]+ \
                         tmp[8:10]+ \
                         tmp[6:8]+ \
                         tmp[4:6]+ \
                         tmp[2:4]
                self.bdaddr = bdaddr.decode()
                print("notify: ack with bdaddr")
            elif cHandle == 0x2e:
                token = binascii.b2a_hex(data)[2:2+32]
                self.token = token.decode()
                print("notify: ack with token")
                # vulnerable protocol!
            else:
                print("notify: unknown")

        def getToken(self):
            return self.token

        def getBdaddr(self):
            return self.bdaddr
    # end of class

    def __init__(self, bdaddr, config):
        self.bdaddr = bdaddr
        self.retry = 5
        self.token = None
        self.p = None
        self.handler = None
        self.config = expanduser(config)
        self.__loadToken()

    def connect(self, init=False):
        retry = self.retry
        while True:
            try:
                print("connecting...\r", end='')
                self.p = Peripheral(self.bdaddr, "random")
                self.handler = MicroBotPush.MbpDelegate(0) 
                self.p.setDelegate(self.handler)
                print("connected    ")
                self.__setToken(init)
                break
            except BTLEException:
                if retry == 0:
                    print("failed")
                    break
                retry = retry - 1 
                sleep(1)

    def disconnect(self):
        if self.p == None:
            return
        try:
            self.p.disconnect()
            self.p = None
            print("disconnected")
        except BTLEException:
            print("failed")

    def __loadToken(self):
        config = configparser.ConfigParser()
        config.read(self.config)
        bdaddr = self.bdaddr.lower().replace(':', '')
        if config.has_option('tokens', bdaddr):
            self.token = config.get('tokens', bdaddr)

    def __storeToken(self):
        config = configparser.ConfigParser()
        config.read(self.config)
        if not config.has_section('tokens'):
            config.add_section('tokens')
        bdaddr = self.bdaddr.lower().replace(':', '')
        config.set('tokens', bdaddr, self.token)
        os.umask(0)
        with open(os.open(self.config, os.O_CREAT | os.O_WRONLY, 0o600), 'w') as file:
            config.write(file)

    def hasToken(self):
        if self.token == None:
            return False
        else:
            return True

    def __initToken(self):
        s = self.p.getServiceByUUID(MicroBotPush.UUID.SVC1831)
        c = s.getCharacteristics(MicroBotPush.UUID.CHR2A98)[0]
        self.p.writeCharacteristic(c.getHandle()+1, b'\x01\x00')
        c.write(binascii.a2b_hex("00000167"+"00"*16))

        while True:
            bdaddr = self.handler.getBdaddr()
            if bdaddr != None:
                break
            if self.p.waitForNotifications(1.0):
                continue
            print("waiting...\r", end='')

    def __setToken(self, init):
        if init:
            self.__initToken()
        else:
            if self.hasToken():
                s = self.p.getServiceByUUID(MicroBotPush.UUID.SVC1831)
                c = s.getCharacteristics(MicroBotPush.UUID.CHR2A98)[0]
                c.write(binascii.a2b_hex("00000167"+self.token))

    def getToken(self):
        if self.p == None:
            return
        s = self.p.getServiceByUUID(MicroBotPush.UUID.SVC1831)
        c = s.getCharacteristics(MicroBotPush.UUID.CHR2A90)[0]
        self.p.writeCharacteristic(c.getHandle()+1, b'\x01\x00')
        rstr = " "+self.__randomstr(32)+"\x00"*7
        c.write(rstr.encode())

        print('touch the button to get a token')

        while True:
            token = self.handler.getToken()
            if token != None:
                break
            if self.p.waitForNotifications(1.0):
                continue
            print("waiting...\r", end='')

        #print(token)
        self.token = token
        self.__storeToken()

    def setDepth(self, depth):
        if self.p == None:
            return
        s = self.p.getServiceByUUID(MicroBotPush.UUID.SVC1821)
        c = s.getCharacteristics(MicroBotPush.UUID.CHR2A35)[0]
        c.write(binascii.a2b_hex('{:02x}'.format(depth)))

    def push(self, period=1):
        if self.p == None:
            return
        retry = self.retry
        while True:
            try:
                s = self.p.getServiceByUUID(MicroBotPush.UUID.SVC1821)
                c = s.getCharacteristics(MicroBotPush.UUID.CHR2A11)[0]
                c.write(b'\x01')
                #sleep(period)
                #c = s.getCharacteristics(MicroBotPush.UUID.CHR2A12)[0]
                #c.write(b'\x01')
                break
            except BTLEDisconnectError:
                if retry == 0:
                    print("failed")
                    break
                retry = retry - 1 
                sleep(1)

    def __randomstr(self, n):
       randstr = [random.choice(string.printable) for i in range(n)]
       return ''.join(randstr)
Beispiel #28
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 _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)
def main():
    # uuid definition
    targetDevice = ""
    targetUUID = UUID("08590f7e-db05-467e-8757-72f6f66666d4")
    # targetUUID   = UUID(0x2a2b)
    serviceUUID = UUID("e20a39f4-73f5-4bc4-a12f-17d1ad666661")

    # scanning for Bluetooth LE device
    # P.S. root permission is needed
    print "scanning started..."
    scanner = Scanner().withDelegate(ScanDelegate())
    devices = scanner.scan(5)

    print "\n\nscanning completed...\n found %d device(s)\n" % len(devices)

    for dev in devices:
        print "Device %s (%s), RSSI=%d dB" % (dev.addr, dev.addrType, dev.rssi)
        for (adtype, desc, value) in dev.getScanData():
            print "  %s = %s" % (desc, value)

        try:
            p = Peripheral(dev.addr, "random")
            ch = p.getCharacteristics(uuid=targetUUID)
            if len(ch) > 0:
                print "the desired target found. the address is", dev.addr
                targetDevice = dev.addr
        except:
            # print "Unexpected error:", sys.exc_info()[0]
            print "Unable to connect"
            print " "
        finally:
            p.disconnect()

    # scanning completed, now continue to connect to device
    if targetDevice == "":
        # the target is not found. end.
        print "no target was found."
    else:
        # the target found, continue to subscribe.
        print "\n\nthe target device is ", targetDevice
        print "now try to subscribe..."

        try:
            # try to get the handle first
            p = Peripheral(targetDevice, "random")
            p.setDelegate(NotificationDelegate())
            # svc = p.getServiceByUUID(serviceUUID)
            ch = p.getCharacteristics(
                uuid=targetUUID)[0]  # svc.getCharacteristics(targetUUID)[0]
            handle = ch.getHandle()
            print handle
            ch.write(struct.pack('<bb', 0x01, 0x00))
            # ch.write(bytes('aa', 'utf-8'))
            # p.writeCharacteristic(handle, struct.pack('<bb', 0x01, 0x00), True)

            print

            # Main loop
            while True:
                if p.waitForNotifications(5):
                    # handleNotification() was called
                    continue

                print "Waiting..."
                # Perhaps do something else here
        # except:
        #     print "Unexpected error:", sys.exc_info()[0]
        finally:
            p.disconnect()
    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: {e} [Local bluetooth problem?]")
                        rep_err = True
                    bt_error = True
                if bt_error is False:
                    self.log.info(f"Bluetooth reconnected to {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(f"bluepy_ble: failed to write {msg}: {e}")
                    bt_error = True
                    self.agent_state(
                        que, 'offline', f'Connected to Bluetooth peripheral lost: {e}')
                wrque.task_done()

            try:
                rx.read()
                mil.waitForNotifications(0.05)
                # time.sleep(0.1)
            except Exception as e:
                self.log.warning(f"Bluetooth error {e}")
                bt_error = True
                self.agent_state(
                    que, 'offline', f'Connected to Bluetooth peripheral lost: {e}')
                continue

        log.debug('wt-end')
Beispiel #31
0
class BLE_interface():
    def __init__(
        self,
        addr_str,
        addr_type,
        adapter,
        write_uuid,
        read_uuid,
    ):
        self.dev = Peripheral(deviceAddr=addr_str,
                              addrType=addr_type,
                              iface=adapter)
        logging.info(f'Connected device {self.dev.addr}')

        if write_uuid:
            self.write_uuid = [UUID(write_uuid)]
        else:
            self.write_uuid = [UUID(x) for x in ble_chars]
            logging.debug(f'No write uuid specified, trying {ble_chars}')
        for c in self.dev.getCharacteristics():
            if c.uuid in self.write_uuid:
                self._write_charac = c
                self.write_uuid = self._write_charac.uuid
                logging.debug(f'Found write characteristic {self.write_uuid}')
                break
        assert hasattr(self, '_write_charac'), \
            "No characteristic with specified UUID found!"
        assert (self._write_charac.properties & Characteristic.props["WRITE_NO_RESP"]), \
            "Specified characteristic is not writable!"

        # Only subscribe to read_uuid notifications if it was specified
        if read_uuid:
            self.read_uuid = [UUID(read_uuid)]
            for c in self.dev.getCharacteristics():
                if c.uuid in self.read_uuid:
                    self._read_charac = c
                    self.read_uuid = self._read_charac.uuid
                    logging.debug(
                        f'Found read characteristic {self.read_uuid}')
                    break
            assert hasattr(self, '_read_charac'), \
                "No read characteristic with specified UUID found!"
            assert (self._read_charac.properties & Characteristic.props["NOTIFY"]), \
                "Specified read characteristic is not notifiable!"
            # Attempt to subscribe to notification now (write CCCD)
            # First get the Client Characteristic Configuration Descriptor
            self._read_charac_cccd = self._read_charac.getDescriptors(0x2902)
            assert (self._read_charac_cccd is not None), \
                "Could not find CCCD for given read UUID!"
            self._read_charac_cccd = self._read_charac_cccd[0]
            # Now write that we want notifications from this UUID
            self._read_charac_cccd.write(bytes([0x01, 0x00]))

    def send(self, data: bytes):
        logging.debug(f'Sending {data}')
        self._write_charac.write(data, withResponse=False)

    def set_receiver(self, callback):
        logging.info('Receiver set up')
        self.dev.setDelegate(ReceiverDelegate(callback))

    def receive_loop(self):
        assert isinstance(
            self.dev.delegate,
            ReceiverDelegate), 'Callback must be set before receive loop!'
        self.dev.waitForNotifications(3.0)

    def shutdown(self):
        self.dev.disconnect()
        logging.info('BT disconnected')
Beispiel #32
0
BLACK = (0,0,0)
RED = (255,60,120)
GREEN = (58,255,118)
BLUE = (64,128,255)
ORANGE = (252, 97, 38)

shouldQuit = False
while not shouldQuit:
    for event in pygame.event.get():
        if event.type == QUIT:
            shouldQuit = True
        elif event.type == KEYDOWN:
            if event.key == K_ESCAPE:
                shouldQuit = True

    leftShoePeripheral.waitForNotifications(0.001)
    rightShoePeripheral.waitForNotifications(0.001)

    hSpacing = 10
    vSpacing = 24
    cursorX = hSpacing
    cursorY = vSpacing

    #left shoe data

    #labels 
    DISPLAYSURF.fill(BLACK)
    labelSurface = metricsFont.render("Left Shoe: ", 1, (255,255,255))
    DISPLAYSURF.blit(labelSurface, (cursorX, vSpacing))

    
Beispiel #33
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.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 #34
0
#!/usr/bin/env python
from bluepy.btle import Peripheral, DefaultDelegate, UUID, ADDR_TYPE_RANDOM
import hexdump


class MyDelegate(DefaultDelegate):
    def __init__(self):
        DefaultDelegate.__init__(self)

    def handleNotification(self, cHandle, data):
        print "Button Pressed"


p = Peripheral('FF:FF:80:02:5B:41')
p.setDelegate(MyDelegate())
print "Connected to device"

svc = p.getServiceByUUID(UUID("0000ffe0-0000-1000-8000-00805f9b34fb"))
ch = svc.getCharacteristics(UUID("0000ffe1-0000-1000-8000-00805f9b34fb"))[0]
noch = ch.getHandle() + 1

p.writeCharacteristic(noch, b'\x01\x00')
while True:
    p.waitForNotifications(1.0)
alertConnection2 = wrist_hexi.getCharacteristics(uuid="2031")[0]
alertConnection3 = shoulder_hexi.getCharacteristics(uuid="2031")[0]

try_until_success(alertConnection.write,
                  msg='Error sending elbow connection command',
                  args=[connectCommand, True])
try_until_success(alertConnection2.write,
                  msg='Error sending wrist connection command',
                  args=[connectCommand, True])
try_until_success(alertConnection3.write,
                  msg='Error sending shoulder connection command',
                  args=[connectCommand, True])

# Infinite loop to receive notifications
while True:
    wrist_hexi.waitForNotifications(1.0)
    elbow_hexi.waitForNotifications(1.0)
    shoulder_hexi.waitForNotifications(1.0)

    if foundInitSequence:
        print("Found initial sequence")
        # Repeat this for all three Hexiwears
        # alertConnection.write(collectCommand, True)
        try_until_success(alertConnection.write,
                          msg='Error sending elbow collection command',
                          args=[collectCommand, True])
        try_until_success(alertConnection2.write,
                          msg='Error sending wrist collection command',
                          args=[collectCommand, True])
        try_until_success(alertConnection3.write,
                          msg='Error sending shoulder collection command',
class LightBlueBean(DefaultDelegate):
    # https://github.com/PunchThrough/bean-documentation/blob/master/app_message_types.md
    MSG_ID_SERIAL_DATA        = 0x0000
    MSG_ID_BT_SET_ADV         = 0x0500
    MSG_ID_BT_SET_CONN        = 0x0502
    MSG_ID_BT_SET_LOCAL_NAME  = 0x0504
    MSG_ID_BT_SET_PIN         = 0x0506
    MSG_ID_BT_SET_TX_PWR      = 0x0508
    MSG_ID_BT_GET_CONFIG      = 0x0510
    MSG_ID_BT_ADV_ONOFF       = 0x0512
    MSG_ID_BT_SET_SCRATCH     = 0x0514
    MSG_ID_BT_GET_SCRATCH     = 0x0515
    MSG_ID_BT_RESTART         = 0x0520
    MSG_ID_GATING             = 0x0550
    MSG_ID_BL_CMD             = 0x1000
    MSG_ID_BL_FW_BLOCK        = 0x1001
    MSG_ID_BL_STATUS          = 0x1002
    MSG_ID_CC_LED_WRITE       = 0x2000
    MSG_ID_CC_LED_WRITE_ALL   = 0x2001
    MSG_ID_CC_LED_READ_ALL    = 0x2002
    MSG_ID_CC_LED_DATA        = 0x2082
    MSG_ID_CC_ACCEL_READ      = 0x2010
    MSG_ID_CC_ACCEL_DATA      = 0x2090
    MSG_ID_CC_TEMP_READ       = 0x2011
    MSG_ID_CC_TEMP_DATA       = 0x2091
    MSG_ID_CC_BATT_READ       = 0x2015
    MSG_ID_CC_BATT_DATA       = 0x2095
    MSG_ID_AR_SET_POWER       = 0x3000
    MSG_ID_AR_GET_CONFIG      = 0x3006
    MSG_ID_DB_LOOPBACK        = 0xFE00
    MSG_ID_DB_COUNTER         = 0xFE01
    
    def __init__(self, mac):
        self.conn = Peripheral(mac)
        self.conn.setDelegate(self)
        self.count = 0
        self.buffin = [None]*10
        self.got1 = False
        print('connected')
        
        self.service = self.conn.getServiceByUUID(_LBN_UUID(0x10))
        self.serial = self.service.getCharacteristics(_LBN_UUID(0x11)) [0]
        
        #print(self.serial.propertiesToString())

        # Turn on notificiations
        self.conn.writeCharacteristic(0x2f, '\x01\x00', False)
        
        i = 0
        while True:
            #print(self.serial.read())
            self.write("a" * 60)
            #self.write("a" * 5)
            #self.sendCmd(LightBlueBean.MSG_ID_CC_ACCEL_READ)
            
            self.conn.waitForNotifications(1)
            time.sleep(1)
        self.conn.disconnect()
        
    def write(self, data):
        self.sendCmd(LightBlueBean.MSG_ID_SERIAL_DATA, data)

    def sendCmd(self, cmd, data = ""):
        # https://github.com/PunchThrough/bean-documentation/blob/master/serial_message_protocol.md
        gst = struct.pack("!BxH", len(data)+2, cmd) + data
        crc = struct.pack("<H", crc16(gst, 0xFFFF))
        gst += crc
        
        gt_qty = len(gst)/19
        if len(gst) % 19 != 0:
            gt_qty += 1
        
        #amnt = len(gst) / gt_qty
        optimal_packet_size = 19
        
        for ch in xrange(0, gt_qty):
            data = gst[:optimal_packet_size]
            gst = gst[optimal_packet_size:]
            
            gt = 0
            if ch == 0:
                gt = 0x80
            gt |= self.count << 5
            gt |= gt_qty - ch - 1
            
            gt = struct.pack("B", gt) + data
        
            #print("<", hexdump(gt))
            self.serial.write(gt)
            #time.sleep(0.1)
        
        self.count = (self.count + 1) % 4

    def writeRaw(self, data):
        self.conn.writeCharacteristic(0x0b, data, False)

    def handleNotification(self, cHandle, data):
        #print(">", hexdump(data))
        gt = struct.unpack("B", data[0]) [0]
        #gt_cntr = gt & 0x60
        gt_left = gt & 0x1F
        
        if gt & 0x80:
            self.got1 = True
            self.buffin = self.buffin[:gt_left+1]
            
        self.buffin[gt_left] = data[1:]
        
        if self.got1 and not self.buffin.count(None):
            #print("Got ", len(self.buffin), "packets")
            self.buffin.reverse()
            self.buffin = ''.join(self.buffin)
            
            crc_ = crc16(self.buffin[:-2], 0xFFFF)
            dlen, cmd = struct.unpack("!BxH", self.buffin[:4])
            crc = struct.unpack("<H", self.buffin[-2:]) [0]
            if crc == crc_:
                print(self.buffin[4:-2])
            else:
                print("CRC check failure")
            
            self.buffin = [None]*10
            self.got1 = False
Beispiel #37
0
        print("ERROR", e)
        if getpass.getuser() != 'root':
            print('使用方法: sudo', argv[0])
        exit()
    # 受信データについてBLEデバイス毎の処理
    for dev in devices:
        for (adtype, desc, val) in dev.getScanData():
            if adtype == 8 and val[0:10] == 'LapisDev':
                print("\nDevice %s (%s), RSSI=%d dB, Connectable=%s" %
                      (dev.addr, dev.addrType, dev.rssi, dev.connectable))
                print("  %3d %s = %s" % (adtype, desc, val))
                address = dev.addr
if address[0] == 'x':
    print("no LapisDev found")
    exit()

p = Peripheral(address, addrType='random')  # Failed to connect to peripheral
p.setDelegate(MyDelegate(DefaultDelegate))

# Setup to turn notifications on
svc = p.getServiceByUUID('0179bbd0-5351-48b5-bf6d-2167639bc867')
ch = svc.getCharacteristics('0179bbd1-5351-48b5-bf6d-2167639bc867')[0]
ch.handle += 2
#ch.write(b'\x01')
print('uuid =', ch.uuid, 'handle =', hex(ch.handle))
p.writeCharacteristic(ch.handle, b'\x01')

# Main
print('Waiting...')
p.waitForNotifications(20)
class YeelightService:
    """
    YeelightService is the yeelight control util for python

    author zhaohui.sol
    """

    SERVICE = "0000FFF0-0000-1000-8000-00805F9B34FB"

    CHAR_CONTROL = "0000FFF1-0000-1000-8000-00805F9B34FB"

    CHAR_DELAY = "0000FFF2-0000-1000-8000-00805F9B34FB"

    CHAR_DELAY_QUERY = "0000FFF3-0000-1000-8000-00805F9B34FB"

    CHAR_DELAY_NOTIFY = "0000FFF4-0000-1000-8000-00805F9B34FB"

    CHAR_QUERY = "0000FFF5-0000-1000-8000-00805F9B34FB"

    CHAR_NOTIFY = "0000FFF6-0000-1000-8000-00805F9B34FB"

    CHAR_COLOR_FLOW = "0000FFF7-0000-1000-8000-00805F9B34FB"

    CHAR_NAME = "0000FFF8-0000-1000-8000-00805F9B34FB"

    CHAR_NAME_NOTIFY = "0000FFF9-0000-1000-8000-00805F9B34FB"

    CHAR_COLOR_EFFECT = "0000FFFC-0000-1000-8000-00805F9B34FB"

    class NotifyDelegate(DefaultDelegate):

        def __init__(self):
            DefaultDelegate.__init__(self)
            self.queue = list()

        def register(self,callback):
            self.queue.append(callback)

        def deregister(self,callback):
            self.queue.remove(callback)

        def handleNotification(self,handle,data):
            logging.warning("notify data %s from %s." % (data,handle))
            res = dict()
            res['data'] = data
            res['handle'] = handle
            for c in self.queue:
                c(res)


    def __init__(self,address):
        """
        address is the yeelight blue ble hardware address
        """
        self.data = dict()
        self.address = address
        self.delegate = YeelightService.NotifyDelegate()
        self.peripher = Peripheral(deviceAddr = address)
        self.service = self.peripher.getServiceByUUID(YeelightService.SERVICE)
        self.peripher.withDelegate(self.delegate)

    def __character_by_uuid__(self,uuid):
        '''
        get character by a special uuid
        '''
        characters = self.service.getCharacteristics(forUUID=uuid)
        return characters[0] if characters else None

    def __write_character__(self,uuid,strdata):
        '''
        write data to a special uuid
        '''
        logging.info(u"write %s to %s." % (strdata,uuid))
        character = self.__character_by_uuid__(uuid)
        if character:
            character.write(strdata)
        else:
            pass

    def __read_character__(self,uuid):
        '''
        read data from a special uuid,may be it's wrong
        '''
        logging.info(u"read data from %s." % uuid)
        character = self.__character_by_uuid__(uuid)
        if character:
            return character.read()
        else:
            return None

    def __notify_character__(self,_to,_write):
        '''
        write data to the uuid and wait data notify
        '''
        res = dict()
        def callback(data):
            for k in data:
                res[k] = data[k]
        self.delegate.register(callback)
        self.__write_character__(_to,_write)
        if self.peripher.waitForNotifications(5):
            logging.info("notify incoming.")
            self.delegate.deregister(callback)
            return res
        else:
            logging.warning("notify timeout.")
            self.delegate.deregister(callback)
            return None

    def __format_request__(self,strdata,length = 18):
        '''
        format the data to a special length
        '''
        if strdata and length >= len(strdata) > 0:
            l = len(strdata)
            strdata += "".join(["," for i in range(length - l)])
            return strdata
        else:
            return "".join(["," for i in range(length)])

    def turn_on(self, brightness = 100):
        '''
        turn on the light with white and full brightness
        '''
        self.control(255,255,255,brightness)

    def turn_off(self):
        '''
        turn off the light
        '''
        self.control(0,0,0,0)

    def control(self,r,g,b,a):
        '''
        turn on the light with special color
        '''
        assert 0 <= r <= 255
        assert 0 <= g <= 255
        assert 0 <= b <= 255
        assert 0 <= a <= 100
        self.__write_character__(YeelightService.CHAR_CONTROL,self.__format_request__("%d,%d,%d,%d"%(r,g,b,a),18))

    def delay_on(self,mins = 5):
        '''
        turn on the light with a min param delay
        '''
        assert 0 < mins < 24 * 60
        self.__write_character__(YeelightService.CHAR_DELAY,self.__format_request__("%d,1" % mins,8))

    def delay_off(self,mins = 5):
        '''
        turn off the light with a min param delay
        '''
        assert 0 < mins < 24 * 60
        self.__write_character__(YeelightService.CHAR_DELAY,self.__format_request__("%d,0" % mins,8))

    def delay_status(self):
        '''
        query the delay status , this method return a raw dict with two key 'handle' and 'data'
        see http://www.yeelight.com/download/yeelight_blue_message_interface_v1.0.pdf
        '''
        return self.__notify_character__(YeelightService.CHAR_DELAY_QUERY,self.__format_request__("RT",2))

    def control_status(self):
        '''
        query the light status , this method return a raw dict with two key 'handle' and 'data'
        see http://www.yeelight.com/download/yeelight_blue_message_interface_v1.0.pdf
        '''
        return self.__notify_character__(YeelightService.CHAR_QUERY,self.__format_request__("S",1))

    def start_color_flow(self,flows):
        '''
        start color flow with a list of params, each param should contain 5 element which is r,g,b,brightness,delay
        see http://www.yeelight.com/download/yeelight_blue_message_interface_v1.0.pdf
        '''
        assert len(flows) <= 9
        for i,e in enumerate(flows):
            assert len(e) == 5
            assert 0 <= e[0] <= 255
            assert 0 <= e[1] <= 255
            assert 0 <= e[2] <= 255
            assert 0 <= e[3] <= 100
            assert 0 <= e[4] <= 10
            self.__write_character__(YeelightService.CHAR_COLOR_FLOW,self.__format_request__("%d,%d,%d,%d,%d,%d" % (i,e[0],e[1],e[2],e[3],e[4]),20))
        self.__write_character__(YeelightService.CHAR_COLOR_FLOW,self.__format_request__("CB",20))

    def stop_color_flow(self):
        '''
        stop color flow
        '''
        self.__write_character__(YeelightService.CHAR_COLOR_FLOW,self.__format_request__("CE",20))

    def effect_smooth(self):
        '''
        make the color change smooth
        '''
        self.__write_character__(YeelightService.CHAR_COLOR_EFFECT,self.__format_request__("TS",2))

    def effect_immediate(self):
        '''
        make the color changes immediate
        '''
        self.__write_character__(YeelightService.CHAR_COLOR_EFFECT,self.__format_request__("TE",2))

    def effect_current_color(self):
        '''
        use the current color as a default startup color
        '''
        self.__write_character__(YeelightService.CHAR_COLOR_EFFECT,self.__format_request__("DF",2))
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 #40
0
    # p = Peripheral("D9:35:6A:75:9F:9D", "random") # Rfduino sur usb
    continuer = True
    while(continuer):
        try:
            p = Peripheral("D1:7F:06:ED:66:DC", "random")  # Rfduino sur pcb
            continuer = False
        except:
            print "Module bluetooth deja connecte, nouvel essai dans 3 sec..."
            time.sleep(3)
    p.withDelegate(MyDelegate())
    Analyser.set_p(p)
    print " device connected..."

    try:
        p.getServices()
        ch = p.getCharacteristics(uuid=rx_uuid)[0]
        print ("notify characteristic with uuid 0x" + rx_uuid.getCommonName())
        cccid = btle.AssignedNumbers.client_characteristic_configuration
        # Ox000F : handle of Client Characteristic Configuration descriptor Rx - (generic uuid 0x2902)
        p.writeCharacteristic(0x000F, struct.pack('<bb', 0x01, 0x00), False)

        if ch.supportsRead():
            while 1:
                p.waitForNotifications(604800)  # 1 semaine d'attente
                # handleNotification() was called
                continue

    finally:
        Analyser.ls.close()
        p.disconnect()
Beispiel #41
0
ledev = None

def service(uuid):
    global ledev
    for u, s in ledev.services.items():
        print('%s : %s' % (str(u), str(s)))
        if u == uuid:
            print('Found %s' % uuid)
            return s
    print('%s not found' % uuid)
    return None

def handler(h, d):
    print('handler(h, d), h=%s, d=%s' % (repr(h), repr(d)))

ledev = Peripheral(addr)
ledev.discoverServices()
print('device:', ledev)
#for uuid in ['fff0', 'fff3']:
#    s = service(uuid)
#    for h in range(s.hndStart, s.hndEnd+1):
#        ledev.writeCharacteristic(h, struct.pack('<BB', 0, 1))

s = service('fff0')

print('listening for notifications...')
while True:
    print(ledev.waitForNotifications(1.))