class GAmonitor(object): SERVICE_UUIDS = [ UUID('c0be9c58-a717-4e18-828c-a41836f0c7e5'), # Sensors ] CHARACTERISTIC_UUIDS = {'9a067338-1da7-4e0f-8b4a-1e445e1e2df9': 'ACC'} NOTIFICATION_CHARACTERISTIC_UUIDS = [ 'ACC', ] # Notification data NOTIFICATION_ON = struct.pack("BB", 0x01, 0x00) NOTIFICATION_OFF = struct.pack("BB", 0x00, 0x00) def __init__(self, mac_address): self.macAddress = mac_address self.delegate = ScanDelegate() def set_delegate(self, delegate): self.delegate = delegate def connect(self): print('getting peripheral', sys.stderr) self.peripheral = Peripheral(self.macAddress, addrType='public') # Retrieve all characteristics from desired services and map them from their UUID self.peripheral.getServices() svc = self.peripheral.getServiceByUUID( "c0be9c58-a717-4e18-828c-a41836f0c7e5") characteristics = { svcid: svc.getCharacteristics()[i] for svcid, i in zip(self.CHARACTERISTIC_UUIDS, range(2)) } print(characteristics, sys.stderr) # Store each characteristic's value handle for each characteristic name self.characteristicValueHandles = dict( (name, characteristics[uuid].getHandle()) for uuid, name in self.CHARACTERISTIC_UUIDS.items()) # Subscribe for notifications for name in self.NOTIFICATION_CHARACTERISTIC_UUIDS: print('Enabling notification: ', sys.stderr) self.peripheral.writeCharacteristic( self.characteristicValueHandles[name] + 1, self.NOTIFICATION_ON, True) print(name, sys.stderr) print(self.characteristicValueHandles[name] + 1, sys.stderr) self.peripheral.setDelegate(self.delegate) def disconnect(self): self.peripheral.disconnect() def wait_for_notifications(self, timeout): print('calling wait for notifications') return self.peripheral.waitForNotifications(timeout)
def read_services_from_peripheral(self, peripheral: Peripheral, dev: ScanEntry) -> typing.List[Service]: ## Try getting servicex try: services = peripheral.getServices() self.relay_discovered_services(peripheral, services) return services except Exception as e: DBG("Getting services from {} failed".format(dev.addr), logLevel=LogLevel.ERR) logging.exception(e) services = list() return services
class BLEController: def __init__(self, delegate): # Variables self._delegate = delegate def connect(self, server_mac): self._server_mac = server_mac self._peripheral = Peripheral(self._server_mac) self._peripheral.setDelegate(self._delegate) self._listening = False def disconnect(self): self._peripheral.disconnect() def print_services(self, primary_service_uuid): svcs = self._peripheral.getServices() print("Services:") for s in svcs: print(s) print("Characteristics:") svc = self._peripheral.getServiceByUUID(primary_service_uuid) chs = svc.getCharacteristics() for c in chs: print(str(c) + " handle: " + str(c.getHandle())) def listen(self): if not self._listening: # Are we listening already? self._listening = True while self._listening: if self._peripheral.waitForNotifications( 1.0): # Calls the delegate if true # Delegate was called continue print('BLEController.listen() -> Listening...') def listen_async(self): #raise Exception("Not Implemented") self._listen_thread = _thread.start_new_thread(self.listen, ()) def adjust_light_source(self, value): # value is a tuple of RGB i.e. (255, 255, 255) # BLE characteristic expects a byte array value_bytes = bytes(value) print("BLEController.adjust_light_source -> {}".format(value_bytes)) handle = 49 # The handle value has to be found using e.g. print_services(), bluez, or similar self._peripheral.writeCharacteristic(handle, value_bytes) def stop_listening(self): self._listening = False
class BleDevice: def __init__(self): self.scanner = Scanner().withDelegate(ScanDelegate()) def Scan(self, time): self.devices = self.scanner.scan(time) def ListDevices(self, scan_data_flag): for dev in self.devices: print("Device {} ({}){}, RSSI={} dB".format( dev.addr, dev.addrType, ' [Connectable]' if dev.connectable else '', dev.rssi)) if scan_data_flag: for (adtype, desc, value) in dev.getScanData(): print(" {} {} = {}".format(adtype, desc, value)) def FindBoogie(self): boogie = None for dev in self.devices: for (adtype, desc, value) in dev.getScanData(): if adtype == 9: if value == "Boogie": boogie = dev self.boogie = dev return boogie def ConnectToDevice(self, device): # Function returns Connected to device when device is not there try: self.peripheral = Peripheral(device) print("Connected to device") except BTLEException: print('Connection failed') def GetServices(self): services = self.peripheral.getServices() print('Services') for service in services: print('{}'.format(service)) characteristics = service.getCharacteristics() for characteristic in characteristics: print('{}'.format(characteristic))
def from_device(address, addr_type): try: services = [] device = Peripheral(address, addr_type) for s in device.getServices(): services.append( Service(uuid=str(s.uuid), name=s.uuid.getCommonName(), characteristics=Characteristic.from_service(s))) return services except BTLEException: print(address, "disconnected. could not read services") return []
class SmartLampController: LAMP_SERVICE_UUID = "00001101-0000-1000-8000-00805f9b34fb" DEVICE_RX_CHARACTERISTIC_UUID = "00001142-0000-1000-8000-00805f9b34fb" def __init__(self, device_name="SmartLamp", scan_time=10.): assert isinstance(device_name, str) self.name = device_name self.scanner = Scanner().withDelegate(ScanDelegate()) print(self, "Start device scanning...") devices = self.scanner.scan(scan_time) print(self, "Scanning completed.") self.target_device = None for dev in devices: for adtype, desc, value in dev.getScanData(): if desc == "Complete Local Name" and value == self.name: self.target_device = dev print(self, f"Found target device: {dev.addr}") break assert self.target_device is not None self.peripheral = Peripheral(self.target_device.addr) print(self, f"Peripheral created.") services = self.peripheral.getServices() self.lamp_service = self.peripheral.getServiceByUUID(self.LAMP_SERVICE_UUID) print(self, f"Service is ready.") self.tx_characteristic = self.lamp_service.getCharacteristics(self.DEVICE_RX_CHARACTERISTIC_UUID)[0] print(self, f"Characteristic is ready.") def __del__(self): if self.peripheral is not None: self.peripheral.disconnect() def _write(self, msg): print(self, f"Send command: {msg}") self.tx_characteristic.write(bytes(msg, "utf-8")) def switch_lamp(self, state): r, g, b = 0, 0, 0 if isinstance(state, bool): if state: r, g, b = 255, 255, 255 elif isinstance(state, dict): r, g, b = state["r"], state["g"], state["b"] elif len(state) == 3: r, g, b = state self._write(f"rgb {r} {g} {b}") def __str__(self): return f"SmartLampController:{self.name}"
def search_loop(): while True: scanner = Scanner().withDelegate(ScanDelegate()) devices = scanner.scan(5.0) for dev in devices: data = dev.getScanData() for (adtype, desc, value) in data: if "Short Local Name" in desc: print("Cube found:") print(" Device %s (%s), RSSI=%d dB" % (dev.addr, dev.addrType, dev.rssi)) for (adtype, desc, value) in data: print(" %s = %s" % (desc, value)) p = Peripheral(deviceAddr=dev.addr, addrType=dev.addrType) for serv in p.getServices(): print(serv.uuid.getCommonName()) for char in serv.getCharacteristics(): pprint(char.propertiesToString()) return
def preparePeripheral(device): #print(" preparing peripheral:") print(" - address: \t{}".format(device.addr)) print(" - addresstype: \t{}".format(device.addrType)) print(" - interface: \t{}".format(device.iface)) print(" - connectable: \t{}".format(device.connectable)) peripheral = Peripheral(device) #devices[0].addr, devices[0].addrType) #peripheral = Peripheral(device.addr,device.addrType,device.iface) print(" peripheral set") peripheral.setDelegate(MyDelegate(peripheral)) print(" delegate set") Peripheral.availableHandles = { } # Dictionary containing ['UUID' : cccHandle] Peripheral.availabeChararacteristics = [] #peripheral.connect() time.sleep(3) print(" getting services") try: services = peripheral.getServices() except: print("Failed to get services") return 0 print(" displaying services") for ser in services: if ser.uuid in recognizedServices.keys(): print(" Found recognized service: {}".format(ser.uuid)) serChar = ser.getCharacteristics() for char in serChar: #print("Found char: {}".format(char.uuid)) if char.uuid in recognizedServices[str(ser.uuid)]: #handle = char.getHandle() print( " -with recognized charachteristic: {}\t({})".format( char.uuid.getCommonName(), char.uuid)) #print(" -with handle: 0x{:02X}".format(handle)) #cccHandle = getCCCHandle(peripheral, handle) #print (" -CCC handle: 0x{:02X}".format(cccHandle)) #peripheral.availableHandles.append(cccHandle) #peripheral.availableHandles[char.uuid] = cccHandle peripheral.availabeChararacteristics.append(char) return peripheral
def newPetition(device): p = Peripheral(device) print(p.getServices()) """ c = p.getCharacteristics(uuid=STATUS_UUID)[0] user = p.getCharacteristics(uuid=USER_UUID)[0] status = p.getCharacteristics(uuid=STATUS_UUID)[0] """ c = p.getCharacteristics(uuid=ANNOUNCE_UUID)[0] status = p.getCharacteristics(uuid=STATUS_UUID)[0] user_ble = p.getCharacteristics(uuid=USER_UUID)[0] user = user_ble.read() user_name = str(user, 'ascii')[1:] print(user_name) occupied_ble = p.getCharacteristics(uuid=OCCUPIED_UUID)[0] occupied = int(occupied_ble.read()) if occupied == 1: print("This spot is occupied") release_spot = releaseSpot(user_name, 1) if release_spot: status.write(b'1') c.write(b'1') else: status.write(b'3') c.write(b'1') elif occupied == 0: print("This spot is free") use_spot = useSpot(user_name, 1) if use_spot: status.write(b'1') c.write(b'1') else: status.write(b'3') c.write(b'1') else: pass p.disconnect() sleep(10) """
class PeripheralResource(): def __init__(self, peripheral, id): self.visible = True self.device = Peripheral(peripheral, random) services = self.device.getServices() self.resources = [] for service in services: try: for char in service.getCharacteristics(): btRes = BluetoothResource(char=char, type=char.propertiesToString()) self.resources.append(btRes) if char.uuid != UUID( 0x2A00) and "WRITE" in char.propertiesToString(): self.led_indicator = char except BTLEException as btexc: if (btexc.code == 2): continue self.device.setDelegate(NotifyDelegate(self.resources, id)) notifyThread = NotifyThread(self) notifyThread.start()
def preparePeripheral(device): #Create the peripheral object from bluetooth device peripheral = Peripheral(device) peripheral.setDelegate(MyDelegate(peripheral)) Peripheral.availableChararacteristics = [] time.sleep(2) #Read all device characteristics and add make a list of recognized characteristics services = peripheral.getServices() for ser in services: if ser.uuid == BLE_SERVICE_ENVIRONMENT: logging.debug(" Found recognized service: {}".format(ser.uuid.getCommonName())) serChar = ser.getCharacteristics() for char in serChar: if char.uuid in recognizedServices[str(ser.uuid)]: logging.debug(" Added characteristics: {}".format(char.uuid.getCommonName())) peripheral.availableChararacteristics.append(char) else: logging.debug(" (Unused characteristics: {})".format(char.uuid.getCommonName())) return peripheral
class TaggerService: def __init__(self, device): self.device = device self.peripheral = None self.services = None self.chars = None self.telemetry = None self.tagger_type = TYPE_PISTOL def connect(self): if self.device is not None: print "Connecting to Tagger" self.peripheral = Peripheral(self.device) self.services = self.peripheral.getServices() self.chars = self.peripheral.getCharacteristics() self.telemetry = TelemetryService(self.peripheral) self.telemetry.enable() def set_type(self, type): self.tagger_type = type def dump_services(self): if self.services is not None: for service in self.services: print "Service: ", service.uuid for char in self.chars: print "Characteristic: ", char.uuid def poll_data(self, time): if self.peripheral is not None: try: self.peripheral.waitForNotifications(1.0) return True except BTLEException: return False else: return False
class BtleDevice(DefaultDelegate): """Base class for a connection to a Bluetooth Low Energy device. Built on bluepy (which is built on bluez), and uses some bluepy classes, so this class is just a helper not really a full API. See https://ianharvey.github.io/bluepy-doc/index.html **** NOTE ***** As of 2019-12-06, you have to fix a bug in the btle.py library: Comment out the _getResp in disconnect() or it can hang on readline when a 'stat disc' reply from the helper is overlooked (as in during a call to setMTU). **** NOTE ***** """ # Common Services: generic_access_service_uuid = UUID('1800') device_info_service_uuid = UUID('180a') # Common Descriptors: characteristic_description_uuid = UUID('2901') characteristic_configure_uuid = UUID('2902') # Common Characteristics: model_number_uuid = UUID('2a24') # As string serial_number_uuid = UUID('2a25') # As string firmware_revision_uuid = UUID('2a26') # As string hardware_revision_uuid = UUID('2a27') # As string manufacturer_name_uuid = UUID('2a29') # As string def __init__(self, mac_addy, mtu=0xff, max_rate=10): """Connect to the device at the specified addy. Max_rate is the maximum rate in messages/second at which we will send messages to the device. I imagine this could/should be automatically inferred from the Peripheral Preferred Connection Parameters, but that's above my pay grade. All I know is, if you don't meter the outgoing messages, some peripherals will just start dropping requests, resulting in timeouts waiting for the acks. For an Airthings Wave+, max_rate 10 works with dumb throttling, or max_rate 5 with smart. The whole issue seems fishy to me -- seems like the lower level protocols should handle this better. But pragmatically, it regularly hangs without this. """ DefaultDelegate.__init__(self) assert mac_addy self.mac_addy = mac_addy self.mtu = mtu self.min_delay = 1. / max_rate self.peripheral = None self.cache = None # Will be dict mapping UUIDs to Characteristics; Created/cleared by connect() self.last_notification = None # This is only used transiently by the default notification handler. Ignore! if smart_throttle: self.last_xmt_time = 0 # Used to throttle outgoing messages. Is the time() of last sent message (or 0) #===== # If you don't know the MAC address, you'll need to scan, but that requires root... #===== @staticmethod def scan(match=None, duration=5, just_one=False, match_scan_entry=False): """This looks through any advertising btle devices for matching ones. *** Note you must run this as root *** match(scan_data) should return True if scan_data is from the device you're looking for. If no match function is provided, it is treated as always returning True (you want to see all devices that respond within duration). scan_data is a dict mapping description to value, unless match_scan_entry is True in which case scan_data is a btle ScanEntry object. Duration is how many seconds to scan before returning. If just_one is False, scans for the full duration and then returns a list of matching ScanEntry objects. (https://ianharvey.github.io/bluepy-doc/scanentry.html) If just_one is True, this returns the first match immediately (no list) or None after full duration timeout. Note (match==None and just_one==True) results in just the first device to respond being immediately returned. """ scanner = Scanner().withDelegate(DefaultDelegate()) scanner.clear() scanner.start() if not just_one: found = [] try: for i in range(max(int(duration * 10), 1)): # Break duration into 10ths scanner.process( 0.1) # Handle incoming messages for 1/10th of a second... devices = scanner.getDevices() for dev in devices: if debug: BtleDevice.dump_scan_entry(dev) if match_scan_entry: params = dev else: params = { name: val for tag, name, val in dev.getScanData() } if match is None or match(params): if just_one: return dev else: found.append(dev) scanner.clear() finally: scanner.stop() if just_one: return None return found def disconnect(self): """Do call this when you are done! (But no sooner. This will automatically re-connect if you try to do anything after this...) """ if self.peripheral is not None: if debug: print( "Disconnecting." ) # TODO - need to wrap all these debugging messages in a better logger... self.throttle() self.peripheral.disconnect() self.peripheral = None self.cache = None return True return False def connect(self): """Optional: Connects to the device. Returns True if this causes the connection, or False if we were already connected. Most of the methods here will call this on-demand, so you only need to call this explicitly if you want to access self.peripheral directly straight away. """ if self.peripheral is None: if debug: print("Connecting to %s." % (self.mac_addy, )) self.cache = {} self.peripheral = Peripheral(self.mac_addy).withDelegate(self) if self.mtu is not None: self.throttle() self.peripheral.setMTU( self.mtu ) # Notification and Indications may not happen if we don't do this. return True return False #===== # Most of the good stuff is here -- ways to read and write values: #===== def __getitem__(self, char): """This reads and returns the current value of the given characteristic. Char can be a uuid string, UUID object, or Characteristic object. """ self.throttle() return self.get_characteristic(char).read() def __setitem__(self, char, val): """This writes the byte array val to the specified characteristic. Char can be a uuid string, UUID object, or Characteristic object. """ self.throttle() self.get_characteristic(char).write(val, True) def get_handle(self, char): """This returns the Handle (small integer) of the specified characteristic. Char can be a uuid string, UUID object, or Characteristic object. Mainly useful to compare to the handle returned by wait_for_notification(). """ return self.get_characteristic(char).getHandle() def get_characteristic(self, uuid): """If you're just doing basic sets, queries and commands with the high level interface you probably don't need to call this directly. This accepts strings or UUID objects and returns the associated Characteristic object. (Or the first one if there are multiple with the same UUID. So, this is mainly useful for high level variables, not for meta information attributes or such.) As a convenience, if you pass a Characteristic, this will return it. (Which is useful for functions that want to allow flexible identification of the Characteristic.) Results are cached for efficiency, so don't be shy about using it. Raises KeyError if uuid can't be found. """ self.connect() if isinstance(uuid, Characteristic): return uuid if not isinstance(uuid, UUID): uuid = UUID(uuid) if uuid not in self.cache: self.throttle() c = self.peripheral.getCharacteristics(uuid=uuid) if c: self.cache[uuid] = c[0] else: self.cache[uuid] = None c = self.cache[uuid] if c is None: raise KeyError("Can't find characteristic %s" % (uuid, )) return c #===== # If you want Notifications or Indications, you need to enable each characteristic accordingly. #===== def can_notify(self, char): """Returns non-zero if this characteristic can generate Notifications or Indications. Char can be a uuid string, UUID object, or Characteristic object. Technically the return value can be further inspected to discern whether it supports Notifications, Indications, or (does this ever happen?) both. """ char = self.get_characteristic(char) return char.properties & (char.props['INDICATE'] | char.props['NOTIFY']) def enable(self, char): """Usually this is the easiest way to enable notifications from a characteristic. You have to call this (or one of the more specific ones below) before you will get any notifications from a given Characteristic. This enables characteristic char for either Indications or Notifications depending on which sort it claims to generate. Char can be a uuid string, UUID object, or Characteristic object. """ char = self.get_characteristic(char) if char.properties & char.props['INDICATE']: self.configure_characteristic(char, 2) elif char.properties & char.props['NOTIFY']: self.configure_characteristic(char, 1) else: raise Exception( "Characteristic does not support any notifications: %s" % (char, )) def disable(self, char): self.configure_characteristic(char, 0) def wait_for_notification(self, timeout): """This waits for the next notification and then returns it as a (handle, data) tuple. This only works if you do not override handleNotifications. This only handles notifications that come in during this call. If you call this successively, you should catch all notifications as long as you don't make any other Btle calls in between (since those calls could let a notification slip into the gap). Returns None on timeout. """ try: self.last_notification = False # False means we're waiting; None means we're not! if self.peripheral.waitForNotifications(timeout): return self.last_notification # Python should grab it before the finally clause resets it... else: return (None, None) finally: self.last_notification = None # Make sure handleNotifications alerts us to notifications that come out of band. def handleNotification(self, handle, data): """Subclass can override this to do whatever it wants with notifications/indications. """ if self.last_notification is None: # Don't put this under Debug flag. This alerts you to Notifications you aren't handling yet... print("UNHANDLED NOTIFICATION: handle=%r data=%r" % (handle, data.hex())) else: self.last_notification = (handle, data) #--- Unlikely you need to call any of these directly: def enable_notifications(self, char): """If you want to get Notifications from a characteristic, you need to call this first. Char can be a uuid string, UUID object, or Characteristic object. Note this also disables Indications for this characteristic. Use configure_characteristic to enable both. """ self.configure_characteristic(char, 1) def enable_indications(self, char): """If you want to get value Indications from a characteristic, you need to call this first. Char can be a uuid string, UUID object, or Characteristic object. Note this also disables Notifications for this characteristic. Use configure_characteristic to enable both. """ self.configure_characteristic(char, 2) def configure_characteristic(self, char, val): """This looks up the handle for the configuration descriptor for this characteristic, and then sends val to it. Char can be a uuid string, UUID object, or Characteristic object. Typically val here is 1 (enable Notifications) or 2 (enable Indications) or 0 (disable). """ char = self.get_characteristic(char) if debug: print("Finding configuration descriptor for handle 0x%x" % (char.getHandle(), )) self.throttle() d, = char.getDescriptors(forUUID=self.characteristic_configure_uuid) if debug: print("Found (handle 0x%x). Setting to 0x%02x" % (d.handle, val)) self.throttle() d.write(struct.pack('<H', val), True) def throttle(self): """This internal utility just waits until self.min_delay from the last time this method was called. """ if smart_throttle: now = time() delay = self.last_xmt_time + self.min_delay - now if delay > 0: sleep(delay) now += delay self.last_xmt_time = now else: sleep(self.min_delay) #===== # Below here are just utilities for displaying the device's data layout, # Which also serve as examples of how to access the devices's attributes. #===== def dump_services(self, read=False, indent=''): """This calls dump_service on all services this device provides. If read is True, it reads and prints each characteristic's value. This is a good way to manually discover the layout of your device. """ self.connect() for s in self.peripheral.getServices(): self.dump_service(s, read, indent) def dump_service(self, service, read=False, indent=''): """This calls dump_characteristic on all the characteristics in the given service. If service is not a Service object, it's assumed to be a UUID and the service will be looked up by that. """ self.connect() if not isinstance(service, Service): try: service = self.peripheral.getServiceByUUID(service) except BTLEEException: print("%sService %r: NOT FOUND" % (indent, service)) return print("%sService %r (%s):" % (indent, service.uuid.getCommonName(), service.uuid)) for c in service.getCharacteristics(): self.dump_characteristic(c, read, indent + " ") def dump_characteristic(self, c, read=False, indent=''): """Dumps whatever we can find out about a characteristic. If read is True, also reads and prints its value (when applicable) """ print("%sCharacteristic %r (%s):" % (indent, c.uuid.getCommonName(), c.uuid)) print("%s Handle: %s (%x)" % (indent, c.getHandle(), c.getHandle())) print("%s Readable: %s" % ( indent, c.supportsRead(), )) print("%s Properties: %s" % ( indent, c.propertiesToString(), )) try: descriptors = c.getDescriptors() except BTLEGattError: pass else: for d in descriptors: print("%s Descriptor: %s (handle 0x%x; uuid %s)" % (indent, d, d.handle, d.uuid)) if d.uuid == self.characteristic_description_uuid: self.throttle() print("%s ------> %r" % (indent, d.read())) if c.supportsRead(): try: self.throttle() val = c.read() except: print("%s (Read) Value: [READ FAILED]" % (indent, )) else: print("%s (Read) Value: %s (0x%s)" % (indent, val, val.hex())) @staticmethod def dump_scan_entry(se): """Just prints the info that comes back from a Scan response. """ print("Scanned Device:") print(" MAC: %s" % (se.addr, )) print(" Addr Type: %s" % (se.addrType, )) print(" Signal Strength: %s dB" % (se.rssi, )) print(" Connectable: %s" % (se.connectable, )) print(" Update Count: %s" % (se.updateCount, )) for t in se.getScanData(): print(" %r" % (t, ))
class Blueterm(cmd.Cmd): intro = cfg['intro'] prompt = cfg['prompt'] def __init__(self, device_index, scan_timeout): cmd.Cmd.__init__(self) self.device_index = device_index self.scan_timeout = scan_timeout self.ble_devs = set() self.ble_gatt = dict() # setup Bluetooth self.scanner = Scanner(device_index) self.periph = Peripheral(None, ADDR_TYPE_PUBLIC, device_index) self.periph.setDelegate(ShellEventHandler()) # Pla def precmd(self, line): return line def do_scan(self, line): """Scan for available BLE RIOT shells. Running this command will reset the cached list of available devices. usage: scan <scan timeout in sec> """ args = line.strip().split(' ') if len(args[0]) > 0: try: to = float(args[0]) except: print("error: unable to parse timeout (must be a number)") return else: to = self.scan_timeout print("Scanning now (blocking for {} seconds)...".format(to)) try: self.ble_devs = list(self.scanner.scan(to)) print("Scan complete:") self.do_list("") except: print("error: failure while scanning") return def do_list(self, line): """List all available BLE devices offering a RIOT shell """ if len(self.ble_devs) == 0: print("No BLE devices available") return for i, dev in enumerate(self.ble_devs): print("[{:2}] {}".format(i, dev.addr), end='') for (adtype, desc, value) in dev.getScanData(): if adtype == 9: print(" (Name: '{}'')".format(value), end='') print() def do_connect(self, line): args = line.strip().split(' ') if len(args[0]) == 0: print("usage: connect <device index>") return try: dev = self.ble_devs[int(args[0])] except: print("error: unable to find given device index") return try: self.periph.connect(dev.addr, dev.addrType) services = self.periph.getServices() for i, service in enumerate(services): print("Service {:2} UUID: {} ({})".format( i, service.uuid, service.uuid.getCommonName())) chars = service.getCharacteristics() type(chars) for i, char in enumerate(chars): print(" Char {:2} UUID: {} ({})".format( i, char.uuid, char.uuid.getCommonName())) # if char.supportsRead(): # tmp = char.read() # print("Data: ", str(tmp)) except: print("error: while conneting something was bad") return
def __get_services_and_chars(self): for device in self.__devices_around: try: if self.__devices_around.get( device) is not None and self.__devices_around[ device].get('scanned_device') is not None: log.debug('Connecting to device: %s', device) if self.__devices_around[device].get('peripheral') is None: address_type = self.__devices_around[device][ 'device_config'].get('addrType', "public") peripheral = Peripheral( self.__devices_around[device]['scanned_device'], address_type) self.__devices_around[device][ 'peripheral'] = peripheral else: peripheral = self.__devices_around[device][ 'peripheral'] try: log.info(peripheral.getState()) except BTLEInternalError: peripheral.connect( self.__devices_around[device]['scanned_device']) try: services = peripheral.getServices() except BTLEDisconnectError: self.__check_and_reconnect(device) services = peripheral.getServices() for service in services: if self.__devices_around[device].get( 'services') is None: log.debug( 'Building device %s map, it may take a time, please wait...', device) self.__devices_around[device]['services'] = {} service_uuid = str(service.uuid).upper() if self.__devices_around[device]['services'].get( service_uuid) is None: self.__devices_around[device]['services'][ service_uuid] = {} try: characteristics = service.getCharacteristics() except BTLEDisconnectError: self.__check_and_reconnect(device) characteristics = service.getCharacteristics() if self.__config.get('buildDevicesMap', False): for characteristic in characteristics: descriptors = [] self.__check_and_reconnect(device) try: descriptors = characteristic.getDescriptors( ) except BTLEDisconnectError: self.__check_and_reconnect(device) descriptors = characteristic.getDescriptors( ) except BTLEGattError as e: log.debug(e) except Exception as e: log.exception(e) characteristic_uuid = str( characteristic.uuid).upper() if self.__devices_around[device][ 'services'][service_uuid].get( characteristic_uuid) is None: self.__check_and_reconnect(device) self.__devices_around[device][ 'services'][service_uuid][ characteristic_uuid] = { 'characteristic': characteristic, 'handle': characteristic.handle, 'descriptors': {} } for descriptor in descriptors: log.debug(descriptor.handle) log.debug(str(descriptor.uuid)) log.debug(str(descriptor)) self.__devices_around[device][ 'services'][service_uuid][ characteristic_uuid][ 'descriptors'][ descriptor. handle] = descriptor else: for characteristic in characteristics: characteristic_uuid = str( characteristic.uuid).upper() self.__devices_around[device]['services'][ service_uuid][characteristic_uuid] = { 'characteristic': characteristic, 'handle': characteristic.handle } if self.__devices_around[device]['is_new_device']: log.debug('New device %s - processing.', device) self.__devices_around[device]['is_new_device'] = False self.__new_device_processing(device) for interest_char in self.__devices_around[device][ 'interest_uuid']: characteristics_configs_for_processing_by_methods = {} for configuration_section in self.__devices_around[ device]['interest_uuid'][interest_char]: characteristic_uuid_from_config = configuration_section[ 'section_config'].get("characteristicUUID") if characteristic_uuid_from_config is None: log.error( 'Characteristic not found in config: %s', pformat(configuration_section)) continue method = configuration_section[ 'section_config'].get('method') if method is None: log.error('Method not found in config: %s', pformat(configuration_section)) continue characteristics_configs_for_processing_by_methods[ method.upper()] = { "method": method, "characteristicUUID": characteristic_uuid_from_config } for method in characteristics_configs_for_processing_by_methods: data = self.__service_processing( device, characteristics_configs_for_processing_by_methods[ method]) for section in self.__devices_around[device][ 'interest_uuid'][interest_char]: converter = section['converter'] converted_data = converter.convert( section, data) self.statistics[ 'MessagesReceived'] = self.statistics[ 'MessagesReceived'] + 1 log.debug(data) log.debug(converted_data) self.__gateway.send_to_storage( self.get_name(), converted_data) self.statistics[ 'MessagesSent'] = self.statistics[ 'MessagesSent'] + 1 except BTLEDisconnectError: log.debug('Connection lost. Device %s', device) continue except Exception as e: log.exception(e)
class TransportBluepy(): def __init__(self): self.devices = [] self.peripheral = None def findRawDevices(self, timeout=1.0): rawDevices = [] scanner = Scanner() rawDevices = scanner.scan(timeout) return rawDevices def rawDeviceInfoStr(self, rawDevice): """ Convert the raw device into an info string. The format of the string is transport-specific. """ info = "Address: {0:s}\n".format(rawDevice.addr) info += "Address type: {0:s}\n".format( "public" if rawDevice.addrType == bluepy.btle.ADDR_TYPE_PUBLIC else "random") info += "Connections?: {0:s}\n".format( "yes" if rawDevice.connectable else "no") info += "Scan Data:\n" scanData = rawDevice.getScanData() for scanRow in scanData: info += " {0:d}\t| {1:s}\t| {2:s}\n".format( scanRow[0], scanRow[1], scanRow[2]) if rawDevice.connectable: self.connect(rawDevice.addr, rawDevice.addrType) info += "Services:\n" rawServices = self.getRawServices() for rawService in rawServices: info += "{0:s}".format(self.rawServiceInfoStr(rawService)) self.disconnect() return info def rawServiceInfoStr(self, rawService): info = " UUID: {0:s}\n".format(str(rawService.uuid)) info += " Characteristics:\n" rawCharacteristics = self.getRawCharacteristicsForService(rawService) for rawCharacteristic in rawCharacteristics: info += "{0:s}".format( self.rawCharacteristicInfoStr(rawCharacteristic)) return info def rawCharacteristicInfoStr(self, rawCharacteristic): info = " - UUID: {0:s}\n".format(str(rawCharacteristic.uuid)) info += " - Handle: {0:d} (0x{0:x})\n".format( rawCharacteristic.getHandle()) info += " - Properties: {0:s}\n".format( rawCharacteristic.propertiesToString()) return info def connect(self, addr, addrType=bluepy.btle.ADDR_TYPE_RANDOM): self.disconnect() self.peripheral = Peripheral(addr, addrType) def disconnect(self): if self.peripheral != None: self.peripheral.disconnect() self.peripheral = None def getRawServices(self): if self.peripheral == None: print("Not connected.\n") return {} rawServices = self.peripheral.getServices() print("Found {0:d} services\n".format(len(rawServices))) return rawServices def getRawCharacteristicsForService(self, service): return service.getCharacteristics() def getRawCharacteristicsByUUID(self, uuid): results = [] if self.peripheral != None: results = self.peripheral.getCharacteristics(uuid=uuid) return results
def __get_services_and_chars(self): for device in self.__devices_around: try: if self.__devices_around.get( device) is not None and self.__devices_around[ device].get('scanned_device') is not None: log.debug( 'Connecting to device with address: %s', self.__devices_around[device] ['scanned_device'].addr.upper()) if self.__devices_around[device].get('peripheral') is None: address_type = self.__devices_around[device][ 'device_config'].get('addrType', "public") peripheral = Peripheral( self.__devices_around[device]['scanned_device'], address_type) self.__devices_around[device][ 'peripheral'] = peripheral else: peripheral = self.__devices_around[device][ 'peripheral'] try: peripheral.connect(self.__devices_around[device] ['scanned_device']) except Exception as e: log.exception(e) services = peripheral.getServices() for service in services: if self.__devices_around[device].get( 'services') is None: log.debug( 'Building device %s map, it may take a time, please wait...', device) self.__devices_around[device]['services'] = {} service_uuid = str(service.uuid).upper() if self.__devices_around[device]['services'].get( service_uuid) is None: self.__devices_around[device]['services'][ service_uuid] = {} try: characteristics = service.getCharacteristics() except BTLEDisconnectError: self.__check_and_reconnect(device) characteristics = service.getCharacteristics() if self.__config.get('buildDevicesMap', False): for characteristic in characteristics: descriptors = [] try: self.__check_and_reconnect(device) try: descriptors = characteristic.getDescriptors( ) except BTLEDisconnectError: self.__check_and_reconnect(device) descriptors = characteristic.getDescriptors( ) except BTLEGattError as e: log.debug(e) except Exception as e: log.exception(e) characteristic_uuid = str( characteristic.uuid).upper() if self.__devices_around[device][ 'services'][service_uuid].get( characteristic_uuid ) is None: self.__devices_around[device][ 'services'][service_uuid][ characteristic_uuid] = { 'characteristic': characteristic, 'handle': characteristic.handle, 'descriptors': {} } for descriptor in descriptors: log.debug(descriptor.handle) log.debug(str(descriptor.uuid)) log.debug(str(descriptor)) self.__devices_around[device][ 'services'][service_uuid][ characteristic_uuid][ 'descriptors'][ descriptor. handle] = descriptor except BTLEDisconnectError: self.__check_and_reconnect(device) else: for characteristic in characteristics: characteristic_uuid = str( characteristic.uuid).upper() self.__devices_around[device]['services'][ service_uuid][characteristic_uuid] = { 'characteristic': characteristic, 'handle': characteristic.handle } if self.__devices_around[device]['is_new_device']: log.debug('New device %s - processing.', device) self.__devices_around[device]['is_new_device'] = False self.__new_device_processing(device) for interest_char in self.__devices_around[device][ 'interest_uuid']: for section in self.__devices_around[device][ 'interest_uuid'][interest_char]: data = self.__service_processing( device, section['section_config']) converter = section['converter'] converted_data = converter.convert(section, data) self.statistics[ 'MessagesReceived'] = self.statistics[ 'MessagesReceived'] + 1 log.debug(data) log.debug(converted_data) self.__gateway.send_to_storage( self.get_name(), converted_data) self.statistics['MessagesSent'] = self.statistics[ 'MessagesSent'] + 1 except BTLEDisconnectError: log.debug('Cannot connect to device %s', device) continue except Exception as e: log.exception(e)
class BluetoothLE: def __init__(self, deviceAddress, deviceType, addrType, mqttConnection): self.deviceAddress = deviceAddress self.deviceType = deviceType self.mqtt = mqttConnection self.addrType = addrType self.bleConnection = None self.ConnectToBLEDevice(True) def ConnectToBLEDevice(self, createNew): print "Connecting to device ", self.deviceType if createNew: self.bleConnection = Peripheral(self.deviceAddress, self.addrType) self.mqtt.setDeviceStatus(self, True) else: self.bleConnection.connect(self.deviceAddress, self.addrType) print "Connected to " + self.deviceType thread.start_new_thread(self.ReadLoop, ()) text_characters = "".join(map(chr, range(32, 127))) _null_trans = string.maketrans("", "") def isText(self, s, text_characters=text_characters, threshold=0.30): # if s contains any null, it's not text: if "\x00" in s: return False # an "empty" string is "text" (arbitrary but reasonable choice): if not s: return True # Get the substring of s made up of non-text characters t = s.translate(self._null_trans, text_characters) # s is 'text' if less than 30% of its characters are non-text ones: return len(t)/len(s) <= threshold def formatValue(self, value): #We need to determine if we have hex data or a character string #The best we can do, without hard coding characteristic ID's is to #see if the valueis alphanumeric or contains only 1 or 2 characters. #If the value is not alphanumeric or is a length of 1 or 2, assume it is hex data theValue = value if not self.isText(theValue) or len(theValue) ==1 or len(theValue) == 2: #Assume the data is hex. We now need to unpack it if len(theValue) == 1: #Assume signed char theValue = struct.unpack('b', theValue)[0] elif len(theValue) ==2: #Assume short theValue = struct.unpack('h', theValue)[0] elif len(theValue) == 4: #Assume long theValue = struct.unpack('l', theValue)[0] elif len(theValue) == 8: #Assume long long theValue = struct.unpack('q', theValue)[0] else: #Only other thing we can do theValue = theValue.encode('string-escape') return theValue def ReadLoop(self): readData = {} characteristics = self.bleConnection.getCharacteristics() for char in characteristics: if char.supportsRead(): try: value = self.formatValue(char.read()) readData[str(char.uuid.getCommonName())] = value except: print "Failed to read data for uuid ", str(char.uuid.getCommonName()) print sys.exc_info() readData["services"] = [] services = self.bleConnection.getServices() for service in services: serviceData = {} serviceData["name"] = str(service.uuid.getCommonName()) serviceChars = service.getCharacteristics() for serviceChar in serviceChars: if serviceChar.supportsRead(): try: value = self.formatValue(serviceChar.read()) serviceData[str(serviceChar.uuid.getCommonName())] = value except: print "Failed to read data for uuid ", str(serviceChar.uuid.getCommonName()) print sys.exc_info() if serviceData: readData["services"].append(serviceData) print readData message = {} message["deviceAddress"] = self.deviceAddress message["deviceType"] = self.deviceType message["readData"] = str(readData) self.mqtt.PublishMessage(json.dumps(message), "BLEReadData", None) #Disconnect the peripheral once we are done reading data self.bleConnection.disconnect() def ReadValue(self, uuid): self.bleConnection.connect(self.deviceAddress, self.addrType) characteristics = self.bleConnection.getCharacteristics(uuid=uuid)[0] readValue = self.formatValue(characteristics.read()) messageToPublish = {} messageToPublish["deviceType"] = self.deviceType messageToPublish["deviceAddress"] = self.deviceAddress messageToPublish["readData"] = readValue self.mqtt.PublishMessage(json.dumps(messageToPublish), "BLEReadData", None) self.bleConnection.disconnect() print "Successfully read data from device. Data value = ", readValue def WriteValue(self, data, uuid): self.bleConnection.connect(self.deviceAddress, self.addrType) uuidValue = UUID(uuid) characteristics = self.bleConnection.getCharacteristics(uuid=uuid)[0] characteristics.write(data, True) print "Successfully wrote data to device " + self.deviceAddress self.bleConnection.disconnect()
elif isNewData: print "Received new data from", dev.addr scanner = Scanner().withDelegate(ScanDelegate()) devices = scanner.scan(0.1) macAddress = "4b:4e:4f:00:00:01" 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) periph = Peripheral(macAddress) services = periph.getServices() for service in services: print "Service UUID : %s" % (service.uuid.getCommonName()) characteristics = service.getCharacteristics() for char in characteristics: print "UUID : %s, property : %s" % (char.uuid.getCommonName(), char.propertiesToString()) if (char.uuid.getCommonName() == "626f6972-6564-656c-616c-636f6f6c0100" ): knocks = struct.unpack("<b", char.read())[0] print knocks periph.disconnect() url = "http://192.168.0.10/plugins/knocklet/core/api/knocklet.api.php" headers = {'content-type': 'application/json'}
DefaultDelegate.__init__(self) def handleNotification(self, cHandle, data): print(data) crtS = None crtL = None dev = Peripheral(mac) dev.setMTU(1000) dlg = delega() dev.withDelegate(dlg) srvz = dev.getServices() for srv in srvz: crt = srv.getCharacteristics() for c in crt: cuuid = c.uuid.getCommonName() pts = c.propertiesToString() print(cuuid + ' : ' + pts) # if c.supportsRead(): # r = c.read() # print(str(r)) if str(c.uuid) == cuuid: if c.supportsRead(): crtL = c # c.getHandle() else: crtS = c # c.getHandle()
class SbrickAPI(object): # UUID of Remote Control Commands. The Remote Control Commands characteristic allows full control over SBrick. rcc_uuid = '02b8cbcc-0e25-4bda-8790-a15f53e6010f' stop_hex = '00' drive_hex = '01' class DriveThread(Thread): def __init__(self, logger, sbrick, channel, direction, power): Thread.__init__(self) self._sbrick = sbrick self._channel = channel self._direction = direction self._power = power self._logger = logger self._stop_event = Event() self._timer_thd = None def run(self): self.drive() def drive(self): self.drive_channel() self.break_channel() def stop(self): self._stop_event.set() self._timer_thd.cancel() def times_up(self): self._logger.debug('Drive action times_up {}{}{}{}'.format( SbrickAPI.drive_hex, self._channel, self._direction, self._power)) self._stop_event.set() def reset_command(self, channel, direction, power): self._channel = channel self._direction = direction self._power = power def reset_timer(self, exec_time): if self._timer_thd: self._timer_thd.cancel() if self._stop_event: self._stop_event.clear() if MAGIC_FOREVER == exec_time: return self._timer_thd = Timer(exec_time, self.times_up) self._timer_thd.setName('timer_' + self._channel) self._timer_thd.start() def drive_channel(self): while (not self._stop_event.is_set()): drive_hex_string = SbrickAPI.drive_hex + self._channel + self._direction + self._power self.exec_command(drive_hex_string) # TODO: not need to sleep #time.sleep(0.1) time.sleep(1) def break_channel(self): stop_hex_string = SbrickAPI.stop_hex + self._channel self.exec_command(stop_hex_string) def exec_command(self, hex_string): self._logger.debug('Exec command {}'.format(hex_string)) binary = bytes.fromhex(hex_string) self._sbrick.rcc_char_write_ex(binary, reconnect_do_again=False) #self._sbrick.rcc_char_read_ex(reconnect_do_again=False) @property def stop_event(self): return self._stop_event @property def timer_thd(self): return self._timer_thd def __init__(self, logger, dev_mac): self._dev_mac = dev_mac self._logger = logger self._lock = Lock() # bluepy is not thread-safe, must use lock to protect it self._blue = Peripheral() self._rcc_char = None self._channel_thread = {'00': None, '01': None, '02': None, '03': None} def __enter__(self): self.connect() return self def __exit__(self, type, value, traceback): self.disconnect() def _construct_new_bluetooth_object(self): self._lock.acquire() self._logger.info("Construct a new bluetooth object") del self._blue self._blue = Peripheral() self._lock.release() def connect(self): try: self._lock.acquire() self._logger.info('Try to connect to SBrick ({})'.format( self._dev_mac)) # connect() is a blocking function self._blue.connect(self._dev_mac) except BTLEException as e: self._lock.release() self._logger.error('SBrick ({}): {}'.format( self._dev_mac, e.message)) if isinstance(e, BTLEDisconnectError): return False else: self._construct_new_bluetooth_object() self._logger.error('exit -1') sys.exit(-1) except Exception as e: self._lock.release() self._logger.error(e) self._construct_new_bluetooth_object() self._logger.error('exit -1') sys.exit(-1) else: self._logger.info('Connect to SBrick ({}) successfully'.format( self._dev_mac)) # Get remote control command characteristic try: self._logger.info('Get rcc characteristic') chars = self._blue.getCharacteristics(uuid=SbrickAPI.rcc_uuid) except Exception as e: self._lock.release() self._logger.error( "Failed to get SBrick characteristics ({}): {}".format( SbrickAPI.rcc_uuid, e)) self._construct_new_bluetooth_object() self._logger.error('exit -1') sys.exit(-1) else: for char in chars: if char.uuid == SbrickAPI.rcc_uuid: self._rcc_char = char # Get services information try: services = self._blue.getServices() except Exception as e: self._lock.release() self._logger.error("Failed to get SBrick services ({}): {}".format( self._dev_mac, e)) self._construct_new_bluetooth_object() self._logger.error('exit -1') sys.exit(-1) else: self._services = services self._lock.release() return True def disconnect_ex(self): # disconnect SBrick using bluetoothctl command bl_cmd = 'disconnect {}\nquit'.format(self._dev_mac) cmd = "echo -e '{}'".format(bl_cmd) p1 = subprocess.Popen(shlex.split(cmd), stdout=subprocess.PIPE) p2 = subprocess.Popen(shlex.split('bluetoothctl'), stdin=p1.stdout, stdout=subprocess.PIPE, shell=True) p1.stdout.close() p2.communicate() # wait 3 seconds for real disconnection, because `bluetoothctl #disconnect` is an asynchronous command time.sleep(3) def disconnect(self): self._lock.acquire() self._blue.disconnect() self._logger.info('Disconnect from SBrick({}) successfully'.format( self._dev_mac)) self._lock.release() def re_connect(self): self._logger.info('Re-connect to SBrick ({})'.format(self._dev_mac)) self.disconnect() return self.connect() def drive(self, channel='00', direction='00', power='f0', exec_time=1): # reset thread status when the thread is dead if self._channel_thread[ channel] and not self._channel_thread[channel].is_alive(): self._channel_thread[channel].join() self._channel_thread[channel] = None if None == self._channel_thread[channel]: # Create a thread for executing drive thd = SbrickAPI.DriveThread(self._logger, self, channel, direction, power) thd.setName('channel_' + channel) thd.reset_timer(exec_time) self._channel_thread[channel] = thd thd.start() else: self._logger.debug('Overwrite drive action') running_thd = self._channel_thread[channel] running_thd.reset_command(channel, direction, power) running_thd.reset_timer(exec_time) def stop(self, channels=['00']): # TODO: validate parameters self._logger.debug('Stop action') for channel in channels: thd = self._channel_thread[channel] if thd: thd.stop() thd.join() self._channel_thread[channel] = None def rcc_char_write_ex(self, binary, reconnect_do_again=True): # make sure _rcc_char exist self._lock.acquire() self._logger.debug( 'RCC characteristic writes binary: {}'.format(binary)) if not self._rcc_char: self._lock.release() self._construct_new_bluetooth_object() if False == self.re_connect(): return False else: self._lock.release() # write binary try: self._lock.acquire() self._rcc_char.write(binary) except BrokenPipeError as e: self._lock.release() self._logger.error('BrokerPipeError with bluepy-helper') self._logger.error('exit -1') sys.exit(-1) except BTLEException as e: self._lock.release() self._logger.error('SBrick ({}): {}'.format( self._dev_mac, e.message)) if isinstance(e, BTLEDisconnectError): self._construct_new_bluetooth_object() if False == self.re_connect(): return False if reconnect_do_again: self.rcc_char_write_ex(binary, reconnect_do_again=False) elif isinstance( e, BTLEInternalError ) and "Helper not started (did you call connect()?)" == e.message: self._construct_new_bluetooth_object() if False == self.re_connect(): return False if reconnect_do_again: self.rcc_char_write_ex(binary, reconnect_do_again=False) elif isinstance( e, BTLEException ) and "Error from bluepy-helper (badstate)" == e.message: if False == self.re_connect(): return False if reconnect_do_again: self.rcc_char_write_ex(binary, reconnect_do_again=False) else: self._construct_new_bluetooth_object() self._logger.error('exit -1') sys.exit(-1) except Exception as e: self._lock.release() self._logger.error(e) self._construct_new_bluetooth_object() self._logger.error('exit -1') sys.exit(-1) else: self._lock.release() return True def rcc_char_read_ex(self, reconnect_do_again=True): try: self._lock.acquire() out = self._rcc_char.read() except BrokenPipeError as e: self._lock.release() self._logger.error('BrokerPipeError with bluepy-helper') self._logger.error('exit -1') sys.exit(-1) except BTLEException as e: self._lock.release() self._logger.error('SBrick ({}): {}'.format( self._dev_mac, e.message)) if BTLEException.DISCONNECTED == e.code: if False == self.re_connect(): return False if reconnect_do_again: self.rcc_char_read_ex(reconnect_do_again=False) else: self._construct_new_bluetooth_object() self._logger.error('exit -1') sys.exit(-1) except Exception as e: self._lock.release() self._logger.error(e) self._construct_new_bluetooth_object() self._logger.error('exit -1') sys.exit(-1) else: self._lock.release() return out def get_info_service(self): self._logger.debug("Service information:") for s in self._services: self._logger.debug(" {service}, {uuid}".format(service=s, uuid=s.uuid)) chars = s.getCharacteristics() for c in chars: self._logger.debug(" {char}, {uuid}, {proty}".format( char=c, uuid=c.uuid, proty=c.propertiesToString())) ret = [] for s in self._services: service = {} service['description'] = "{}".format(s) service['uuid'] = s.uuid.getCommonName() service['characteristics'] = [] chars = s.getCharacteristics() for c in chars: characteristic = {} characteristic['description'] = "{}".format(c) characteristic['uuid'] = c.uuid.getCommonName() characteristic['property'] = c.propertiesToString() #characteristic['value'] = c.read() if c.supportsRead() else '' service['characteristics'].append(characteristic) ret.append(service) self.disconnect() return ret def get_info_adc(self): ret = {} # Get temperature code = bytes.fromhex('0F09') if False == self.rcc_char_write_ex(code): return ret binary = self.rcc_char_read_ex() value = struct.unpack("<H", binary)[0] self._temperature = (value / 118.85795) - 160 # Get voltage code = bytes.fromhex('0F08') if False == self.rcc_char_write_ex(code): return ret binary = self.rcc_char_read_ex() value = struct.unpack('<H', binary)[0] self._voltage = (value * 0.83875) / 2047.0 self._logger.debug("ADC information:") self._logger.debug(" Temperature = {}".format(self._temperature)) self._logger.debug(" Voltage = {}".format(self._voltage)) ret['temperature'] = self._temperature ret['voltage'] = self._voltage self.disconnect() return ret def get_info_general(self): ret = {} # Get is_authenticated code = bytes.fromhex('03') if False == self.rcc_char_write_ex(code): return ret binary = self.rcc_char_read_ex() value = struct.unpack("<B", binary)[0] self._is_auth = value # Get authentication timeout code = bytes.fromhex('09') if False == self.rcc_char_write_ex(code): return ret binary = self.rcc_char_read_ex() value = struct.unpack("<B", binary)[0] self._auth_timeout = value * 0.1 # second # Get brick ID code = bytes.fromhex('0A') if False == self.rcc_char_write_ex(code): return ret binary = self.rcc_char_read_ex() value = struct.unpack("<6B", binary) l = list(map(lambda v: "%X" % (v), list(value))) self._brick_id = ' '.join(l) # Get watchdog timeout code = bytes.fromhex('0E') if False == self.rcc_char_write_ex(code): return ret binary = self.rcc_char_read_ex() value = struct.unpack("<B", binary)[0] self._watchdog_timeout = value * 0.1 # second # Get thermal limit code = bytes.fromhex('15') if False == self.rcc_char_write_ex(code): return ret binary = self.rcc_char_read_ex() value = struct.unpack("<H", binary)[0] self._thermal_limit = (value / 118.85795) - 160 # Get PWM counter value code = bytes.fromhex('20') if False == self.rcc_char_write_ex(code): return ret binary = self.rcc_char_read_ex() value = struct.unpack("<H", binary)[0] self._pwm_counter_value = value # Get channel status code = bytes.fromhex('22') if False == self.rcc_char_write_ex(code): return ret binary = self.rcc_char_read_ex() value = struct.unpack("<7B", binary) self._channel_status = value # Get is guest password set code = bytes.fromhex('23') if False == self.rcc_char_write_ex(code): return ret binary = self.rcc_char_read_ex() value = struct.unpack("<B", binary)[0] self._is_quest_pw_set = value # Get connection parameters code = bytes.fromhex('25') if False == self.rcc_char_write_ex(code): return ret binary = self.rcc_char_read_ex() value = struct.unpack("<3H", binary) self._conn_param = value # Get release on reset code = bytes.fromhex('27') if False == self.rcc_char_write_ex(code): return ret binary = self.rcc_char_read_ex() value = struct.unpack("<B", binary)[0] self._ror = value # Get power cycle counter code = bytes.fromhex('28') if False == self.rcc_char_write_ex(code): return ret binary = self.rcc_char_read_ex() value = struct.unpack("<I", binary)[0] self._power_cycle_counter = value # Get uptime counter code = bytes.fromhex('29') if False == self.rcc_char_write_ex(code): return ret binary = self.rcc_char_read_ex() value = struct.unpack("<I", binary)[0] self._uptime_counter = value self._logger.debug("General information:") self._logger.debug(" Is authenticated: {}".format(self._is_auth)) self._logger.debug(" Authentication timeout(second): {}".format( self._auth_timeout)) self._logger.debug(" Brick ID: {}".format(self._brick_id)) self._logger.debug(" Watchdog timeout(second): {}".format( self._watchdog_timeout)) self._logger.debug(" Thermal limit: {}".format(self._thermal_limit)) self._logger.debug(" PWM counter value: {}".format( self._pwm_counter_value)) self._logger.debug(" Channel status: {}".format(self._channel_status)) self._logger.debug(" Is guest password set: {}".format( self._is_quest_pw_set)) self._logger.debug(" Connection parameters(ms): {}".format( self._conn_param)) self._logger.debug(" Release on reset: {}".format(self._ror)) self._logger.debug(" Power cycle counter: {}".format( self._power_cycle_counter)) self._logger.debug(" Uptime counter: {}".format(self._uptime_counter)) ret['is_auth'] = self._is_auth ret['auth_timeout'] = self._auth_timeout ret['brick_id'] = self._brick_id ret['watchdog_timeout'] = self._watchdog_timeout ret['thermal_limit'] = self._thermal_limit ret['is_quest_password_set'] = self._is_quest_pw_set ret['power_cycle_count'] = self._power_cycle_counter ret['uptime_count'] = self._uptime_counter self.disconnect() return ret def set_watchdog_timeout(self, timeout): """ timeout: 0.1 seconds, 1 byte. Ragne: 0 ~ 255 """ self.connect() code = bytes.fromhex('0D') + struct.pack('<B', timeout) self.rcc_char_write_ex(code) self.disconnect() @property def blue(self): return self._blue
# To solve the endianess problem reverse the # string to find exact value devices = Scanner() # Scanner Object temp=devices.scan(10) # Start Scan try: for dev in temp: if dev.getScanData()[3][2] == "mr. singh": # Check for the target BLE device name if dev.connectable: p = Peripheral(dev.addr, "random") p.setDelegate(ScanDelegate()) # Create internal Object of scandelegate class to handle the notifications except (RuntimeError, TypeError, NameError): print "Device not found" exit(1) ################### Check for the Services and its characteristics ################# print p.getServices()[0] print p.getServices()[1] print p.getServices()[2] Heart_Rate_Measurement = p.getServiceByUUID(0x180D).getCharacteristics()[0] Body_Sensor_Location = p.getServiceByUUID(0x180D).getCharacteristics()[1] Heart_Rate_Control_Point = p.getServiceByUUID(0x180D).getCharacteristics()[2] print Heart_Rate_Measurement , Heart_Rate_Measurement.uuid # Print characteristic and its uuid print Body_Sensor_Location , Body_Sensor_Location.uuid print Heart_Rate_Control_Point , Heart_Rate_Control_Point.uuid ################## Print the Value ########################### body_sensor=["not_selected","Chest","Wrist","Finger","Hand","Ear Lobe","Foot"] # List for body sensor location try: ch = p.getServiceByUUID(0x180D).getCharacteristics()[1] # body sensor location characteristics
print(" %04x: %s = %s" % (adtype, desc, value)) print("Try to connect to %s..." % dev.addr) for i in range(0, 5): try: p = Peripheral(dev) break except BTLEException as e: print("Problems connecting to %s:" % dev.addr, e) continue if not p: print("Could not connect to device, giving up.") continue try: # p = Peripheral(dev) p.setDelegate(MyDelegate(None)) for service in p.getServices(): uuid = str(service.uuid) uuid = uuid[:4] + "'''" + uuid[4:8] + "'''" # + uuid[8:] # ''' desc = uuid_desc(str(service.uuid)[:8]) or "?" print("| Service || <code>%s</code> || || || %s || ||" % (uuid, desc)) print("|-") for char in service.getCharacteristics(): uuid = str(char.uuid) uuid = uuid[:4] + "'''" + uuid[4:8] + "'''" # + uuid[8:] # ''' desc = uuid_desc(str(char.uuid)[:8]) or "?" type = uuid_type(str(char.uuid)[:8]) or "" data = "" if char.supportsRead(): if str(service.uuid) in ignore_char: data = '(ignored service)'
class Switcher: switcher = None characteristics = None battery_handler = None hashed_share_code_handler = None authority_handler = None time_handler = None switch_handler = None uuids = None share_code = None def __init__(self, mac_address, share_code, debug=False): bluepy.btle.Debugging = debug self.mac_address = mac_address self.share_code = share_code def scan(self): scan_timeout = 10 # seconds scanner = Scanner() retry = True while (retry): try: print('Scanning...') devices = scanner.scan(scan_timeout) for device in devices: name = None for ad_type, description, value in device.getScanData(): if ad_type == 9: name = value if name is None or 'SWITCHER_M' not in name: continue print('A switcher is found: {}({} {})'.format( name, device.addr, device.addrType)) if device.connectable: print('Connectable switcher is found.') self.mac_address = device.addr retry = False break else: print('The switcher is busy') if retry: print('Connectable switcher is not found. Retry...') sleep(2) except Exception as e: print('Error on scanning') traceback.print_exc() self.connect() def connect(self, callback=None): retry = True while retry: try: print('Try to connect to the switcher...') self.switcher = Peripheral(self.mac_address, 'random') retry = False except Exception as e: print('Error on connecting') traceback.print_exc() print('Switcher is connected') if callback: try: callback.on_connected(self) except BTLEException as e: traceback.print_exc() if e.code == BTLEException.DISCONNECTED: print('Switcher disconnected') self.switcher = None # TODO: try re-connect except Exception as e: print('Error on using') traceback.print_exc() finally: if self.switcher: print('Disconnect due to an error') self.switcher.disconnect() def auto_reconnect(func): @functools.wraps(func) def wrap(self, *args, **kargs): if self.switcher is None: self.connect() while (True): try: return func(self, *args, **kargs) except BTLEException as e: if e.code == BTLEException.DISCONNECTED: print('Switcher has gone') else: traceback.print_exc() print('Try reconnect...') self.connect() return wrap def disconnect(self): if self.switcher: self.switcher.disconnect() print('Switcher is disconnected') def to_bytes(self, digits): return bytearray(int(ch) for ch in str(digits)) def show_informations(self): self.load_uuids() self.get_services(True) self.get_characteristics(True) def load_uuids(self): import json with open('uuid.json') as f: self.uuids = {v: k for k, v in json.load(f).items()} def get_uuid_description(self, uuid): try: return self.uuids[uuid] except: return 'Unknown' def get_services(self, print_services=False): print('get_services') services = self.switcher.getServices() if print_services: for service in services: uuid = str(service.uuid) print('UUID: {} ({})'.format(uuid, self.get_uuid_description(uuid))) def get_characteristics(self, print_characteristics=False): print('get_characteristics') if self.characteristics is None: self.characteristics = self.switcher.getCharacteristics() if print_characteristics: for ch in self.characteristics: uuid = str(ch.uuid) print('UUID: {} ({})'.format(uuid, self.get_uuid_description(uuid))) print('Properties: {} ({})'.format(ch.properties, ch.propertiesToString())) print('Handler: 0x{:02x}'.format(ch.getHandle())) print('\n') return self.characteristics def get_descriptors(self): """ Switcher sends no response for this method """ descriptors = self.switcher.getDescriptors() print('descriptors') print(descriptors) def get_handler(self, number_of_handler): characteristics = self.get_characteristics() for ch in characteristics: if ch.getHandle() == number_of_handler: return ch print('There is no handler: {}'.format(number_of_handler)) return None def get_hashed_share_code_handler(self): if not self.hashed_share_code_handler: self.hashed_share_code_handler = self.get_handler(0x1d) return self.hashed_share_code_handler @auto_reconnect def get_battery(self): battery = int.from_bytes(self.switcher.readCharacteristic(0xe), byteorder='big') return battery @auto_reconnect def compare_hashed_share_code(self): hashed_share_code = self.to_bytes('0' + self.share_code) print('Write: {}'.format(hashed_share_code)) result = self.switcher.writeCharacteristic(0x1d, hashed_share_code, True) print(result) @auto_reconnect def get_authority(self): return self.switcher.readCharacteristic(0x1f)[0] def get_day_name(self, day): days = [ 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday' ] return days[day] @auto_reconnect def get_time(self): day, hours, minutes = self.switcher.readCharacteristic(0x2b) return '{} {:02d}:{:02d}'.format(self.get_day_name(day), hours, minutes) @auto_reconnect def manage_switch(self, switch, on=True): """ switch: 1, 2 on: True / False switch 1 on -> 0 switch 1 off -> 1 switch 2 on -> 2 switch 2 off -> 3 """ if switch not in [1, 2]: print('Switch must be 1 or 2') return print('Switch {} {}'.format(switch, 'ON' if on else 'OFF')) param = (switch - 1) * 2 + (0 if on else 1) print(param) result = self.switcher.writeCharacteristic(0x11, bytes([param]), True) print(result) @auto_reconnect def test_switch(self, value): result = self.switcher.writeCharacteristic(0x11, bytes([value]), True) print(result)
#print'Connected: '+str(len(connection_threads)) #print 'Scanning...' devices = scanner.scan(2) for d in devices: #print "[",d.addr,"]:",d.rssi s = "" found = 0 for (adtype, desc, value) in d.getScanData(): #print " %s = %s"%(desc,value) s = s + str(desc) + " : " + str(value) + "\n" if desc == "Complete Local Name" and "HMSoft" in value: #print s try: p = Peripheral(d) p.setDelegate(NotificationDelegate(0)) services = p.getServices() for srv in services: #print " UUID:", srv.uuid try: characteristics = srv.getCharacteristics() for c in characteristics: #print " UUID:", c.uuid if c.uuid == "0000ffe1-0000-1000-8000-00805f9b34fb": print "Alco char found!" #Connect c.write( binascii.unhexlify( sendDataPacket( "0x04", "0x04,0xff,0x01"))) p.waitForNotifications(1.0) #Measure
def scan(self, bdaddr, addr_type, include_descriptor: bool): """ bdaddr - Remote BD_ADDR """ def run_mainloop(): mainloop.run() mainloop_thread = threading.Thread(target=run_mainloop, args=[]) mainloop_thread.start() try: try: target = Peripheral(bdaddr, iface=self.devid, addrType=addr_type) except BTLEDisconnectError as e: logger.error("BTLEDisconnectError") print(ERROR_INDENT, e, sep='') return services = target.getServices() print("Number of services: %s\n\n" % len(services)) # Show service for service in services: logger.debug('Start handle: {}'.format(service.hndStart)) logger.debug('End handle: {}'.format(service.hndEnd)) try: characteristics = [] characteristics = service.getCharacteristics() except BTLEException as e: logger.warning("BTLEException") print(WARNING_INDENT, e, sep='') # continue print( blue('Service'), '(0x%04x - 0x%04x, %s characteristics)' % (service.hndStart, service.hndEnd, len(characteristics))) print( indent + 'Handle: 0x%04x' % service.hndStart ) # ", "\"attr handle\" by using gatttool -b <BD_ADDR> --primary print(indent + 'Type: (May be primary service 0x2800)') print(indent + 'Value (Service UUID): ', blue(str(service.uuid).replace(sig_uuid_suffix, '')), end=' ') try: print( '(' + services_spec['0x' + ("%s" % service.uuid)[4:8].upper()]['Name'] + ')', '\x1B[0m') except KeyError: print('(' + red('unknown') + ')', '\x1B[0m') print( indent + 'Permission: Read Only, No Authentication, No Authorization\n' ) # Show characteristic for characteristic in characteristics: descriptors = [] # 对每个 characteristic 都获取 descriptor 会很耗时 # 有些设备会因此断开连接。于是这里提供了一个是否获取 descriptor 的选项 if include_descriptor: descriptors = characteristic.getDescriptors() try: print(indent + yellow('Characteristic'), '(%s descriptors)' % len(descriptors)) #print('-'*8) print(indent * 2 + 'Handle: %#06x' % (characteristic.getHandle() - 1)) print(indent * 2 + 'Type: 0x2803 (Characteristic)') print(indent * 2 + 'Value:') print(indent * 3 + 'Characteristic properties:', green(characteristic.propertiesToString())) print(indent * 3 + 'Characteristic value handle: %#06x' % characteristic.getHandle()) print( indent * 3 + 'Characteristic UUID: ', green( str(characteristic.uuid).replace( sig_uuid_suffix, '')), end=' ' ) # This UUID is also the type field of characteristic value declaration attribute. try: print('(' + characteristics_spec['0x' + ( "%s" % characteristic.uuid)[4:8].upper()]['Name'] + ')') except KeyError: print('(' + red('unknown') + ')') print( indent * 3 + 'Permission: Read Only, No Authentication, No Authorization' ) if characteristic.supportsRead(): print(indent + yellow('Characteristic value')) print(indent * 2 + 'Handle:', green('%#06x' % characteristic.getHandle())) print( indent * 2 + 'Type:', str(characteristic.uuid).replace( sig_uuid_suffix, '')) print(indent * 2 + 'Value:', green(str(characteristic.read()))) print( indent * 2 + 'Permission: Higher layer profile or implementation-specific' ) except BTLEException as e: print(' ' + str(e)) # Show descriptor for descriptor in descriptors: try: print(indent + yellow('Descriptor')) print(indent * 2 + 'Handle:', green('%#06x' % descriptor.handle)) print(indent * 2 + 'Type:', str(descriptor.uuid).replace( sig_uuid_suffix, ''), end=' ') try: print('(' + descriptors_spec['0x' + ( "%s" % descriptor.uuid)[4:8].upper()]['Name'] + ')') except KeyError: print('(Unknown descriptor)') print(indent * 2 + 'Value:', green(str(descriptor.read()))) print(indent * 2 + 'Permissions:') except BTLEException as e: print(indent * 2 + str(e)) print() print() # Set remote device untursted output = subprocess.check_output(' '.join( ['bluetoothctl', 'untrust', bdaddr]), stderr=STDOUT, timeout=60, shell=True) logger.info(output.decode()) # output = subprocess.check_output( # ' '.join(['sudo', 'systemctl', 'stop', 'bluetooth.service']), # stderr=STDOUT, timeout=60, shell=True) # output = subprocess.check_output( # ' '.join(['sudo', 'rm', '-rf', '/var/lib/bluetooth/' + \ # self.hci_bdaddr + '/' + bdaddr.upper()]), # stderr=STDOUT, timeout=60, shell=True) # output = subprocess.check_output( # ' '.join(['sudo', 'systemctl', 'start', 'bluetooth.service']), # stderr=STDOUT, timeout=60, shell=True) finally: if self.agent_registered: self.agent_mgr_1_iface.UnregisterAgent( ObjectPath(self.bluescan_agent.path)) logger.info('Unregistered Agent object') mainloop.quit()
class BLEConnection: def __init__(self, address, mambo): """ Initialize with its BLE address - if you don't know the address, call findMambo and that will discover it for you. :param address: unique address for this mambo :param mambo: the Mambo object for this mambo (needed for callbacks for sensors) """ self.address = address self.drone_connection = Peripheral() self.mambo = mambo # the following UUID segments come from the Mambo and from the documenation at # http://forum.developer.parrot.com/t/minidrone-characteristics-uuid/4686/3 # the 3rd and 4th bytes are used to identify the service self.service_uuids = { 'fa00': 'ARCOMMAND_SENDING_SERVICE', 'fb00': 'ARCOMMAND_RECEIVING_SERVICE', 'fc00': 'PERFORMANCE_COUNTER_SERVICE', 'fd21': 'NORMAL_BLE_FTP_SERVICE', 'fd51': 'UPDATE_BLE_FTP', 'fe00': 'UPDATE_RFCOMM_SERVICE', '1800': 'Device Info', '1801': 'unknown', } # the following characteristic UUID segments come from the documentation at # http://forum.developer.parrot.com/t/minidrone-characteristics-uuid/4686/3 # the 4th bytes are used to identify the characteristic # the usage of the channels are also documented here # http://forum.developer.parrot.com/t/ble-characteristics-of-minidrones/5912/2 self.characteristic_send_uuids = { '0a': 'SEND_NO_ACK', # not-ack commandsandsensors (PCMD only) '0b': 'SEND_WITH_ACK', # ack commandsandsensors (all piloting commandsandsensors) '0c': 'SEND_HIGH_PRIORITY', # emergency commandsandsensors '1e': 'ACK_COMMAND' # ack for data sent on 0e } # counters for each packet (required as part of the packet) self.characteristic_send_counter = { 'SEND_NO_ACK': 0, 'SEND_WITH_ACK': 0, 'SEND_HIGH_PRIORITY': 0, 'ACK_COMMAND': 0, 'RECEIVE_WITH_ACK': 0 } # the following characteristic UUID segments come from the documentation at # http://forum.developer.parrot.com/t/minidrone-characteristics-uuid/4686/3 # the 4th bytes are used to identify the characteristic # the types of commandsandsensors and data coming back are also documented here # http://forum.developer.parrot.com/t/ble-characteristics-of-minidrones/5912/2 self.characteristic_receive_uuids = { '0e': 'ACK_DRONE_DATA', # drone data that needs an ack (needs to be ack on 1e) '0f': 'NO_ACK_DRONE_DATA', # data from drone (including battery and others), no ack '1b': 'ACK_COMMAND_SENT', # ack 0b channel, SEND_WITH_ACK '1c': 'ACK_HIGH_PRIORITY', # ack 0c channel, SEND_HIGH_PRIORITY } # these are the FTP incoming and outcoming channels # the handling characteristic seems to be the one to send commandsandsensors to (per the SDK) # information gained from reading ARUTILS_BLEFtp.m in the SDK self.characteristic_ftp_uuids = { '22': 'NORMAL_FTP_TRANSFERRING', '23': 'NORMAL_FTP_GETTING', '24': 'NORMAL_FTP_HANDLING', '52': 'UPDATE_FTP_TRANSFERRING', '53': 'UPDATE_FTP_GETTING', '54': 'UPDATE_FTP_HANDLING', } # FTP commandsandsensors (obtained via ARUTILS_BLEFtp.m in the SDK) self.ftp_commands = { "list": "LIS", "get": "GET" } # need to save for communication (but they are initialized in connect) self.services = None self.send_characteristics = dict() self.receive_characteristics = dict() self.handshake_characteristics = dict() self.ftp_characteristics = dict() self.data_types = { 'ACK': 1, 'DATA_NO_ACK': 2, 'LOW_LATENCY_DATA': 3, 'DATA_WITH_ACK': 4 } # store whether a command was acked self.command_received = { 'SEND_WITH_ACK': False, 'SEND_HIGH_PRIORITY': False, 'ACK_COMMAND': False } # instead of parsing the XML file every time, cache the results self.command_tuple_cache = dict() self.sensor_tuple_cache = dict() # maximum number of times to try a packet before assuming it failed self.max_packet_retries = 3 def connect(self, num_retries): """ Connects to the drone and re-tries in case of failure the specified number of times :param: num_retries is the number of times to retry :return: True if it succeeds and False otherwise """ # first try to connect to the wifi try_num = 1 connected = False while (try_num < num_retries and not connected): try: self._connect() connected = True except BTLEException: color_print("retrying connections", "INFO") try_num += 1 # fall through, return False as something failed return connected def _reconnect(self, num_retries): """ Reconnect to the drone (assumed the BLE crashed) :param: num_retries is the number of times to retry :return: True if it succeeds and False otherwise """ try_num = 1 success = False while (try_num < num_retries and not success): try: color_print("trying to re-connect to the mambo at address %s" % self.address, "WARN") self.drone_connection.connect(self.address, "random") color_print("connected! Asking for services and characteristics", "SUCCESS") success = True except BTLEException: color_print("retrying connections", "WARN") try_num += 1 if (success): # do the magic handshake self._perform_handshake() return success def _connect(self): """ Connect to the mambo to prepare for flying - includes getting the services and characteristics for communication :return: throws an error if the drone connection failed. Returns void if nothing failed. """ color_print("trying to connect to the mambo at address %s" % self.address, "INFO") self.drone_connection.connect(self.address, "random") color_print("connected! Asking for services and characteristics", "SUCCESS") # re-try until all services have been found allServicesFound = False # used for notifications handle_map = dict() while not allServicesFound: # get the services self.services = self.drone_connection.getServices() # loop through the services for s in self.services: hex_str = self._get_byte_str_from_uuid(s.uuid, 3, 4) # store the characteristics for receive & send if (self.service_uuids[hex_str] == 'ARCOMMAND_RECEIVING_SERVICE'): # only store the ones used to receive data for c in s.getCharacteristics(): hex_str = self._get_byte_str_from_uuid(c.uuid, 4, 4) if hex_str in self.characteristic_receive_uuids: self.receive_characteristics[self.characteristic_receive_uuids[hex_str]] = c handle_map[c.getHandle()] = hex_str elif (self.service_uuids[hex_str] == 'ARCOMMAND_SENDING_SERVICE'): # only store the ones used to send data for c in s.getCharacteristics(): hex_str = self._get_byte_str_from_uuid(c.uuid, 4, 4) if hex_str in self.characteristic_send_uuids: self.send_characteristics[self.characteristic_send_uuids[hex_str]] = c elif (self.service_uuids[hex_str] == 'UPDATE_BLE_FTP'): # store the FTP info for c in s.getCharacteristics(): hex_str = self._get_byte_str_from_uuid(c.uuid, 4, 4) if hex_str in self.characteristic_ftp_uuids: self.ftp_characteristics[self.characteristic_ftp_uuids[hex_str]] = c elif (self.service_uuids[hex_str] == 'NORMAL_BLE_FTP_SERVICE'): # store the FTP info for c in s.getCharacteristics(): hex_str = self._get_byte_str_from_uuid(c.uuid, 4, 4) if hex_str in self.characteristic_ftp_uuids: self.ftp_characteristics[self.characteristic_ftp_uuids[hex_str]] = c # need to register for notifications and write 0100 to the right handles # this is sort of magic (not in the docs!) but it shows up on the forum here # http://forum.developer.parrot.com/t/minimal-ble-commands-to-send-for-take-off/1686/2 # Note this code snippet below more or less came from the python example posted to that forum (I adapted it to my interface) for c in s.getCharacteristics(): if self._get_byte_str_from_uuid(c.uuid, 3, 4) in \ ['fb0f', 'fb0e', 'fb1b', 'fb1c', 'fd22', 'fd23', 'fd24', 'fd52', 'fd53', 'fd54']: self.handshake_characteristics[self._get_byte_str_from_uuid(c.uuid, 3, 4)] = c # check to see if all 8 characteristics were found allServicesFound = True for r_id in self.characteristic_receive_uuids.values(): if r_id not in self.receive_characteristics: color_print("setting to false in receive on %s" % r_id) allServicesFound = False for s_id in self.characteristic_send_uuids.values(): if s_id not in self.send_characteristics: color_print("setting to false in send") allServicesFound = False for f_id in self.characteristic_ftp_uuids.values(): if f_id not in self.ftp_characteristics: color_print("setting to false in ftp") allServicesFound = False # and ensure all handshake characteristics were found if len(self.handshake_characteristics.keys()) != 10: color_print("setting to false in len") allServicesFound = False # do the magic handshake self._perform_handshake() # initialize the delegate to handle notifications self.drone_connection.setDelegate(MamboDelegate(handle_map, self.mambo, self)) def _perform_handshake(self): """ Magic handshake Need to register for notifications and write 0100 to the right handles This is sort of magic (not in the docs!) but it shows up on the forum here http://forum.developer.parrot.com/t/minimal-ble-commandsandsensors-to-send-for-take-off/1686/2 :return: nothing """ color_print("magic handshake to make the drone listen to our commandsandsensors") # Note this code snippet below more or less came from the python example posted to that forum (I adapted it to my interface) for c in self.handshake_characteristics.values(): # for some reason bluepy characteristic handle is two lower than what I need... # Need to write 0x0100 to the characteristics value handle (which is 2 higher) self.drone_connection.writeCharacteristic(c.handle + 2, struct.pack("<BB", 1, 0)) def disconnect(self): """ Disconnect the BLE connection. Always call this at the end of your programs to cleanly disconnect. :return: void """ self.drone_connection.disconnect() def _get_byte_str_from_uuid(self, uuid, byte_start, byte_end): """ Extract the specified byte string from the UUID btle object. This is an ugly hack but it was necessary because of the way the UUID object is represented and the documentation on the byte strings from Parrot. You give it the starting byte (counting from 1 since that is how their docs count) and the ending byte and it returns that as a string extracted from the UUID. It is assumed it happens before the first - in the UUID. :param uuid: btle UUID object :param byte_start: starting byte (counting from 1) :param byte_end: ending byte (counting from 1) :return: string with the requested bytes (to be used as a key in the lookup tables for services) """ uuid_str = format("%s" % uuid) idx_start = 2 * (byte_start - 1) idx_end = 2 * (byte_end) my_hex_str = uuid_str[idx_start:idx_end] return my_hex_str def send_turn_command(self, command_tuple, degrees): """ Build the packet for turning and send it :param command_tuple: command tuple from the parser :param degrees: how many degrees to turn :return: True if the command was sent and False otherwise """ self.characteristic_send_counter['SEND_WITH_ACK'] = (self.characteristic_send_counter['SEND_WITH_ACK'] + 1) % 256 packet = struct.pack("<BBBBHh", self.data_types['DATA_WITH_ACK'], self.characteristic_send_counter['SEND_WITH_ACK'], command_tuple[0], command_tuple[1], command_tuple[2], degrees) return self.send_command_packet_ack(packet) def send_auto_takeoff_command(self, command_tuple): """ Build the packet for auto takeoff and send it :param command_tuple: command tuple from the parser :return: True if the command was sent and False otherwise """ # print command_tuple self.characteristic_send_counter['SEND_WITH_ACK'] = ( self.characteristic_send_counter[ 'SEND_WITH_ACK'] + 1) % 256 packet = struct.pack("<BBBBHB", self.data_types['DATA_WITH_ACK'], self.characteristic_send_counter['SEND_WITH_ACK'], command_tuple[0], command_tuple[1], command_tuple[2], 1) return self.send_command_packet_ack(packet) def send_command_packet_ack(self, packet): """ Sends the actual packet on the ack channel. Internal function only. :param packet: packet constructed according to the command rules (variable size, constructed elsewhere) :return: True if the command was sent and False otherwise """ try_num = 0 self._set_command_received('SEND_WITH_ACK', False) while (try_num < self.max_packet_retries and not self.command_received['SEND_WITH_ACK']): color_print("sending command packet on try %d" % try_num, 2) self._safe_ble_write(characteristic=self.send_characteristics['SEND_WITH_ACK'], packet=packet) #self.send_characteristics['SEND_WITH_ACK'].write(packet) try_num += 1 color_print("sleeping for a notification", 2) #notify = self.drone.waitForNotifications(1.0) self.smart_sleep(0.5) #color_print("awake %s " % notify, 2) return self.command_received['SEND_WITH_ACK'] def send_pcmd_command(self, command_tuple, roll, pitch, yaw, vertical_movement, duration): """ Send the PCMD command with the specified roll, pitch, and yaw :param command_tuple: command tuple per the parser :param roll: :param pitch: :param yaw: :param vertical_movement: :param duration: """ start_time = time.time() while (time.time() - start_time < duration): self.characteristic_send_counter['SEND_NO_ACK'] = ( self.characteristic_send_counter['SEND_NO_ACK'] + 1) % 256 packet = struct.pack("<BBBBHBbbbbI", self.data_types['DATA_NO_ACK'], self.characteristic_send_counter['SEND_NO_ACK'], command_tuple[0], command_tuple[1], command_tuple[2], 1, roll, pitch, yaw, vertical_movement, 0) self._safe_ble_write(characteristic=self.send_characteristics['SEND_NO_ACK'], packet=packet) # self.send_characteristics['SEND_NO_ACK'].write(packet) notify = self.drone_connection.waitForNotifications(0.1) def send_noparam_command_packet_ack(self, command_tuple): """ Send a command on the ack channel - where all commandsandsensors except PCMD go, per http://forum.developer.parrot.com/t/ble-characteristics-of-minidrones/5912/2 the id of the last command sent (for use in ack) is the send counter (which is incremented before sending) Ensures the packet was received or sends it again up to a maximum number of times. :param command_tuple: 3 tuple of the command bytes. 0 padded for 4th byte :return: True if the command was sent and False otherwise """ self.characteristic_send_counter['SEND_WITH_ACK'] = (self.characteristic_send_counter['SEND_WITH_ACK'] + 1) % 256 packet = struct.pack("<BBBBH", self.data_types['DATA_WITH_ACK'], self.characteristic_send_counter['SEND_WITH_ACK'], command_tuple[0], command_tuple[1], command_tuple[2]) return self.send_command_packet_ack(packet) def send_enum_command_packet_ack(self, command_tuple, enum_value, usb_id=None): """ Send a command on the ack channel with enum parameters as well (most likely a flip). All commandsandsensors except PCMD go on the ack channel per http://forum.developer.parrot.com/t/ble-characteristics-of-minidrones/5912/2 the id of the last command sent (for use in ack) is the send counter (which is incremented before sending) :param command_tuple: 3 tuple of the command bytes. 0 padded for 4th byte :param enum_value: the enum index :return: nothing """ self.characteristic_send_counter['SEND_WITH_ACK'] = (self.characteristic_send_counter['SEND_WITH_ACK'] + 1) % 256 if (usb_id is None): packet = struct.pack("<BBBBBBI", self.data_types['DATA_WITH_ACK'], self.characteristic_send_counter['SEND_WITH_ACK'], command_tuple[0], command_tuple[1], command_tuple[2], 0, enum_value) else: color_print((self.data_types['DATA_WITH_ACK'], self.characteristic_send_counter['SEND_WITH_ACK'], command_tuple[0], command_tuple[1], command_tuple[2], 0, usb_id, enum_value), 1) packet = struct.pack("<BBBBHBI", self.data_types['DATA_WITH_ACK'], self.characteristic_send_counter['SEND_WITH_ACK'], command_tuple[0], command_tuple[1], command_tuple[2], usb_id, enum_value) return self.send_command_packet_ack(packet) def send_param_command_packet(self, command_tuple, param_tuple=None, param_type_tuple=0, ack=True): """ Send a command packet with parameters. Ack channel is optional for future flexibility, but currently commands are always send over the Ack channel so it defaults to True. Contributed by awm102 on github. Edited by Amy McGovern to work for BLE commands also. :param: command_tuple: the command tuple derived from command_parser.get_command_tuple() :param: param_tuple (optional): the parameter values to be sent (can be found in the XML files) :param: param_size_tuple (optional): a tuple of strings representing the data type of the parameters e.g. u8, float etc. (can be found in the XML files) :param: ack (optional): allows ack to be turned off if required :return: """ # Create lists to store the number of bytes and pack chars needed for parameters # Default them to zero so that if no params are provided the packet size is correct param_size_list = [0] * len(param_tuple) pack_char_list = [0] * len(param_tuple) if param_tuple is not None: # Fetch the parameter sizes. By looping over the param_tuple we only get the data # for requested parameters so a mismatch in params and types does not matter for i, param in enumerate(param_tuple): pack_char_list[i], param_size_list[i] = get_data_format_and_size(param, param_type_tuple[i]) if ack: ack_string = 'SEND_WITH_ACK' data_ack_string = 'DATA_WITH_ACK' else: ack_string = 'SEND_NO_ACK' data_ack_string = 'DATA_NO_ACK' # Construct the base packet self.characteristic_send_counter['SEND_WITH_ACK'] = (self.characteristic_send_counter['SEND_WITH_ACK'] + 1) % 256 # TODO: Amy changed this to match the BLE packet structure but needs to fully test it packet = struct.pack("<BBBBH", self.data_types[data_ack_string], self.characteristic_send_counter[ack_string], command_tuple[0], command_tuple[1], command_tuple[2]) if param_tuple is not None: # Add in the parameter values based on their sizes for i, param in enumerate(param_tuple): packet += struct.pack(pack_char_list[i], param) # TODO: Fix this to not go with ack always return self.send_command_packet_ack(packet) def _set_command_received(self, channel, val): """ Set the command received on the specified channel to the specified value (used for acks) :param channel: channel :param val: True or False :return: """ self.command_received[channel] = val def _safe_ble_write(self, characteristic, packet): """ Write to the specified BLE characteristic but first ensure the connection is valid :param characteristic: :param packet: :return: """ success = False while (not success): try: characteristic.write(packet) success = True except BTLEException: color_print("reconnecting to send packet", "WARN") self._reconnect(3) def ack_packet(self, buffer_id, packet_id): """ Ack the packet id specified by the argument on the ACK_COMMAND channel :param packet_id: the packet id to ack :return: nothing """ #color_print("ack last packet on the ACK_COMMAND channel", "INFO") self.characteristic_send_counter['ACK_COMMAND'] = (self.characteristic_send_counter['ACK_COMMAND'] + 1) % 256 packet = struct.pack("<BBB", self.data_types['ACK'], self.characteristic_send_counter['ACK_COMMAND'], packet_id) #color_print("sending packet %d %d %d" % (self.data_types['ACK'], self.characteristic_send_counter['ACK_COMMAND'], # packet_id), "INFO") self._safe_ble_write(characteristic=self.send_characteristics['ACK_COMMAND'], packet=packet) #self.send_characteristics['ACK_COMMAND'].write(packet) def smart_sleep(self, timeout): """ Sleeps the requested number of seconds but wakes up for notifications Note: NEVER use regular time.sleep! It is a blocking sleep and it will likely cause the BLE to disconnect due to dropped notifications. Always use smart_sleep instead! :param timeout: number of seconds to sleep :return: """ start_time = time.time() while (time.time() - start_time < timeout): try: notify = self.drone_connection.waitForNotifications(0.1) except: color_print("reconnecting to wait", "WARN") self._reconnect(3)
def scan_services(self, mac_address): p = Peripheral(mac_address) services = p.getServices() for service in services: print(service) self.savetofile(data=services)
sample_size = 128 # 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()
if len(data) >= 3: data1 = data[0] data2 = data[1] data3 = data[2] data4 = float(data2 * 256 + data1) / 100.0 print("Get Temp:" + str(data4) + "C; Humidity:" + str(data3) + "%RH") print("Start To Connect BLE") p = Peripheral('a4:c1:38:0b:99:ed') p.setDelegate(MyDelegate()) try: chList = p.getCharacteristics() seList = p.getServices() for se in seList: try: print(se.uuid) except: print("SE Error") print("---------------------------") print("Get Service1") try: se1 = p.getServiceByUUID('0000180f-0000-1000-8000-00805f9b34fb') ch1 = se1.getCharacteristics('00002a19-0000-1000-8000-00805f9b34fb') for _ch1 in ch1: print(_ch1.uuid)
class Blueterm(cmd.Cmd): intro = cfg['intro'] prompt = cfg['prompt'] class State(Enum): IDLE = 1 CONNECTED = 2 def __init__(self, device_index, scan_timeout): cmd.Cmd.__init__(self) self.device_index = device_index self.scan_timeout = scan_timeout self.ble_devs = set() self.ble_gatt = dict() self.chars = dict() self.state = self.State.IDLE # setup Bluetooth self.scanner = Scanner(device_index) self.periph = Peripheral(None, ADDR_TYPE_PUBLIC, device_index) self.periph.setDelegate(ShellEventHandler()) # Pla def precmd(self, line): return line def do_state(self, line): """Print current connection state """ if self.state == self.State.CONNECTED: print("Connected to {}".format(self.periph.addr)) else: print(self.state) def do_scan(self, line): """Scan for available BLE RIOT shells. Running this command will reset the cached list of available devices. usage: scan <scan timeout in sec> """ args = line.strip().split(' ') if len(args[0]) > 0: try: to = float(args[0]) except: print("error: unable to parse timeout (must be a number)") return else: to = self.scan_timeout print("Scanning now (blocking for {} seconds)...".format(to)) try: self.ble_devs = list(self.scanner.scan(to)) print("Scan complete:") self.do_list("") except: print("error: failure while scanning") return def do_list(self, line): """List all available BLE devices offering a RIOT shell """ if len(self.ble_devs) == 0: print("No BLE devices available") return for i, dev in enumerate(self.ble_devs): print("[{:2}] {}".format(i, dev.addr), end='') for (adtype, desc, value) in dev.getScanData(): if adtype == 9: print(" (Name: '{}'')".format(value), end='') print() def do_connect(self, line): args = line.strip().split(' ') if len(args[0]) == 0: print("usage: connect <device index>") return try: dev = self.ble_devs[int(args[0])] except: print("error: unable to find given device index") return try: self.periph.connect(dev.addr, dev.addrType) services = self.periph.getServices() for i, service in enumerate(services): print(" Service {:2} UUID: {} ({})".format( i, service.uuid, service.uuid.getCommonName())) chars = service.getCharacteristics() type(chars) for i, char in enumerate(chars): self.chars[char.getHandle()] = char print("{:5} Char {:2} UUID: {} ({})".format( char.getHandle(), i, char.uuid, char.uuid.getCommonName())) # if char.supportsRead(): # tmp = char.read() # print("Data: ", str(tmp)) self.state = self.State.CONNECTED except: print("error: while conneting something was bad") return def do_disconnect(self, line): """Close any open connection """ self.periph.disconnect() self.chars = dict() self.state = self.State.IDLE print(self.periph.addr) def do_read(self, line): try: handle = int(line.strip()) char = self.chars[handle] if not char.supportsRead(): print("error: characteristic is not readable") else: buf = char.read() print("out: {}".format(buf.decode('utf-8'))) except: print("usage: read <handle>") return def do_write(self, line): cmd = line.strip().partition(' ') if not cmd[2]: print("usage: write <handle> <data>") return try: handle = int(cmd[0]) char = self.chars[handle] char.write(cmd[2].encode('utf-8')) except: print("error: unable to find characteristic")
class BleDevice: def __init__(self): self.scanner = Scanner().withDelegate(ScanDelegate()) def Scan(self, time): self.devices = self.scanner.scan(time) def ListDevices(self, scan_data_flag): for dev in self.devices: # print("Device {} ({}){}, RSSI={} dB".format(dev.addr, dev.addrType, ' [Connectable]' if dev.connectable else '', dev.rssi)) if scan_data_flag: for (adtype, desc, value) in dev.getScanData(): print(" {} {} = {}".format(adtype, desc, value)) def FindBoogie(self): boogie = None for dev in self.devices: for (adtype, desc, value) in dev.getScanData(): if adtype == 9: if value == "Boogie": boogie = dev self.boogie = dev return boogie def ConnectToDevice(self, device): # Function returns Connected to device when device is not there # print('EXIT IS : {}'.format(device)) if device is None: print("No device detected") else: try: self.peripheral = Peripheral(device) print("Connected to device") except BTLEException: print('Connection failed') def GetServices(self): services = self.peripheral.getServices() print('Services') for service in services: print('My Service : {}'.format(service)) characteristics = service.getCharacteristics() for characteristic in characteristics: print('{}'.format(characteristic)) self.services = services def ConnectToBoogie(self): # Function find & connect to boogie. Validate connection with a known service and characteristic # Service : 1a49d922-e3a9-4b00-9253-c4c72a1bcb5d Characteristic : 7895cecb-a923-4ce4-bc58-27e8ce6e00ea Expected value : (0x32) or if char = (2) UUID_ack = False CHARACT_ack = False UUIDval = "1a49d922-e3a9-4b00-9253-c4c72a1bcb5d" CHARACTval = "Characteristic <7895cecb-a923-4ce4-bc58-27e8ce6e00ea>" boogie = ble.FindBoogie() ble.ConnectToDevice(boogie) print('Trying to verify') try: serviceUUID = self.peripheral.getServiceByUUID(UUIDval) print('My UUID service : {}'.format(serviceUUID)) UUID_ack = True except BTLEException: print('Service not found') UUID_ack = False if UUID_ack is True: Service_charact = serviceUUID.getCharacteristics() # print('Caracteristic list : {}'.format(Service_charact)) for characteristic in Service_charact: print('My CHARACT : {}'.format(characteristic)) print(characteristic) if characteristic == CHARACTval: CHARACT_ack = True print('UUIDack is : {}, CHARACTack is : {}'.format( UUID_ack, CHARACT_ack))