def discoverCharacteristics(self, uuid=None): """Find characteristics under the service Notes: if uuid is None, all characteristics are discovered. Any previously discovered characteristics are invalidated. Args: uuid : a specific uuid to discover Returns: A list of discovered characteristics """ if uuid and not isinstance(uuid, UUID): uuid = UUID(uuid) allCharacs, allCharacHandles = self.__discover_all_characteristics() if uuid is None: self._characteristicHandles = allCharacHandles self.characteristics = allCharacs return allCharacs else: characs = [x for x in allCharacs if x.uuid == uuid] for charac in characs: if charac._handleNo not in self._characteristicHandles: self._characteristicHandles[charac._handleNo] = charac self.characteristics.append(charac) self.characteristics.sort(key=lambda x: x._handleNo) return characs
def discoverServices(self, uuid=None, startHandle=1, endHandle=0xFFFF): """Find services on the server. Notes: if uuid is None, all services are discovered. Any previously discovered services are invalidated. Args: uuid : type of service to discover startHandle : beginning of the handle range endHandle : end of the handle range Returns: A list of services.""" if startHandle == 0: raise ClientError("invalid start handle") if startHandle > endHandle: raise ClientError("invalid handle range") if uuid and not isinstance(uuid, UUID): uuid = UUID(uuid) if uuid is None: return self.__discover_all_services(startHandle=startHandle, endHandle=endHandle) else: return self.__discover_services_by_uuid(uuid, startHandle=startHandle, endHandle=endHandle)
def __discover_services(self, startHandle, endHandle): primSvcUuid = UUID(gatt.PRIM_SVC_UUID) services = [] serviceHandles = {} currHandle = startHandle while True: req = att_pdu.new_read_by_group_req(currHandle, endHandle, primSvcUuid) resp = self._new_transaction(req) if not resp: raise ClientError("transaction failed") if resp[0] == att.OP_READ_BY_GROUP_RESP: attDataLen = resp[1] idx = 2 endGroup = currHandle # not needed while idx < len(resp) and idx + attDataLen <= len(resp): handleNo = att_pdu.unpack_handle(resp, idx) endGroup = att_pdu.unpack_handle(resp, idx + 2) uuid = UUID(resp[idx+4:idx+attDataLen], reverse=True) service = _ClientService(self, uuid, handleNo, endGroup) services.append(service) serviceHandles[handleNo] = service idx += attDataLen currHandle = endGroup + 1 if currHandle >= endHandle: break elif (resp[0] == att.OP_ERROR and len(resp) == att_pdu.ERROR_PDU_LEN and resp[1] == att.OP_READ_BY_GROUP_REQ and resp[4] == att.ECODE_ATTR_NOT_FOUND): break elif (resp[0] == att.OP_ERROR and len(resp) == att_pdu.ERROR_PDU_LEN and resp[1] == att.OP_READ_BY_GROUP_REQ): raise ClientError("error - %s" % att.ecodeLookup(resp[4])) else: raise ClientError("unexpected - %s" % att.opcodeLookup(resp[0])) return services, serviceHandles
def __discover_services_by_uuid(self, uuid, startHandle, endHandle): primSvcUuid = UUID(gatt.PRIM_SVC_UUID) services = [] newServices = [] serviceHandles = {} currHandle = startHandle while True: req = att_pdu.new_find_by_type_value_req(currHandle, endHandle, primSvcUuid, uuid.raw[::-1]) resp = self._new_transaction(req) if not resp: raise ClientError("transaction failed") if resp[0] == att.OP_FIND_BY_TYPE_REQ: idx = 2 endGroup = currHandle # not needed while idx < len(resp) and idx + 4 <= len(resp): handleNo = att_pdu.unpack_handle(resp, idx) endGroup = att_pdu.unpack_handle(resp, idx + 2) if handleNo in self._serviceHandles: service = self._serviceHandles[handleNo] else: service = _ClientService(self, uuid, handleNo, endGroup) serviceHandles[handleNo] = service newServices.append(service) services.append(service) idx += 4 currHandle = endGroup + 1 if currHandle >= endHandle: break elif (resp[0] == att.OP_ERROR and len(resp) == att_pdu.ERROR_PDU_LEN and resp[1] == att.OP_FIND_BY_TYPE_REQ and resp[4] == att.ECODE_ATTR_NOT_FOUND): break elif (resp[0] == att.OP_ERROR and len(resp) == att_pdu.ERROR_PDU_LEN and resp[1] == att.OP_FIND_BY_TYPE_REQ): raise ClientError("error - %s" % att.ecodeLookup(resp[4])) else: raise ClientError("unexpected - %s" % att.opcodeLookup(resp[0])) self.services.extend(newServices) self.services.sort(key=lambda x: x._handleNo) self._serviceHandles.update(serviceHandles) return services
def addService(self, uuid): """Add a service of type uuid. Returns: The newly added service. """ if not isinstance(uuid, UUID): uuid = UUID(uuid) service = _ServerService(self, uuid) self.services.append(service) return service
def addDescriptor(self, uuid, value=None): """Add a descriptor to the last characteristic Args: uuid : the type of the descriptor value : default value for descriptor Returns: The newly added characteristic """ if not isinstance(uuid, UUID): uuid = UUID(uuid) return _ServerDescriptor(self, uuid, value)
def __init__(self, server, uuid): assert isinstance(server, GattServer) assert isinstance(uuid, UUID) # Public members self.uuid = uuid self.characteristics = [] # Protected members self._handle = _ServerHandle(server, self, UUID(gatt.PRIM_SVC_UUID)) # reversed by convention self._handle._set_read_value(uuid.raw[::-1])
def addCharacteristic(self, uuid, value=None, allowNotify=False, allowIndicate=False): """Add a characteristic to the last service. Notes: permissions are learned by adding callbacks and values to the characteristic. Args: uuid : type of the characteristic value : static value for the characteristic allowNotify : allow notifications allowIndicate : allow indications Returns: The newly added characteristic """ if not isinstance(uuid, UUID): uuid = UUID(uuid) return _ServerCharacteristic(self, uuid, value, allowNotify, allowIndicate)
def __discover_all_characteristics(self): startHandle = self._handleNo + 1 endHandle = self._endGroup if startHandle > endHandle: return [], {} characUuid = UUID(gatt.CHARAC_UUID) characs = [] characHandles = {} currHandle = startHandle while True: req = att_pdu.new_read_by_type_req(currHandle, endHandle, characUuid) resp = self.client._new_transaction(req) if not resp: raise ClientError("transaction failed") if resp[0] == att.OP_READ_BY_TYPE_RESP: attDataLen = resp[1] idx = 2 maxHandleNo = currHandle # not needed while idx < len(resp) and idx + attDataLen <= len(resp): handleNo = att_pdu.unpack_handle(resp, idx) properties = resp[idx+2] valHandleNo = att_pdu.unpack_handle(resp, idx + 3) uuid = UUID(resp[idx+5:idx+attDataLen], reverse=True) charac = _ClientCharacteristic(self.client, self, uuid, handleNo, properties, valHandleNo) characs.append(charac) characHandles[handleNo] = characs idx += attDataLen maxHandleNo = valHandleNo currHandle = maxHandleNo + 1 if currHandle >= endHandle: break elif (resp[0] == att.OP_ERROR and len(resp) == att_pdu.ERROR_PDU_LEN and resp[1] == att.OP_READ_BY_TYPE_REQ and resp[4] == att.ECODE_ATTR_NOT_FOUND): break elif (resp[0] == att.OP_ERROR and len(resp) == att_pdu.ERROR_PDU_LEN and resp[1] == att.OP_READ_BY_TYPE_REQ and resp[4] == att.ECODE_READ_NOT_PERM): currHandle = currHandle + 1 if currHandle >= endHandle: break elif (resp[0] == att.OP_ERROR and len(resp) == att_pdu.ERROR_PDU_LEN and resp[1] == att.OP_READ_BY_TYPE_REQ): raise ClientError("error - %s" % att.ecodeLookup(resp[4])) else: raise ClientError("unexpected - %s" % att.opcodeLookup(resp[0])) for i, charac in enumerate(characs): if i + 1 < len(characs): charac._set_end_group(characs[i+1]._handleNo-1) else: charac._set_end_group(self._endGroup) return characs, characHandles
def __init__(self, server, uuid, staticValue=None, allowNotify=False, allowIndicate=False): assert isinstance(server, GattServer) assert isinstance(uuid, UUID) # Public members self.server = server self.uuid = uuid self.descriptors = [] self.service = server.services[-1] self.service._add_characteristic(self) # Protected members self._properties = 0 if staticValue: self._properties |= gatt.CHARAC_PROP_READ if allowNotify: self._properties |= gatt.CHARAC_PROP_NOTIFY if allowIndicate: self._properties |= gatt.CHARAC_PROP_IND self._has_subscriber = False self._subscribe_callback = lambda: None self._unsubscribe_callback = lambda: None self._handle = _ServerHandle(server, self, UUID(gatt.CHARAC_UUID)) self._valHandle = _ServerHandle(server, self, self.uuid) if allowNotify or allowIndicate: self._cccd = _ServerDescriptor(server, UUID(gatt.CLIENT_CHARAC_CFG_UUID)) def cccd_cb(value): """Special callback for CCCD""" assert isinstance(value, bytearray) if len(value) != 2: raise ServerError("invalid cccd pdu") else: if value[0] == 1 or value[1] == 1: if not self._has_subscriber: self._subscribe_callback(value) self._has_subscriber = True else: if self._has_subscriber: self._unsubscribe_callback() self._has_subscriber = False self._cccd.write(value) self._cccd._handle._set_write_callback(cccd_cb) self._cccd.write(bytearray([0, 0])) else: self._cccd = None if staticValue: self._valHandle._set_read_value(staticValue) def read_cb(): """Properties may change.""" handleValue = bytearray([self._properties]) handleValue += self._valHandle._get_handle_as_bytearray() handleValue += self.uuid.raw[::-1] return handleValue self._handle._set_read_callback(read_cb)
def discoverDescriptors(self): """Return a list of descriptors""" assert self._endGroup is not None startHandle = self._handleNo + 1 endHandle = self._endGroup if startHandle > endHandle: return [] descriptors = [] cccdUuid = UUID(gatt.CLIENT_CHARAC_CFG_UUID) cccd = None userDescUuid = UUID(gatt.CHARAC_USER_DESC_UUID) userDesc = None currHandle = startHandle while True: req = att_pdu.new_find_info_req(currHandle, endHandle) resp = self.client._new_transaction(req) if not resp: raise ClientError("transaction failed") if resp[0] == att.OP_FIND_INFO_RESP: attDataLen = 4 if resp[1] == att.FIND_INFO_RESP_FMT_16BIT \ else 18 idx = 2 handleNo = currHandle # not needed while idx < len(resp) and idx + attDataLen <= len(resp): handleNo = att_pdu.unpack_handle(resp, idx) uuid = UUID(resp[idx+2:idx+attDataLen], reverse=True) if handleNo == self._valHandleNo: idx += attDataLen continue if uuid == userDescUuid: descriptor = _ClientDescriptor(self.client, self, uuid, handleNo, cacheable=True) userDesc = descriptor else: descriptor = _ClientDescriptor(self.client, self, uuid, handleNo) if uuid == cccdUuid: # hide the cccd from users cccd = descriptor else: descriptors.append(descriptor) idx += attDataLen currHandle = handleNo + 1 if currHandle >= endHandle: break elif (resp[0] == att.OP_ERROR and len(resp) == att_pdu.ERROR_PDU_LEN and resp[1] == att.OP_FIND_INFO_REQ and resp[4] == att.ECODE_ATTR_NOT_FOUND): break elif (resp[0] == att.OP_ERROR and len(resp) == att_pdu.ERROR_PDU_LEN and resp[1] == att.OP_FIND_INFO_REQ): raise ClientError("error - %s" % att.ecodeLookup(resp[4])) else: raise ClientError("unexpected - %s" % att.opcodeLookup(resp[0])) self.descriptors = descriptors self._cccd = cccd self._user_desc = userDesc return descriptors
def parse_read_by_group_req(pdu): return pdu[0], unpack_handle(pdu, 1), unpack_handle(pdu, 3), \ UUID(pdu[5:], reverse=True)
def parse_find_by_type_req(pdu): return pdu[0], unpack_handle(pdu, 1), unpack_handle(pdu, 3), \ UUID(pdu[5:7], reverse=True), pdu[7:]