# GATT-based BLE Connection and Data Transmission
## Introduction
Generic Attribute Profile (GATT) provides profile discovery and description services for the BLE protocol. It defines how ATT attributes are organized and exchanged over a BLE connection.
A GATT server (referred to as server in this topic) is a device that stores attribute data locally and provides data access to a remote GATT client paired via BLE. A GATT client (referred to as client in this topic) is a device that accesses data on the remote GATT server via read, write, notify, or indicate operations.
## When to Use
You can use the APIs provided by the **gatt** module to:
- Connect to the server to read and write data.
- Manage services on the server and respond to the requests from the client.
## Available APIs
For details about the APIs and sample code, see [@ohos.bluetooth.ble](../../reference/apis-connectivity-kit/js-apis-bluetooth-ble.md).
The following table describes the related APIs.
| API | Description |
| ------------------------------------------ | ------------------------------------------------------------------------------------------------------ |
| connect() | Connects the client to the remote BLE device. |
| disconnect() | Disconnects the client from the remote BLE device. |
| close() | Closes this client to unregister it from the protocol stack. After this API is called, this **GattClientDevice** instance cannot be used any longer.|
| getDeviceName() | Obtains the name of the remote BLE device for the client. |
| getServices() | Obtains all services of the remote BLE device for the client. |
| readCharacteristicValue() | Reads a characteristic value of a service of the remote BLE device. |
| readDescriptorValue() | Reads the descriptor contained in a characteristic of the remote BLE device. |
| writeCharacteristicValue() | Writes a characteristic value to the remote BLE device. |
| writeDescriptorValue() | Writes binary data to a descriptor of the remote BLE device. |
| getRssiValue() | Obtains the received signal strength indication (RSSI) of the peer BLE device. This API can be used only after a connection is set up by **connect()**.|
| setBLEMtuSize() | Sets the maximum transmission unit (MTU) that can be transmitted between the client and its peer BLE device. This API can be used only after a connection is set up by **connect()**.|
| setCharacteristicChangeNotification() | Sets the characteristic change notification. The client will be notified when the characteristic value of the remote BLE device changes. |
| setCharacteristicChangeIndication() | Sets the characteristic change indication. The client will be indicated when the characteristic value of the remote BLE device changes. |
| on(type: 'BLECharacteristicChange') | Subscribes to the BLE characteristic changes. The client can receive a notification from the server only after the **setNotifyCharacteristicChanged** method is called. |
| off(type: 'BLECharacteristicChange') | Unsubscribes from the BLE characteristic changes. |
| on(type: 'BLEConnectionStateChange') | Subscribes to the BLE connection state changes for the client. |
| off(type: 'BLEConnectionStateChange') | Unsubscribes from the BLE connection state changes for the client. |
| on(type: 'BLEMtuChange') | Subscribes to MTU status changes for the client. |
| off(type: 'BLEMtuChange') | Unsubscribes from MTU status changes for the client. |
| addService() | Adds a service to this server. |
| removeService() | Removes a service from this server. |
| close() | Closes this server to unregister it from the protocol stack. After this API is called, the **GattServer** instance cannot be used any longer. |
| notifyCharacteristicChanged() | Notifies a connected client device when a characteristic value changes. |
| sendResponse() | Sends a response to a read or write request from the client. |
| on(type: 'characteristicRead') | Subscribes to the characteristic read request event for the server. |
| off(type: 'characteristicRead') | Unsubscribes from the characteristic read request event for the server. |
| on(type: 'characteristicWrite') | Subscribes to the characteristic write request event for the server. |
| off(type: 'characteristicWrite') | Unsubscribes from the characteristic write request event for the server. |
| on(type: 'descriptorRead') | Subscribes to the descriptor read request event for the server. |
| off(type: 'descriptorRead') | Unsubscribes from the descriptor read request event for the server. |
| on(type: 'descriptorWrite') | Subscribes to the descriptor write request event for the server. |
| off(type: 'descriptorWrite') | Unsubscribes from the descriptor write request event for the server. |
| on(type: 'connectionStateChange') | Subscribes to the BLE connection state changes for the server. |
| off(type: 'connectionStateChange') | Unsubscribes from the BLE connection state changes for the server. |
| on(type: 'BLEMtuChange') | Subscribes to MTU status changes for the server. |
| off(type: 'BLEMtuChange') | Unsubscribes from MTU status changes for the server. |
## How to Develop
### Reading and Writing Data on the Server
1. Import the **ble** module.
2. Enable Bluetooth.
3. Apply for the **ohos.permission.ACCESS_BLUETOOTH** permission.
4. Create a **gattClient** instance.
5. Connect to the server.
6. Read characteristics and descriptors from the server.
7. Write characteristics and descriptors to the server.
8. Disconnect from the server and destroy the **gattClient** instance.
9. Example:
```ts
import { ble } from '@kit.ConnectivityKit';
import { constant } from '@kit.ConnectivityKit';
import { AsyncCallback, BusinessError } from '@kit.BasicServicesKit';
const TAG: string = 'GattClientManager';
export class GattClientManager {
device: string | undefined = undefined;
gattClient: ble.GattClientDevice | undefined = undefined;
connectState: ble.ProfileConnectionState = constant.ProfileConnectionState.STATE_DISCONNECTED;
myServiceUuid: string = '00001810-0000-1000-8000-00805F9B34FB';
myCharacteristicUuid: string = '00001820-0000-1000-8000-00805F9B34FB';
myFirstDescriptorUuid: string = '00002902-0000-1000-8000-00805F9B34FB'; // 2902 is generally used for notification or indication.
mySecondDescriptorUuid: string = '00002903-0000-1000-8000-00805F9B34FB';
found: boolean = false;
// Construct BLEDescriptor.
private initDescriptor(des: string, value: ArrayBuffer): ble.BLEDescriptor {
let descriptor: ble.BLEDescriptor = {
serviceUuid: this.myServiceUuid,
characteristicUuid: this.myCharacteristicUuid,
descriptorUuid: des,
descriptorValue: value
};
return descriptor;
}
// Construct BLECharacteristic.
private initCharacteristic(): ble.BLECharacteristic {
let descriptors: Array = [];
let descBuffer = new ArrayBuffer(2);
let descValue = new Uint8Array(descBuffer);
descValue[0] = 11;
descValue[1] = 12;
descriptors[0] = this.initDescriptor(this.myFirstDescriptorUuid, new ArrayBuffer(2));
descriptors[1] = this.initDescriptor(this.mySecondDescriptorUuid, descBuffer);
let charBuffer = new ArrayBuffer(2);
let charValue = new Uint8Array(charBuffer);
charValue[0] = 1;
charValue[1] = 2;
let characteristic: ble.BLECharacteristic = {
serviceUuid: this.myServiceUuid,
characteristicUuid: this.myCharacteristicUuid,
characteristicValue: charBuffer,
descriptors: descriptors
};
return characteristic;
}
private logCharacteristic(char: ble.BLECharacteristic) {
let message = 'logCharacteristic uuid:' + char.characteristicUuid + '\n';
let value = new Uint8Array(char.characteristicValue);
message += 'logCharacteristic value: ';
for (let i = 0; i < char.characteristicValue.byteLength; i++) {
message += value[i] + ' ';
}
console.info(TAG, message);
}
private logDescriptor(des: ble.BLEDescriptor) {
let message = 'logDescriptor uuid:' + des.descriptorUuid + '\n';
let value = new Uint8Array(des.descriptorValue);
message += 'logDescriptor value: ';
for (let i = 0; i < des.descriptorValue.byteLength; i++) {
message += value[i] + ' ';
}
console.info(TAG, message);
}
private checkService(services: Array): boolean {
for (let i = 0; i < services.length; i++) {
if (services[i].serviceUuid != this.myServiceUuid) {
continue;
}
for (let j = 0; j < services[i].characteristics.length; j++) {
if (services[i].characteristics[j].characteristicUuid != this.myCharacteristicUuid) {
continue;
}
for (let k = 0; k < services[i].characteristics[j].descriptors.length; k++) {
if (services[i].characteristics[j].descriptors[k].descriptorUuid == this.myFirstDescriptorUuid) {
console.info(TAG, 'find expected service from server');
return true;
}
}
}
}
console.error(TAG, 'no expected service from server');
return false;
}
// 1. Subscribe to the connection status change event.
public onGattClientStateChange() {
if (!this.gattClient) {
console.error(TAG, 'no gattClient');
return;
}
try {
this.gattClient.on('BLEConnectionStateChange', (stateInfo: ble.BLEConnectionChangeState) => {
let state = '';
switch (stateInfo.state) {
case 0:
state = 'DISCONNECTED';
break;
case 1:
state = 'CONNECTING';
break;
case 2:
state = 'CONNECTED';
break;
case 3:
state = 'DISCONNECTING';
break;
default:
state = 'undefined';
break;
}
console.info(TAG, 'onGattClientStateChange: device=' + stateInfo.deviceId + ', state=' + state);
if (stateInfo.deviceId == this.device) {
this.connectState = stateInfo.state;
}
});
} catch (err) {
console.error(TAG, 'errCode: ' + (err as BusinessError).code + ', errMessage: ' + (err as BusinessError).message);
}
}
// 2. Called when the client proactively connects to the server.
public startConnect(peerDevice: string) {// The peer device is generally discovered through BLE scan.
if (this.connectState != constant.ProfileConnectionState.STATE_DISCONNECTED) {
console.error(TAG, 'startConnect failed');
return;
}
console.info(TAG, 'startConnect ' + peerDevice);
this.device = peerDevice;
// 2.1 Use device to construct gattClient. This instance is used for subsequent interactions.
this.gattClient = ble.createGattClientDevice(peerDevice);
try {
this.onGattClientStateChange(); // 2.2 Subscribe to the connection status.
this.gattClient.connect(); // 2.3 Initiate a connection.
} catch (err) {
console.error(TAG, 'errCode: ' + (err as BusinessError).code + ', errMessage: ' + (err as BusinessError).message);
}
}
// 3. After the client is connected, start service discovery.
public discoverServices() {
if (!this.gattClient) {
console.info(TAG, 'no gattClient');
return;
}
console.info(TAG, 'discoverServices');
try {
this.gattClient.getServices().then((result: Array) => {
console.info(TAG, 'getServices success: ' + JSON.stringify(result));
this.found = this.checkService(result); // Ensure that the service required exists on the server.
});
} catch (err) {
console.error(TAG, 'errCode: ' + (err as BusinessError).code + ', errMessage: ' + (err as BusinessError).message);
}
}
// 4. Read a specific characteristic after obtaining the services on the server.
public readCharacteristicValue() {
if (!this.gattClient || this.connectState != constant.ProfileConnectionState.STATE_CONNECTED) {
console.error(TAG, 'no gattClient or not connected');
return;
}
if (!this.found) {// Ensure that the server has the corresponding characteristic.
console.error(TAG, 'no characteristic from server');
return;
}
let characteristic = this.initCharacteristic();
console.info(TAG, 'readCharacteristicValue');
try {
this.gattClient.readCharacteristicValue(characteristic).then((outData: ble.BLECharacteristic) => {
this.logCharacteristic(outData);
})
} catch (err) {
console.error(TAG, 'errCode: ' + (err as BusinessError).code + ', errMessage: ' + (err as BusinessError).message);
}
}
// 5. Write a characteristic value after obtaining the services on the server.
public writeCharacteristicValue() {
if (!this.gattClient || this.connectState != constant.ProfileConnectionState.STATE_CONNECTED) {
console.error(TAG, 'no gattClient or not connected');
return;
}
if (!this.found) {// Ensure that the server has the corresponding characteristic.
console.error(TAG, 'no characteristic from server');
return;
}
let characteristic = this.initCharacteristic();
console.info(TAG, 'writeCharacteristicValue');
try {
this.gattClient.writeCharacteristicValue(characteristic, ble.GattWriteType.WRITE, (err) => {
if (err) {
console.error(TAG, 'errCode: ' + (err as BusinessError).code + ', errMessage: ' + (err as BusinessError).message);
return;
}
console.info(TAG, 'writeCharacteristicValue success');
});
} catch (err) {
console.error(TAG, 'errCode: ' + (err as BusinessError).code + ', errMessage: ' + (err as BusinessError).message);
}
}
// 6. Read a specific service descriptor after obtaining the services on the server.
public readDescriptorValue() {
if (!this.gattClient || this.connectState != constant.ProfileConnectionState.STATE_CONNECTED) {
console.error(TAG, 'no gattClient or not connected');
return;
}
if (!this.found) {// Ensure that the server has the corresponding descriptor.
console.error(TAG, 'no descriptor from server');
return;
}
let descBuffer = new ArrayBuffer(0);
let descriptor = this.initDescriptor(this.mySecondDescriptorUuid, descBuffer);
console.info(TAG, 'readDescriptorValue');
try {
this.gattClient.readDescriptorValue(descriptor).then((outData: ble.BLEDescriptor) => {
this.logDescriptor(outData);
});
} catch (err) {
console.error(TAG, 'errCode: ' + (err as BusinessError).code + ', errMessage: ' + (err as BusinessError).message);
}
}
// 7. Write a service descriptor after obtaining the services on the server.
public writeDescriptorValue() {
if (!this.gattClient || this.connectState != constant.ProfileConnectionState.STATE_CONNECTED) {
console.error(TAG, 'no gattClient or not connected');
return;
}
if (!this.found) {// Ensure that the server has the corresponding descriptor.
console.error(TAG, 'no descriptor from server');
return;
}
let descBuffer = new ArrayBuffer(2);
let descValue = new Uint8Array(descBuffer);
descValue[0] = 11;
descValue[1] = 12;
let descriptor = this.initDescriptor(this.mySecondDescriptorUuid, descBuffer);
console.info(TAG, 'writeDescriptorValue');
try {
this.gattClient.writeDescriptorValue(descriptor, (err) => {
if (err) {
console.error(TAG, 'errCode: ' + (err as BusinessError).code + ', errMessage: ' + (err as BusinessError).message);
return;
}
console.info(TAG, 'writeDescriptorValue success');
});
} catch (err) {
console.error(TAG, 'errCode: ' + (err as BusinessError).code + ', errMessage: ' + (err as BusinessError).message);
}
}
// 8. The client proactively disconnects from the server.
public stopConnect() {
if (!this.gattClient || this.connectState != constant.ProfileConnectionState.STATE_CONNECTED) {
console.error(TAG, 'no gattClient or not connected');
return;
}
console.info(TAG, 'stopConnect ' + this.device);
try {
this.gattClient.disconnect (); // 8.1 Disconnect from the server.
this.gattClient.off('BLEConnectionStateChange', (stateInfo: ble.BLEConnectionChangeState) => {
});
this.gattClient.close () // 8.2 Close this gattClient if it is no longer required.
} catch (err) {
console.error(TAG, 'errCode: ' + (err as BusinessError).code + ', errMessage: ' + (err as BusinessError).message);
}
}
}
let gattClientManager = new GattClientManager();
export default gattClientManager as GattClientManager;
```
9. For details about the error codes, see [Bluetooth Error Codes](../../reference/apis-connectivity-kit/errorcode-bluetoothManager.md).
### Managing Services on the Server and Notifying the Client
1. Import the **ble** module.
2. Enable Bluetooth.
3. Apply for the **ohos.permission.ACCESS_BLUETOOTH** permission.
4. Create a **gattServer** object.
5. Add services.
6. Notify the client after a characteristic is written to the server.
7. Remove services.
8. Close the gattServer instance.
9. Example:
```ts
import { ble } from '@kit.ConnectivityKit';
import { constant } from '@kit.ConnectivityKit';
import { AsyncCallback, BusinessError } from '@kit.BasicServicesKit';
const TAG: string = 'GattServerManager';
export class GattServerManager {
gattServer: ble.GattServer | undefined = undefined;
connectState: ble.ProfileConnectionState = constant.ProfileConnectionState.STATE_DISCONNECTED;
myServiceUuid: string = '00001810-0000-1000-8000-00805F9B34FB';
myCharacteristicUuid: string = '00001820-0000-1000-8000-00805F9B34FB';
myFirstDescriptorUuid: string = '00002902-0000-1000-8000-00805F9B34FB'; // 2902 is generally used for notification or indication.
mySecondDescriptorUuid: string = '00002903-0000-1000-8000-00805F9B34FB';
// Construct BLEDescriptor.
private initDescriptor(des: string, value: ArrayBuffer): ble.BLEDescriptor {
let descriptor: ble.BLEDescriptor = {
serviceUuid: this.myServiceUuid,
characteristicUuid: this.myCharacteristicUuid,
descriptorUuid: des,
descriptorValue: value
};
return descriptor;
}
// Construct BLECharacteristic.
private initCharacteristic(): ble.BLECharacteristic {
let descriptors: Array = [];
let descBuffer = new ArrayBuffer(2);
let descValue = new Uint8Array(descBuffer);
descValue[0] = 31;
descValue[1] = 32;
descriptors[0] = this.initDescriptor(this.myFirstDescriptorUuid, new ArrayBuffer(2));
descriptors[1] = this.initDescriptor(this.mySecondDescriptorUuid, descBuffer);
let charBuffer = new ArrayBuffer(2);
let charValue = new Uint8Array(charBuffer);
charValue[0] = 21;
charValue[1] = 22;
let characteristic: ble.BLECharacteristic = {
serviceUuid: this.myServiceUuid,
characteristicUuid: this.myCharacteristicUuid,
characteristicValue: charBuffer,
descriptors: descriptors
};
return characteristic;
}
// 1. Subscribe to the connection status change event.
public onGattServerStateChange() {
if (!this.gattServer) {
console.error(TAG, 'no gattServer');
return;
}
try {
this.gattServer.on('connectionStateChange', (stateInfo: ble.BLEConnectionChangeState) => {
let state = '';
switch (stateInfo.state) {
case 0:
state = 'DISCONNECTED';
break;
case 1:
state = 'CONNECTING';
break;
case 2:
state = 'CONNECTED';
break;
case 3:
state = 'DISCONNECTING';
break;
default:
state = 'undefined';
break;
}
console.info(TAG, 'onGattServerStateChange: device=' + stateInfo.deviceId + ', state=' + state);
});
} catch (err) {
console.error(TAG, 'errCode: ' + (err as BusinessError).code + ', errMessage: ' + (err as BusinessError).message);
}
}
// 2. Register a service with the server.
public registerServer() {
let characteristics: Array = [];
let characteristic = this.initCharacteristic();
characteristics.push(characteristic);
let gattService: ble.GattService = {
serviceUuid: this.myServiceUuid,
isPrimary: true,
characteristics: characteristics
};
console.info(TAG, 'registerServer ' + this.myServiceUuid);
try {
The this.gattServer = ble.createGattServer(); // 2.1 Create a gattServer instance, which is used in subsequent interactions.
this.onGattServerStateChange(); // 2.2 Subscribe to the connection status.
this.gattServer.addService(gattService);
} catch (err) {
console.error(TAG, 'errCode: ' + (err as BusinessError).code + ', errMessage: ' + (err as BusinessError).message);
}
}
// 3. Subscribe to the characteristic read requests from the gattClient.
public onCharacteristicRead() {
if (!this.gattServer) {
console.error(TAG, 'no gattServer');
return;
}
console.info(TAG, 'onCharacteristicRead');
try {
this.gattServer.on('characteristicRead', (charReq: ble.CharacteristicReadRequest) => {
let deviceId: string = charReq.deviceId;
let transId: number = charReq.transId;
let offset: number = charReq.offset;
console.info(TAG, 'receive characteristicRead');
let rspBuffer = new ArrayBuffer(2);
let rspValue = new Uint8Array(rspBuffer);
rspValue[0] = 21;
rspValue[1] = 22;
let serverResponse: ble.ServerResponse = {
deviceId: deviceId,
transId: transId,
status: 0, // The value 0 indicates the operation is successful.
offset: offset,
value: rspBuffer
};
try {
this.gattServer.sendResponse(serverResponse);
} catch (err) {
console.error(TAG, 'errCode: ' + (err as BusinessError).code + ', errMessage: ' + (err as BusinessError).message);
}
});
} catch (err) {
console.error(TAG, 'errCode: ' + (err as BusinessError).code + ', errMessage: ' + (err as BusinessError).message);
}
}
// 4. Subscribe to the characteristic write requests from the gattClient.
public onCharacteristicWrite() {
if (!this.gattServer) {
console.error(TAG, 'no gattServer');
return;
}
console.info(TAG, 'onCharacteristicWrite');
try {
this.gattServer.on('characteristicWrite', (charReq: ble.CharacteristicWriteRequest) => {
let deviceId: string = charReq.deviceId;
let transId: number = charReq.transId;
let offset: number = charReq.offset;
console.info(TAG, 'receive characteristicWrite: needRsp=' + charReq.needRsp);
if (!charReq.needRsp) {
return;
}
let rspBuffer = new ArrayBuffer(0);
let serverResponse: ble.ServerResponse = {
deviceId: deviceId,
transId: transId,
status: 0, // The value 0 indicates the operation is successful.
offset: offset,
value: rspBuffer
};
try {
this.gattServer.sendResponse(serverResponse);
} catch (err) {
console.error(TAG, 'errCode: ' + (err as BusinessError).code + ', errMessage: ' + (err as BusinessError).message);
}
});
} catch (err) {
console.error(TAG, 'errCode: ' + (err as BusinessError).code + ', errMessage: ' + (err as BusinessError).message);
}
}
// 5. Subscribe to the descriptor read requests from the gattClient.
public onDescriptorRead() {
if (!this.gattServer) {
console.error(TAG, 'no gattServer');
return;
}
console.info(TAG, 'onDescriptorRead');
try {
this.gattServer.on('descriptorRead', (desReq: ble.DescriptorReadRequest) => {
let deviceId: string = desReq.deviceId;
let transId: number = desReq.transId;
let offset: number = desReq.offset;
console.info(TAG, 'receive descriptorRead');
let rspBuffer = new ArrayBuffer(2);
let rspValue = new Uint8Array(rspBuffer);
rspValue[0] = 31;
rspValue[1] = 32;
let serverResponse: ble.ServerResponse = {
deviceId: deviceId,
transId: transId,
status: 0, // The value 0 indicates the operation is successful.
offset: offset,
value: rspBuffer
};
try {
this.gattServer.sendResponse(serverResponse);
} catch (err) {
console.error(TAG, 'errCode: ' + (err as BusinessError).code + ', errMessage: ' + (err as BusinessError).message);
}
});
} catch (err) {
console.error(TAG, 'errCode: ' + (err as BusinessError).code + ', errMessage: ' + (err as BusinessError).message);
}
}
// 6. Subscribe to the descriptor write requests from the gattClient.
public onDescriptorWrite() {
if (!this.gattServer) {
console.error(TAG, 'no gattServer');
return;
}
console.info(TAG, 'onDescriptorWrite');
try {
this.gattServer.on('descriptorWrite', (desReq: ble.DescriptorWriteRequest) => {
let deviceId: string = desReq.deviceId;
let transId: number = desReq.transId;
let offset: number = desReq.offset;
console.info(TAG, 'receive descriptorWrite: needRsp=' + desReq.needRsp);
if (!desReq.needRsp) {
return;
}
let rspBuffer = new ArrayBuffer(0);
let serverResponse: ble.ServerResponse = {
deviceId: deviceId,
transId: transId,
status: 0, // The value 0 indicates the operation is successful.
offset: offset,
value: rspBuffer
};
try {
this.gattServer.sendResponse(serverResponse);
} catch (err) {
console.error(TAG, 'errCode: ' + (err as BusinessError).code + ', errMessage: ' + (err as BusinessError).message);
}
});
} catch (err) {
console.error(TAG, 'errCode: ' + (err as BusinessError).code + ', errMessage: ' + (err as BusinessError).message);
}
}
// 7. Unregister the service that is not required from the server.
public unRegisterServer() {
if (!this.gattServer) {
console.error(TAG, 'no gattServer');
return;
}
console.info(TAG, 'unRegisterServer ' + this.myServiceUuid);
try {
this.gattServer.removeService (this.myServiceUuid); // 7.1 Remove the service.
this.gattServer.off('connectionStateChange', (stateInfo: ble.BLEConnectionChangeState) => { // 7.2 Unsubscribe from the connection state changes.
});
this.gattServer.close() // 7.3 Close the gattServer if it is no longer required.
} catch (err) {
console.error(TAG, 'errCode: ' + (err as BusinessError).code + ', errMessage: ' + (err as BusinessError).message);
}
}
}
let gattServerManager = new GattServerManager();
export default gattServerManager as GattServerManager;
```
8. For details about the error codes, see [Bluetooth Error Codes](../../reference/apis-connectivity-kit/errorcode-bluetoothManager.md).