Loading android/pandora/mmi2grpc/mmi2grpc/gatt.py +164 −62 Original line number Diff line number Diff line Loading @@ -14,14 +14,16 @@ import re import sys import time from threading import Thread from mmi2grpc._helpers import assert_description from mmi2grpc._helpers import assert_description, match_description from mmi2grpc._proxy import ProfileProxy from pandora_experimental.gatt_grpc import GATT from pandora.host_grpc import Host from pandora.host_pb2 import PUBLIC, RANDOM from pandora.security_grpc import SecurityStorage from pandora_experimental.gatt_pb2 import ( INVALID_HANDLE, READ_NOT_PERMITTED, Loading @@ -37,6 +39,8 @@ from pandora_experimental.gatt_pb2 import ( PERMISSION_READ, PERMISSION_WRITE, PERMISSION_READ_ENCRYPTED, ENABLE_NOTIFICATION_VALUE, ENABLE_INDICATION_VALUE, ) from pandora_experimental.gatt_pb2 import GattServiceParams from pandora_experimental.gatt_pb2 import GattCharacteristicParams Loading Loading @@ -67,12 +71,15 @@ class GATTProxy(ProfileProxy): super().__init__(channel) self.gatt = GATT(channel) self.host = Host(channel) self.security_storage = SecurityStorage(channel) self.connection = None self.services = None self.characteristics = None self.descriptors = None self.read_response = None self.write_response = None self.characteristic_notification_received = False self.handles = [] self.written_over_length = False self.last_added_service = None Loading Loading @@ -109,6 +116,8 @@ class GATTProxy(ProfileProxy): self.descriptors = None self.read_response = None self.write_response = None self.characteristic_notification_received = False self.handles = [] self.written_over_length = False self.last_added_service = None return "OK" Loading Loading @@ -456,10 +465,12 @@ class GATTProxy(ProfileProxy): assert self.connection is not None handle = stringHandleToInt(re.findall("'([a0-Z9]*)'O", description)[0]) def read(): nonlocal handle self.read_response = self.gatt.ReadCharacteristicFromHandle(\ connection=self.connection, handle=handle) worker = Thread(target=read) worker.start() worker.join(timeout=30) Loading Loading @@ -747,11 +758,13 @@ class GATTProxy(ProfileProxy): matches = re.findall("'([a0-Z9]*)'O with <= '([a0-Z9]*)'", description) handle = stringHandleToInt(matches[0][0]) data = bytes([1]) * int(matches[0][1]) def write(): nonlocal handle nonlocal data self.write_response = self.gatt.WriteAttFromHandle(connection=self.connection,\ handle=handle, value=data) worker = Thread(target=write) worker.start() worker.join(timeout=30) Loading Loading @@ -966,8 +979,8 @@ class GATTProxy(ProfileProxy): connectable=True, own_address_type=PUBLIC, ) self.gatt.RegisterService( service=GattServiceParams( time.sleep(1) self.gatt.RegisterService(service=GattServiceParams( uuid=BASE_READ_WRITE_SERVICE_UUID, characteristics=[ GattCharacteristicParams( Loading Loading @@ -1051,8 +1064,7 @@ class GATTProxy(ProfileProxy): that the Implementation Under Test (IUT) can respond Read Not Permitted. """ self.last_added_service = self.gatt.RegisterService( service=GattServiceParams( self.last_added_service = self.gatt.RegisterService(service=GattServiceParams( uuid=CUSTOM_SERVICE_UUID, characteristics=[ GattCharacteristicParams( Loading Loading @@ -1095,8 +1107,7 @@ class GATTProxy(ProfileProxy): Authentication. """ self.last_added_service = self.gatt.RegisterService( service=GattServiceParams( self.last_added_service = self.gatt.RegisterService(service=GattServiceParams( uuid=CUSTOM_SERVICE_UUID, characteristics=[ GattCharacteristicParams( Loading Loading @@ -1128,8 +1139,7 @@ class GATTProxy(ProfileProxy): Implementation Under Test (IUT) can issue a Read Not Permitted Response. """ self.last_added_service = self.gatt.RegisterService( service=GattServiceParams( self.last_added_service = self.gatt.RegisterService(service=GattServiceParams( uuid=CUSTOM_SERVICE_UUID, characteristics=[ GattCharacteristicParams( Loading Loading @@ -1161,8 +1171,7 @@ class GATTProxy(ProfileProxy): Permitted. """ self.last_added_service = self.gatt.RegisterService( service=GattServiceParams( self.last_added_service = self.gatt.RegisterService(service=GattServiceParams( uuid=CUSTOM_SERVICE_UUID, characteristics=[ GattCharacteristicParams( Loading @@ -1174,6 +1183,99 @@ class GATTProxy(ProfileProxy): )) return "{:04x}".format(self.last_added_service.service.characteristics[0].handle) def MMI_IUT_SEND_INDICATION(self, description: str, **kwargs): """ Please write to client characteristic configuration handle = 'XXXX'O to enable indication to the PTS. Discover all characteristics if needed. Description: Verify that the Implementation Under Test (IUT) can receive indication sent from PTS. """ assert self.connection is not None handle = stringHandleToInt(re.findall("'([a0-Z9]*)'O", description)[0]) self.handles.append(handle) self.gatt.SetCharacteristicNotificationFromHandle(connection=self.connection,\ handle=handle, enable_value=ENABLE_INDICATION_VALUE) return "OK" @assert_description def MMI_IUT_RECEIVE_INDICATION(self, **kwargs): """ Please confirm IUT received indication from PTS. Click YES if received, otherwise NO. Description: Verify that the Implementation Under Test (IUT) can receive indication send from PTS. """ for handle in self.handles: self.characteristic_notification_received = self.gatt.WaitCharacteristicNotification( connection=self.connection, handle=handle).characteristic_notification_received assert self.characteristic_notification_received return "OK" def MMI_IUT_SEND_NOTIFICATION(self, description: str, **kwargs): """ Please write to client characteristic configuration handle = 'XXXX'O to enable notification to the PTS. Discover all characteristics if needed. Description: Verify that the Implementation Under Test (IUT) can receive notification sent from PTS. """ assert self.connection is not None handle = stringHandleToInt(re.findall("'([a0-Z9]*)'O", description)[0]) self.handles.append(handle) self.gatt.SetCharacteristicNotificationFromHandle(connection=self.connection,\ handle=handle, enable_value=ENABLE_NOTIFICATION_VALUE) return "OK" @assert_description def MMI_IUT_RECEIVE_NOTIFICATION(self, **kwargs): """ Please confirm IUT received notification from PTS. Click YES if received, otherwise NO. Description: Verify that the Implementation Under Test (IUT) can receive notification send from PTS. """ for handle in self.handles: self.characteristic_notification_received = self.gatt.WaitCharacteristicNotification( connection=self.connection, handle=handle).characteristic_notification_received assert self.characteristic_notification_received return "OK" @assert_description def MMI_IUT_DELETE_SECUIRTY_KEY(self, pts_addr: bytes, **kwargs): """ Please delete security key before connecting to PTS if IUT was bonded previously. """ self.security_storage.DeleteBond(public=pts_addr) return "OK" def MMI_IUT_WRITE_SUPPORT_FEATURE(self, description: str, **kwargs): """ Please write to client support feature handle = 'XXXX'O to enable Robust Caching. Discover all characteristics if needed. """ assert self.connection is not None handle = stringHandleToInt(re.findall("'([a0-Z9]*)'O", description)[0]) data = bytes([1]) self.write_response = self.gatt.WriteAttFromHandle(connection=self.connection,\ handle=handle, value=data) return "OK" @match_description def _mmi_2004(self, passkey: str, **kwargs): """ Please confirm that 6 digit number is matched with (?P<passkey>[0-9]+). """ return "OK" common_uuid = "0000XXXX-0000-1000-8000-00805f9b34fb" Loading android/pandora/server/configs/pts_bot_tests_config.json +12 −12 Original line number Diff line number Diff line Loading @@ -217,6 +217,8 @@ "GATT/CL/GAD/BV-04-C", "GATT/CL/GAD/BV-05-C", "GATT/CL/GAD/BV-06-C", "GATT/CL/GAI/BV-01-C", "GATT/CL/GAN/BV-01-C", "GATT/CL/GAR/BI-01-C", "GATT/CL/GAR/BI-02-C", "GATT/CL/GAR/BI-06-C", Loading @@ -229,6 +231,11 @@ "GATT/CL/GAR/BV-04-C", "GATT/CL/GAR/BV-06-C", "GATT/CL/GAR/BV-07-C", "GATT/CL/GAS/BV-01-C", "GATT/CL/GAS/BV-03-C", "GATT/CL/GAS/BV-05-C", "GATT/CL/GAT/BV-01-C", "GATT/CL/GAT/BV-02-C", "GATT/CL/GAW/BI-02-C", "GATT/CL/GAW/BI-03-C", "GATT/CL/GAW/BI-07-C", Loading @@ -252,6 +259,7 @@ "GATT/SR/GAR/BI-01-C", "GATT/SR/GAR/BI-02-C", "GATT/SR/GAR/BI-04-C", "GATT/SR/GAR/BI-05-C", "GATT/SR/GAR/BI-06-C", "GATT/SR/GAR/BI-07-C", "GATT/SR/GAR/BI-08-C", Loading @@ -259,9 +267,11 @@ "GATT/SR/GAR/BI-12-C", "GATT/SR/GAR/BI-14-C", "GATT/SR/GAR/BI-16-C", "GATT/SR/GAR/BI-17-C", "GATT/SR/GAR/BI-18-C", "GATT/SR/GAR/BI-19-C", "GATT/SR/GAR/BI-21-C", "GATT/SR/GAR/BI-22-C", "GATT/SR/GAR/BI-36-C", "GATT/SR/GAR/BI-38-C", "GATT/SR/GAR/BI-42-C", Loading @@ -270,15 +280,14 @@ "GATT/SR/GAR/BV-04-C", "GATT/SR/GAR/BV-05-C", "GATT/SR/GAR/BV-09-C", "GATT/CL/GAS/BV-05-C", "GATT/CL/GAT/BV-01-C", "GATT/CL/GAT/BV-02-C", "GATT/SR/GAW/BI-02-C", "GATT/SR/GAW/BI-03-C", "GATT/SR/GAW/BI-05-C", "GATT/SR/GAW/BI-06-C", "GATT/SR/GAW/BI-07-C", "GATT/SR/GAW/BI-08-C", "GATT/SR/GAW/BI-12-C", "GATT/SR/GAW/BI-13-C", "GATT/SR/UNS/BI-01-C", "GATT/SR/UNS/BI-02-C", "HFP/AG/ACC/BV-08-I", Loading Loading @@ -791,8 +800,6 @@ "GAP/SEC/SEM/BV-44-C", "GATT/CL/GAD/BV-07-C", "GATT/CL/GAD/BV-08-C", "GATT/CL/GAI/BV-01-C", "GATT/CL/GAN/BV-01-C", "GATT/CL/GAN/BV-02-C", "GATT/CL/GAR/BI-04-C", "GATT/CL/GAR/BI-05-C", Loading @@ -801,8 +808,6 @@ "GATT/CL/GAR/BI-16-C", "GATT/CL/GAR/BI-17-C", "GATT/CL/GAR/BV-03-C", "GATT/CL/GAS/BV-01-C", "GATT/CL/GAS/BV-03-C", "GATT/CL/GAT/BV-03-C", "GATT/CL/GAW/BI-05-C", "GATT/CL/GAW/BI-06-C", Loading @@ -815,20 +820,15 @@ "GATT/SR/GAN/BV-01-C", "GATT/SR/GAN/BV-02-C", "GATT/SR/GAN/BV-02-C_LT2", "GATT/SR/GAR/BI-05-C", "GATT/SR/GAR/BI-11-C", "GATT/SR/GAR/BI-13-C", "GATT/SR/GAR/BI-17-C", "GATT/SR/GAR/BI-22-C", "GATT/SR/GAR/BI-44-C", "GATT/SR/GAR/BV-06-C", "GATT/SR/GAR/BV-07-C", "GATT/SR/GAR/BV-08-C", "GATT/SR/GAS/BV-01-C", "GATT/SR/GAT/BV-01-C", "GATT/SR/GAW/BI-06-C", "GATT/SR/GAW/BI-09-C", "GATT/SR/GAW/BI-13-C", "GATT/SR/GAW/BI-32-C", "GATT/SR/GAW/BI-33-C", "GATT/SR/GAW/BV-01-C", Loading android/pandora/server/src/com/android/pandora/Gatt.kt +51 −0 Original line number Diff line number Diff line Loading @@ -271,6 +271,57 @@ class Gatt(private val context: Context) : GATTImplBase(), Closeable { } } override fun setCharacteristicNotificationFromHandle( request: SetCharacteristicNotificationFromHandleRequest, responseObserver: StreamObserver<SetCharacteristicNotificationFromHandleResponse> ) { grpcUnary<SetCharacteristicNotificationFromHandleResponse>(mScope, responseObserver) { Log.i(TAG, "SetCharcteristicNotificationFromHandle") val gattInstance = GattInstance.get(request.connection.address) val descriptor: BluetoothGattDescriptor? = getDescriptorWithHandle(request.handle, gattInstance) checkNotNull(descriptor) { "Found no descriptor with handle ${request.handle}" } var characteristic = descriptor.getCharacteristic() gattInstance.mGatt.setCharacteristicNotification(characteristic, true) if (request.enableValue == EnableValue.ENABLE_INDICATION_VALUE){ val valueWrote = gattInstance.writeDescriptorBlocking(descriptor, BluetoothGattDescriptor.ENABLE_INDICATION_VALUE) SetCharacteristicNotificationFromHandleResponse.newBuilder() .setHandle(valueWrote.handle) .setStatus(valueWrote.status) .build() } else { val valueWrote = gattInstance.writeDescriptorBlocking(descriptor, BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE) SetCharacteristicNotificationFromHandleResponse.newBuilder() .setHandle(valueWrote.handle) .setStatus(valueWrote.status) .build() } } } override fun waitCharacteristicNotification( request: WaitCharacteristicNotificationRequest, responseObserver: StreamObserver<WaitCharacteristicNotificationResponse> ) { grpcUnary<WaitCharacteristicNotificationResponse>(mScope, responseObserver) { val gattInstance = GattInstance.get(request.connection.address) val descriptor: BluetoothGattDescriptor? = getDescriptorWithHandle(request.handle, gattInstance) checkNotNull(descriptor) { "Found no descriptor with handle ${request.handle}" } var characteristic = descriptor.getCharacteristic() val characteristicNotificationReceived = gattInstance.waitForOnCharacteristicChanged(characteristic) WaitCharacteristicNotificationResponse.newBuilder() .setCharacteristicNotificationReceived(characteristicNotificationReceived) .build() } } /** * Discovers services, then returns characteristic with given handle. BluetoothGatt API is * package-private so we have to redefine it here. Loading android/pandora/server/src/com/android/pandora/GattInstance.kt +21 −0 Original line number Diff line number Diff line Loading @@ -42,6 +42,8 @@ class GattInstance(val mDevice: BluetoothDevice, val mTransport: Int, val mConte private var mConnectionState = MutableStateFlow(BluetoothProfile.STATE_DISCONNECTED) private var mValuesRead = MutableStateFlow(0) private var mValueWrote = MutableStateFlow(false) private var mOnCharacteristicChanged = MutableStateFlow(false) private var mCharacteristicChangedMap : MutableMap<BluetoothGattCharacteristic, Boolean> = mutableMapOf() /** * Wrapper for characteristic and descriptor reading. Uuid, startHandle and endHandle are used to Loading Loading @@ -151,6 +153,16 @@ class GattInstance(val mDevice: BluetoothDevice, val mTransport: Int, val mConte mGattInstanceValueWrote.status = AttStatusCode.forNumber(status) mValueWrote.value = true } override fun onCharacteristicChanged( bluetoothGatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, value: ByteArray ) { Log.i(TAG, "onCharacteristicChanged, characteristic: " + characteristic.getUuid().toString()) mCharacteristicChangedMap[characteristic] = true mOnCharacteristicChanged.value = true } } init { Loading Loading @@ -187,6 +199,15 @@ class GattInstance(val mDevice: BluetoothDevice, val mTransport: Int, val mConte return mServiceDiscovered.value } public suspend fun waitForOnCharacteristicChanged( characteristic: BluetoothGattCharacteristic ): Boolean{ if (mOnCharacteristicChanged.value == false) { mOnCharacteristicChanged.first { it == true } } return mCharacteristicChangedMap[characteristic] == true } public suspend fun waitForState(newState: Int) { if (mConnectionState.value != newState) { mConnectionState.first { it == newState } Loading pandora/interfaces/pandora_experimental/gatt.proto +35 −0 Original line number Diff line number Diff line Loading @@ -37,6 +37,12 @@ service GATT { // Register a GATT service rpc RegisterService(RegisterServiceRequest) returns (RegisterServiceResponse); // Set characteristic notification/indication with given client characteristic configuration descriptor handle rpc SetCharacteristicNotificationFromHandle(SetCharacteristicNotificationFromHandleRequest) returns (SetCharacteristicNotificationFromHandleResponse); // Wait for characteristic notification/indication rpc WaitCharacteristicNotification(WaitCharacteristicNotificationRequest) returns (WaitCharacteristicNotificationResponse); } enum AttStatusCode { Loading Loading @@ -65,6 +71,11 @@ enum AttPermissions { PERMISSION_READ_ENCRYPTED = 0x02; } enum EnableValue { ENABLE_NOTIFICATION_VALUE = 0; ENABLE_INDICATION_VALUE = 1; } // A message representing a GATT service. message GattService { uint32 handle = 1; Loading Loading @@ -118,6 +129,30 @@ message WriteResponse { AttStatusCode status = 2; } // Request for the `SetCharacteristicNotificationFromHandle` rpc. message SetCharacteristicNotificationFromHandleRequest { Connection connection = 1; uint32 handle = 2; EnableValue enable_value = 3; } // Response for the `SetCharacteristicNotificationFromHandle` rpc. message SetCharacteristicNotificationFromHandleResponse { uint32 handle = 1; AttStatusCode status = 2; } // Request for the `WaitCharacteristicNotification` rpc. message WaitCharacteristicNotificationRequest { Connection connection = 1; uint32 handle = 2; } // Response for the `WaitCharacteristicNotification` rpc. message WaitCharacteristicNotificationResponse { bool characteristic_notification_received = 1; } // Request for the `DiscoverServiceByUuid` rpc. message DiscoverServiceByUuidRequest { Connection connection = 1; Loading Loading
android/pandora/mmi2grpc/mmi2grpc/gatt.py +164 −62 Original line number Diff line number Diff line Loading @@ -14,14 +14,16 @@ import re import sys import time from threading import Thread from mmi2grpc._helpers import assert_description from mmi2grpc._helpers import assert_description, match_description from mmi2grpc._proxy import ProfileProxy from pandora_experimental.gatt_grpc import GATT from pandora.host_grpc import Host from pandora.host_pb2 import PUBLIC, RANDOM from pandora.security_grpc import SecurityStorage from pandora_experimental.gatt_pb2 import ( INVALID_HANDLE, READ_NOT_PERMITTED, Loading @@ -37,6 +39,8 @@ from pandora_experimental.gatt_pb2 import ( PERMISSION_READ, PERMISSION_WRITE, PERMISSION_READ_ENCRYPTED, ENABLE_NOTIFICATION_VALUE, ENABLE_INDICATION_VALUE, ) from pandora_experimental.gatt_pb2 import GattServiceParams from pandora_experimental.gatt_pb2 import GattCharacteristicParams Loading Loading @@ -67,12 +71,15 @@ class GATTProxy(ProfileProxy): super().__init__(channel) self.gatt = GATT(channel) self.host = Host(channel) self.security_storage = SecurityStorage(channel) self.connection = None self.services = None self.characteristics = None self.descriptors = None self.read_response = None self.write_response = None self.characteristic_notification_received = False self.handles = [] self.written_over_length = False self.last_added_service = None Loading Loading @@ -109,6 +116,8 @@ class GATTProxy(ProfileProxy): self.descriptors = None self.read_response = None self.write_response = None self.characteristic_notification_received = False self.handles = [] self.written_over_length = False self.last_added_service = None return "OK" Loading Loading @@ -456,10 +465,12 @@ class GATTProxy(ProfileProxy): assert self.connection is not None handle = stringHandleToInt(re.findall("'([a0-Z9]*)'O", description)[0]) def read(): nonlocal handle self.read_response = self.gatt.ReadCharacteristicFromHandle(\ connection=self.connection, handle=handle) worker = Thread(target=read) worker.start() worker.join(timeout=30) Loading Loading @@ -747,11 +758,13 @@ class GATTProxy(ProfileProxy): matches = re.findall("'([a0-Z9]*)'O with <= '([a0-Z9]*)'", description) handle = stringHandleToInt(matches[0][0]) data = bytes([1]) * int(matches[0][1]) def write(): nonlocal handle nonlocal data self.write_response = self.gatt.WriteAttFromHandle(connection=self.connection,\ handle=handle, value=data) worker = Thread(target=write) worker.start() worker.join(timeout=30) Loading Loading @@ -966,8 +979,8 @@ class GATTProxy(ProfileProxy): connectable=True, own_address_type=PUBLIC, ) self.gatt.RegisterService( service=GattServiceParams( time.sleep(1) self.gatt.RegisterService(service=GattServiceParams( uuid=BASE_READ_WRITE_SERVICE_UUID, characteristics=[ GattCharacteristicParams( Loading Loading @@ -1051,8 +1064,7 @@ class GATTProxy(ProfileProxy): that the Implementation Under Test (IUT) can respond Read Not Permitted. """ self.last_added_service = self.gatt.RegisterService( service=GattServiceParams( self.last_added_service = self.gatt.RegisterService(service=GattServiceParams( uuid=CUSTOM_SERVICE_UUID, characteristics=[ GattCharacteristicParams( Loading Loading @@ -1095,8 +1107,7 @@ class GATTProxy(ProfileProxy): Authentication. """ self.last_added_service = self.gatt.RegisterService( service=GattServiceParams( self.last_added_service = self.gatt.RegisterService(service=GattServiceParams( uuid=CUSTOM_SERVICE_UUID, characteristics=[ GattCharacteristicParams( Loading Loading @@ -1128,8 +1139,7 @@ class GATTProxy(ProfileProxy): Implementation Under Test (IUT) can issue a Read Not Permitted Response. """ self.last_added_service = self.gatt.RegisterService( service=GattServiceParams( self.last_added_service = self.gatt.RegisterService(service=GattServiceParams( uuid=CUSTOM_SERVICE_UUID, characteristics=[ GattCharacteristicParams( Loading Loading @@ -1161,8 +1171,7 @@ class GATTProxy(ProfileProxy): Permitted. """ self.last_added_service = self.gatt.RegisterService( service=GattServiceParams( self.last_added_service = self.gatt.RegisterService(service=GattServiceParams( uuid=CUSTOM_SERVICE_UUID, characteristics=[ GattCharacteristicParams( Loading @@ -1174,6 +1183,99 @@ class GATTProxy(ProfileProxy): )) return "{:04x}".format(self.last_added_service.service.characteristics[0].handle) def MMI_IUT_SEND_INDICATION(self, description: str, **kwargs): """ Please write to client characteristic configuration handle = 'XXXX'O to enable indication to the PTS. Discover all characteristics if needed. Description: Verify that the Implementation Under Test (IUT) can receive indication sent from PTS. """ assert self.connection is not None handle = stringHandleToInt(re.findall("'([a0-Z9]*)'O", description)[0]) self.handles.append(handle) self.gatt.SetCharacteristicNotificationFromHandle(connection=self.connection,\ handle=handle, enable_value=ENABLE_INDICATION_VALUE) return "OK" @assert_description def MMI_IUT_RECEIVE_INDICATION(self, **kwargs): """ Please confirm IUT received indication from PTS. Click YES if received, otherwise NO. Description: Verify that the Implementation Under Test (IUT) can receive indication send from PTS. """ for handle in self.handles: self.characteristic_notification_received = self.gatt.WaitCharacteristicNotification( connection=self.connection, handle=handle).characteristic_notification_received assert self.characteristic_notification_received return "OK" def MMI_IUT_SEND_NOTIFICATION(self, description: str, **kwargs): """ Please write to client characteristic configuration handle = 'XXXX'O to enable notification to the PTS. Discover all characteristics if needed. Description: Verify that the Implementation Under Test (IUT) can receive notification sent from PTS. """ assert self.connection is not None handle = stringHandleToInt(re.findall("'([a0-Z9]*)'O", description)[0]) self.handles.append(handle) self.gatt.SetCharacteristicNotificationFromHandle(connection=self.connection,\ handle=handle, enable_value=ENABLE_NOTIFICATION_VALUE) return "OK" @assert_description def MMI_IUT_RECEIVE_NOTIFICATION(self, **kwargs): """ Please confirm IUT received notification from PTS. Click YES if received, otherwise NO. Description: Verify that the Implementation Under Test (IUT) can receive notification send from PTS. """ for handle in self.handles: self.characteristic_notification_received = self.gatt.WaitCharacteristicNotification( connection=self.connection, handle=handle).characteristic_notification_received assert self.characteristic_notification_received return "OK" @assert_description def MMI_IUT_DELETE_SECUIRTY_KEY(self, pts_addr: bytes, **kwargs): """ Please delete security key before connecting to PTS if IUT was bonded previously. """ self.security_storage.DeleteBond(public=pts_addr) return "OK" def MMI_IUT_WRITE_SUPPORT_FEATURE(self, description: str, **kwargs): """ Please write to client support feature handle = 'XXXX'O to enable Robust Caching. Discover all characteristics if needed. """ assert self.connection is not None handle = stringHandleToInt(re.findall("'([a0-Z9]*)'O", description)[0]) data = bytes([1]) self.write_response = self.gatt.WriteAttFromHandle(connection=self.connection,\ handle=handle, value=data) return "OK" @match_description def _mmi_2004(self, passkey: str, **kwargs): """ Please confirm that 6 digit number is matched with (?P<passkey>[0-9]+). """ return "OK" common_uuid = "0000XXXX-0000-1000-8000-00805f9b34fb" Loading
android/pandora/server/configs/pts_bot_tests_config.json +12 −12 Original line number Diff line number Diff line Loading @@ -217,6 +217,8 @@ "GATT/CL/GAD/BV-04-C", "GATT/CL/GAD/BV-05-C", "GATT/CL/GAD/BV-06-C", "GATT/CL/GAI/BV-01-C", "GATT/CL/GAN/BV-01-C", "GATT/CL/GAR/BI-01-C", "GATT/CL/GAR/BI-02-C", "GATT/CL/GAR/BI-06-C", Loading @@ -229,6 +231,11 @@ "GATT/CL/GAR/BV-04-C", "GATT/CL/GAR/BV-06-C", "GATT/CL/GAR/BV-07-C", "GATT/CL/GAS/BV-01-C", "GATT/CL/GAS/BV-03-C", "GATT/CL/GAS/BV-05-C", "GATT/CL/GAT/BV-01-C", "GATT/CL/GAT/BV-02-C", "GATT/CL/GAW/BI-02-C", "GATT/CL/GAW/BI-03-C", "GATT/CL/GAW/BI-07-C", Loading @@ -252,6 +259,7 @@ "GATT/SR/GAR/BI-01-C", "GATT/SR/GAR/BI-02-C", "GATT/SR/GAR/BI-04-C", "GATT/SR/GAR/BI-05-C", "GATT/SR/GAR/BI-06-C", "GATT/SR/GAR/BI-07-C", "GATT/SR/GAR/BI-08-C", Loading @@ -259,9 +267,11 @@ "GATT/SR/GAR/BI-12-C", "GATT/SR/GAR/BI-14-C", "GATT/SR/GAR/BI-16-C", "GATT/SR/GAR/BI-17-C", "GATT/SR/GAR/BI-18-C", "GATT/SR/GAR/BI-19-C", "GATT/SR/GAR/BI-21-C", "GATT/SR/GAR/BI-22-C", "GATT/SR/GAR/BI-36-C", "GATT/SR/GAR/BI-38-C", "GATT/SR/GAR/BI-42-C", Loading @@ -270,15 +280,14 @@ "GATT/SR/GAR/BV-04-C", "GATT/SR/GAR/BV-05-C", "GATT/SR/GAR/BV-09-C", "GATT/CL/GAS/BV-05-C", "GATT/CL/GAT/BV-01-C", "GATT/CL/GAT/BV-02-C", "GATT/SR/GAW/BI-02-C", "GATT/SR/GAW/BI-03-C", "GATT/SR/GAW/BI-05-C", "GATT/SR/GAW/BI-06-C", "GATT/SR/GAW/BI-07-C", "GATT/SR/GAW/BI-08-C", "GATT/SR/GAW/BI-12-C", "GATT/SR/GAW/BI-13-C", "GATT/SR/UNS/BI-01-C", "GATT/SR/UNS/BI-02-C", "HFP/AG/ACC/BV-08-I", Loading Loading @@ -791,8 +800,6 @@ "GAP/SEC/SEM/BV-44-C", "GATT/CL/GAD/BV-07-C", "GATT/CL/GAD/BV-08-C", "GATT/CL/GAI/BV-01-C", "GATT/CL/GAN/BV-01-C", "GATT/CL/GAN/BV-02-C", "GATT/CL/GAR/BI-04-C", "GATT/CL/GAR/BI-05-C", Loading @@ -801,8 +808,6 @@ "GATT/CL/GAR/BI-16-C", "GATT/CL/GAR/BI-17-C", "GATT/CL/GAR/BV-03-C", "GATT/CL/GAS/BV-01-C", "GATT/CL/GAS/BV-03-C", "GATT/CL/GAT/BV-03-C", "GATT/CL/GAW/BI-05-C", "GATT/CL/GAW/BI-06-C", Loading @@ -815,20 +820,15 @@ "GATT/SR/GAN/BV-01-C", "GATT/SR/GAN/BV-02-C", "GATT/SR/GAN/BV-02-C_LT2", "GATT/SR/GAR/BI-05-C", "GATT/SR/GAR/BI-11-C", "GATT/SR/GAR/BI-13-C", "GATT/SR/GAR/BI-17-C", "GATT/SR/GAR/BI-22-C", "GATT/SR/GAR/BI-44-C", "GATT/SR/GAR/BV-06-C", "GATT/SR/GAR/BV-07-C", "GATT/SR/GAR/BV-08-C", "GATT/SR/GAS/BV-01-C", "GATT/SR/GAT/BV-01-C", "GATT/SR/GAW/BI-06-C", "GATT/SR/GAW/BI-09-C", "GATT/SR/GAW/BI-13-C", "GATT/SR/GAW/BI-32-C", "GATT/SR/GAW/BI-33-C", "GATT/SR/GAW/BV-01-C", Loading
android/pandora/server/src/com/android/pandora/Gatt.kt +51 −0 Original line number Diff line number Diff line Loading @@ -271,6 +271,57 @@ class Gatt(private val context: Context) : GATTImplBase(), Closeable { } } override fun setCharacteristicNotificationFromHandle( request: SetCharacteristicNotificationFromHandleRequest, responseObserver: StreamObserver<SetCharacteristicNotificationFromHandleResponse> ) { grpcUnary<SetCharacteristicNotificationFromHandleResponse>(mScope, responseObserver) { Log.i(TAG, "SetCharcteristicNotificationFromHandle") val gattInstance = GattInstance.get(request.connection.address) val descriptor: BluetoothGattDescriptor? = getDescriptorWithHandle(request.handle, gattInstance) checkNotNull(descriptor) { "Found no descriptor with handle ${request.handle}" } var characteristic = descriptor.getCharacteristic() gattInstance.mGatt.setCharacteristicNotification(characteristic, true) if (request.enableValue == EnableValue.ENABLE_INDICATION_VALUE){ val valueWrote = gattInstance.writeDescriptorBlocking(descriptor, BluetoothGattDescriptor.ENABLE_INDICATION_VALUE) SetCharacteristicNotificationFromHandleResponse.newBuilder() .setHandle(valueWrote.handle) .setStatus(valueWrote.status) .build() } else { val valueWrote = gattInstance.writeDescriptorBlocking(descriptor, BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE) SetCharacteristicNotificationFromHandleResponse.newBuilder() .setHandle(valueWrote.handle) .setStatus(valueWrote.status) .build() } } } override fun waitCharacteristicNotification( request: WaitCharacteristicNotificationRequest, responseObserver: StreamObserver<WaitCharacteristicNotificationResponse> ) { grpcUnary<WaitCharacteristicNotificationResponse>(mScope, responseObserver) { val gattInstance = GattInstance.get(request.connection.address) val descriptor: BluetoothGattDescriptor? = getDescriptorWithHandle(request.handle, gattInstance) checkNotNull(descriptor) { "Found no descriptor with handle ${request.handle}" } var characteristic = descriptor.getCharacteristic() val characteristicNotificationReceived = gattInstance.waitForOnCharacteristicChanged(characteristic) WaitCharacteristicNotificationResponse.newBuilder() .setCharacteristicNotificationReceived(characteristicNotificationReceived) .build() } } /** * Discovers services, then returns characteristic with given handle. BluetoothGatt API is * package-private so we have to redefine it here. Loading
android/pandora/server/src/com/android/pandora/GattInstance.kt +21 −0 Original line number Diff line number Diff line Loading @@ -42,6 +42,8 @@ class GattInstance(val mDevice: BluetoothDevice, val mTransport: Int, val mConte private var mConnectionState = MutableStateFlow(BluetoothProfile.STATE_DISCONNECTED) private var mValuesRead = MutableStateFlow(0) private var mValueWrote = MutableStateFlow(false) private var mOnCharacteristicChanged = MutableStateFlow(false) private var mCharacteristicChangedMap : MutableMap<BluetoothGattCharacteristic, Boolean> = mutableMapOf() /** * Wrapper for characteristic and descriptor reading. Uuid, startHandle and endHandle are used to Loading Loading @@ -151,6 +153,16 @@ class GattInstance(val mDevice: BluetoothDevice, val mTransport: Int, val mConte mGattInstanceValueWrote.status = AttStatusCode.forNumber(status) mValueWrote.value = true } override fun onCharacteristicChanged( bluetoothGatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, value: ByteArray ) { Log.i(TAG, "onCharacteristicChanged, characteristic: " + characteristic.getUuid().toString()) mCharacteristicChangedMap[characteristic] = true mOnCharacteristicChanged.value = true } } init { Loading Loading @@ -187,6 +199,15 @@ class GattInstance(val mDevice: BluetoothDevice, val mTransport: Int, val mConte return mServiceDiscovered.value } public suspend fun waitForOnCharacteristicChanged( characteristic: BluetoothGattCharacteristic ): Boolean{ if (mOnCharacteristicChanged.value == false) { mOnCharacteristicChanged.first { it == true } } return mCharacteristicChangedMap[characteristic] == true } public suspend fun waitForState(newState: Int) { if (mConnectionState.value != newState) { mConnectionState.first { it == newState } Loading
pandora/interfaces/pandora_experimental/gatt.proto +35 −0 Original line number Diff line number Diff line Loading @@ -37,6 +37,12 @@ service GATT { // Register a GATT service rpc RegisterService(RegisterServiceRequest) returns (RegisterServiceResponse); // Set characteristic notification/indication with given client characteristic configuration descriptor handle rpc SetCharacteristicNotificationFromHandle(SetCharacteristicNotificationFromHandleRequest) returns (SetCharacteristicNotificationFromHandleResponse); // Wait for characteristic notification/indication rpc WaitCharacteristicNotification(WaitCharacteristicNotificationRequest) returns (WaitCharacteristicNotificationResponse); } enum AttStatusCode { Loading Loading @@ -65,6 +71,11 @@ enum AttPermissions { PERMISSION_READ_ENCRYPTED = 0x02; } enum EnableValue { ENABLE_NOTIFICATION_VALUE = 0; ENABLE_INDICATION_VALUE = 1; } // A message representing a GATT service. message GattService { uint32 handle = 1; Loading Loading @@ -118,6 +129,30 @@ message WriteResponse { AttStatusCode status = 2; } // Request for the `SetCharacteristicNotificationFromHandle` rpc. message SetCharacteristicNotificationFromHandleRequest { Connection connection = 1; uint32 handle = 2; EnableValue enable_value = 3; } // Response for the `SetCharacteristicNotificationFromHandle` rpc. message SetCharacteristicNotificationFromHandleResponse { uint32 handle = 1; AttStatusCode status = 2; } // Request for the `WaitCharacteristicNotification` rpc. message WaitCharacteristicNotificationRequest { Connection connection = 1; uint32 handle = 2; } // Response for the `WaitCharacteristicNotification` rpc. message WaitCharacteristicNotificationResponse { bool characteristic_notification_received = 1; } // Request for the `DiscoverServiceByUuid` rpc. message DiscoverServiceByUuidRequest { Connection connection = 1; Loading