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)
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...")
# 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()
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
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()
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")
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()
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) ################################################################################
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())
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
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())
): # 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()
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
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)
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
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()
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")
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)
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()
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()
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))
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
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)
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')
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')
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))
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
#!/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
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')
# 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()
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.))