blob: 1725e0eb481ed6b34efbc03d87964db6ec48ced2 [file] [log] [blame]
# Copyright 2019 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Server side bluetooth GATT client helper class for testing"""
import base64
import json
class GATT_ClientFacade(object):
"""A wrapper for getting GATT application from GATT server"""
def __init__(self, bluetooth_facade):
"""Initialize a GATT_ClientFacade
@param bluetooth_facade: facade to communicate with adapter in DUT
"""
self.bluetooth_facade = bluetooth_facade
def browse(self, address):
"""Browse the application on GATT server
@param address: a string of MAC address of the GATT server device
@return: GATT_Application object
"""
attr_map_json = json.loads(self.bluetooth_facade.\
get_gatt_attributes_map(address))
application = GATT_Application()
application.browse(attr_map_json, self.bluetooth_facade)
return application
class GATT_Application(object):
"""A GATT client application class"""
def __init__(self):
"""Initialize a GATT Application"""
self.services = dict()
def browse(self, attr_map_json, bluetooth_facade):
"""Browse the application on GATT server
@param attr_map_json: a json object returned by
bluetooth_device_xmlrpc_server
@bluetooth_facade: facade to communicate with adapter in DUT
"""
servs_json = attr_map_json['services']
for uuid in servs_json:
path = servs_json[uuid]['path']
service_obj = GATT_Service(uuid, path, bluetooth_facade)
service_obj.read_properties()
self.add_service(service_obj)
chrcs_json = servs_json[uuid]['characteristics']
for uuid in chrcs_json:
path = chrcs_json[uuid]['path']
chrc_obj = GATT_Characteristic(uuid, path, bluetooth_facade)
chrc_obj.read_properties()
service_obj.add_characteristic(chrc_obj)
descs_json = chrcs_json[uuid]['descriptors']
for uuid in descs_json:
path = descs_json[uuid]['path']
desc_obj = GATT_Descriptor(uuid, path, bluetooth_facade)
desc_obj.read_properties()
chrc_obj.add_descriptor(desc_obj)
def find_by_uuid(self, uuid):
"""Find attribute under this application by specifying UUID
@param uuid: string of UUID
@return: Attribute object if found,
none otherwise
"""
for serv_uuid, serv in self.services.items():
found = serv.find_by_uuid(uuid)
if found:
return found
return None
def add_service(self, service):
"""Add a service into this application"""
self.services[service.uuid] = service
@staticmethod
def diff(appl_a, appl_b):
"""Compare two Applications, and return their difference
@param appl_a: the first application which is going to be compared
@param appl_b: the second application which is going to be compared
@return: a list of string, each describes one difference
"""
result = []
uuids_a = set(appl_a.services.keys())
uuids_b = set(appl_b.services.keys())
uuids = uuids_a.union(uuids_b)
for uuid in uuids:
serv_a = appl_a.services.get(uuid, None)
serv_b = appl_b.services.get(uuid, None)
if not serv_a or not serv_b:
result.append("Service %s is not included in both Applications:"
"%s vs %s" % (uuid, bool(serv_a), bool(serv_b)))
else:
result.extend(GATT_Service.diff(serv_a, serv_b))
return result
class GATT_Service(object):
"""GATT client service class"""
PROPERTIES = ['UUID', 'Primary', 'Device', 'Includes']
def __init__(self, uuid, object_path, bluetooth_facade):
"""Initialize a GATT service object
@param uuid: string of UUID
@param object_path: object path of this service
@param bluetooth_facade: facade to communicate with adapter in DUT
"""
self.uuid = uuid
self.object_path = object_path
self.bluetooth_facade = bluetooth_facade
self.properties = dict()
self.characteristics = dict()
def add_characteristic(self, chrc_obj):
"""Add a characteristic attribute into service
@param chrc_obj: a characteristic object
"""
self.characteristics[chrc_obj.uuid] = chrc_obj
def read_properties(self):
"""Read all properties in this service"""
for prop_name in self.PROPERTIES:
self.properties[prop_name] = self.read_property(prop_name)
return self.properties
def read_property(self, property_name):
"""Read a property in this service
@param property_name: string of the name of the property
@return: the value of the property
"""
return self.bluetooth_facade.get_gatt_service_property(
self.object_path, property_name)
def find_by_uuid(self, uuid):
"""Find attribute under this service by specifying UUID
@param uuid: string of UUID
@return: Attribute object if found,
none otherwise
"""
if self.uuid == uuid:
return self
for chrc_uuid, chrc in self.characteristics.items():
found = chrc.find_by_uuid(uuid)
if found:
return found
return None
@staticmethod
def diff(serv_a, serv_b):
"""Compare two Services, and return their difference
@param serv_a: the first service which is going to be compared
@param serv_b: the second service which is going to be compared
@return: a list of string, each describes one difference
"""
result = []
for prop_name in GATT_Service.PROPERTIES:
if serv_a.properties[prop_name] != serv_b.properties[prop_name]:
result.append("Service %s is different in %s: %s vs %s" %
(serv_a.uuid, prop_name,
serv_a.properties[prop_name],
serv_b.properties[prop_name]))
uuids_a = set(serv_a.characteristics.keys())
uuids_b = set(serv_b.characteristics.keys())
uuids = uuids_a.union(uuids_b)
for uuid in uuids:
chrc_a = serv_a.characteristics.get(uuid, None)
chrc_b = serv_b.characteristics.get(uuid, None)
if not chrc_a or not chrc_b:
result.append("Characteristic %s is not included in both "
"Services: %s vs %s" % (uuid, bool(chrc_a),
bool(chrc_b)))
else:
result.extend(GATT_Characteristic.diff(chrc_a, chrc_b))
return result
class GATT_Characteristic(object):
"""GATT client characteristic class"""
PROPERTIES = ['UUID', 'Service', 'Value', 'Notifying', 'Flags']
def __init__(self, uuid, object_path, bluetooth_facade):
"""Initialize a GATT characteristic object
@param uuid: string of UUID
@param object_path: object path of this characteristic
@param bluetooth_facade: facade to communicate with adapter in DUT
"""
self.uuid = uuid
self.object_path = object_path
self.bluetooth_facade = bluetooth_facade
self.properties = dict()
self.descriptors = dict()
def add_descriptor(self, desc_obj):
"""Add a characteristic attribute into service
@param desc_obj: a descriptor object
"""
self.descriptors[desc_obj.uuid] = desc_obj
def read_properties(self):
"""Read all properties in this characteristic"""
for prop_name in self.PROPERTIES:
self.properties[prop_name] = self.read_property(prop_name)
return self.properties
def read_property(self, property_name):
"""Read a property in this characteristic
@param property_name: string of the name of the property
@return: the value of the property
"""
return self.bluetooth_facade.get_gatt_characteristic_property(
self.object_path, property_name)
def find_by_uuid(self, uuid):
"""Find attribute under this characteristic by specifying UUID
@param uuid: string of UUID
@return: Attribute object if found,
none otherwise
"""
if self.uuid == uuid:
return self
for desc_uuid, desc in self.descriptors.items():
if desc_uuid == uuid:
return desc
return None
def read_value(self):
"""Perform ReadValue in DUT and store it in property 'Value'
@return: bytearray of the value
"""
value = self.bluetooth_facade.gatt_characteristic_read_value(
self.uuid, self.object_path)
self.properties['Value'] = bytearray(base64.standard_b64decode(value))
return self.properties['Value']
@staticmethod
def diff(chrc_a, chrc_b):
"""Compare two Characteristics, and return their difference
@param serv_a: the first service which is going to be compared
@param serv_b: the second service which is going to be compared
@return: a list of string, each describes one difference
"""
result = []
for prop_name in GATT_Characteristic.PROPERTIES:
if chrc_a.properties[prop_name] != chrc_b.properties[prop_name]:
result.append("Characteristic %s is different in %s: %s vs %s"
% (chrc_a.uuid, prop_name,
chrc_a.properties[prop_name],
chrc_b.properties[prop_name]))
uuids_a = set(chrc_a.descriptors.keys())
uuids_b = set(chrc_b.descriptors.keys())
uuids = uuids_a.union(uuids_b)
for uuid in uuids:
desc_a = chrc_a.descriptors.get(uuid, None)
desc_b = chrc_b.descriptors.get(uuid, None)
if not desc_a or not desc_b:
result.append("Descriptor %s is not included in both"
"Characteristic: %s vs %s" % (uuid, bool(desc_a),
bool(desc_b)))
else:
result.extend(GATT_Descriptor.diff(desc_a, desc_b))
return result
class GATT_Descriptor(object):
"""GATT client descriptor class"""
PROPERTIES = ['UUID', 'Characteristic', 'Value', 'Flags']
def __init__(self, uuid, object_path, bluetooth_facade):
"""Initialize a GATT descriptor object
@param uuid: string of UUID
@param object_path: object path of this descriptor
@param bluetooth_facade: facade to communicate with adapter in DUT
"""
self.uuid = uuid
self.object_path = object_path
self.bluetooth_facade = bluetooth_facade
self.properties = dict()
def read_properties(self):
"""Read all properties in this characteristic"""
for prop_name in self.PROPERTIES:
self.properties[prop_name] = self.read_property(prop_name)
return self.properties
def read_property(self, property_name):
"""Read a property in this characteristic
@param property_name: string of the name of the property
@return: the value of the property
"""
return self.bluetooth_facade.get_gatt_descriptor_property(
self.object_path, property_name)
def read_value(self):
"""Perform ReadValue in DUT and store it in property 'Value'
@return: bytearray of the value
"""
value = self.bluetooth_facade.gatt_descriptor_read_value(
self.uuid, self.object_path)
self.properties['Value'] = bytearray(base64.standard_b64decode(value))
return self.properties['Value']
@staticmethod
def diff(desc_a, desc_b):
"""Compare two Descriptors, and return their difference
@param serv_a: the first service which is going to be compared
@param serv_b: the second service which is going to be compared
@return: a list of string, each describes one difference
"""
result = []
for prop_name in desc_a.properties.keys():
if desc_a.properties[prop_name] != desc_b.properties[prop_name]:
result.append("Descriptor %s is different in %s: %s vs %s" %
(desc_a.uuid, prop_name,
desc_a.properties[prop_name],
desc_b.properties[prop_name]))
return result
def UUID_Short2Full(uuid):
"""Transform 2 bytes uuid string to 16 bytes
@param uuid: 2 bytes shortened UUID string in hex
@return: full uuid string
"""
uuid_template = '0000%s-0000-1000-8000-00805f9b34fb'
return uuid_template % uuid
class GATT_HIDApplication(GATT_Application):
"""Default HID Application on Raspberry Pi GATT server
"""
BatteryServiceUUID = UUID_Short2Full('180f')
BatteryLevelUUID = UUID_Short2Full('2a19')
CliChrcConfigUUID = UUID_Short2Full('2902')
GenericAttributeProfileUUID = UUID_Short2Full('1801')
ServiceChangedUUID = UUID_Short2Full('2a05')
DeviceInfoUUID = UUID_Short2Full('180a')
ManufacturerNameStrUUID = UUID_Short2Full('2a29')
PnPIDUUID = UUID_Short2Full('2a50')
GenericAccessProfileUUID = UUID_Short2Full('1800')
DeviceNameUUID = UUID_Short2Full('2a00')
AppearanceUUID = UUID_Short2Full('2a01')
def __init__(self):
"""
"""
GATT_Application.__init__(self)
BatteryService = GATT_Service(self.BatteryServiceUUID, None, None)
BatteryService.properties = {
'UUID': BatteryService.uuid,
'Primary': True,
'Device': None,
'Includes': None
}
self.add_service(BatteryService)
BatteryLevel = GATT_Characteristic(self.BatteryLevelUUID, None, None)
BatteryLevel.properties = {
'UUID': BatteryLevel.uuid,
'Service': None,
'Value': [],
'Notifying': False,
'Flags': ['read', 'notify']
}
BatteryService.add_characteristic(BatteryLevel)
CliChrcConfig = GATT_Descriptor(self.CliChrcConfigUUID, None, None)
CliChrcConfig.properties = {
'UUID': CliChrcConfig.uuid,
'Characteristic': None,
'Value': [],
'Flags': None
}
BatteryLevel.add_descriptor(CliChrcConfig)
GenericAttributeProfile = GATT_Service(self.GenericAttributeProfileUUID,
None, None)
GenericAttributeProfile.properties = {
'UUID': GenericAttributeProfile.uuid,
'Primary': True,
'Device': None,
'Includes': None
}
self.add_service(GenericAttributeProfile)
ServiceChanged = GATT_Characteristic(self.ServiceChangedUUID, None,
None)
ServiceChanged.properties = {
'UUID': ServiceChanged.uuid,
'Service': None,
'Value': [],
'Notifying': False,
'Flags': ['indicate']
}
GenericAttributeProfile.add_characteristic(ServiceChanged)
CliChrcConfig = GATT_Descriptor(self.CliChrcConfigUUID, None, None)
CliChrcConfig.properties = {
'UUID': CliChrcConfig.uuid,
'Characteristic': None,
'Value': [],
'Flags': None
}
ServiceChanged.add_descriptor(CliChrcConfig)
DeviceInfo = GATT_Service(self.DeviceInfoUUID, None, None)
DeviceInfo.properties = {
'UUID': DeviceInfo.uuid,
'Primary': True,
'Device': None,
'Includes': None
}
self.add_service(DeviceInfo)
ManufacturerNameStr = GATT_Characteristic(self.ManufacturerNameStrUUID,
None, None)
ManufacturerNameStr.properties = {
'UUID': ManufacturerNameStr.uuid,
'Service': None,
'Value': [],
'Notifying': None,
'Flags': ['read']
}
DeviceInfo.add_characteristic(ManufacturerNameStr)
PnPID = GATT_Characteristic(self.PnPIDUUID, None, None)
PnPID.properties = {
'UUID': PnPID.uuid,
'Service': None,
'Value': [],
'Notifying': None,
'Flags': ['read']
}
DeviceInfo.add_characteristic(PnPID)
GenericAccessProfile = GATT_Service(self.GenericAccessProfileUUID,
None, None)
GenericAccessProfile.properties = {
'UUID': GenericAccessProfile.uuid,
'Primary': True,
'Device': None,
'Includes': None
}
self.add_service(GenericAccessProfile)
DeviceName = GATT_Characteristic(self.DeviceNameUUID, None, None)
DeviceName.properties = {
'UUID': DeviceName.uuid,
'Service': None,
'Value': [],
'Notifying': None,
'Flags': ['read']
}
GenericAccessProfile.add_characteristic(DeviceName)
Appearance = GATT_Characteristic(self.AppearanceUUID, None, None)
Appearance.properties = {
'UUID': Appearance.uuid,
'Service': None,
'Value': [],
'Notifying': None,
'Flags': ['read']
}
GenericAccessProfile.add_characteristic(Appearance)