• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (c) 2025 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 *     http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16// [Start generic_attribute]
17import { ble } from '@kit.ConnectivityKit';
18import { constant } from '@kit.ConnectivityKit';
19import { BusinessError } from '@kit.BasicServicesKit';
20import { ToastReport } from '../common/ToastReport';
21import util from '@ohos.util';
22
23const TAG: string = 'GattClientManager';
24// [StartExclude generic_attribute]
25let encoder = new util.TextEncoder();
26// [EndExclude generic_attribute]
27
28export class GattData {
29  public serviceUuid: string = '00001810-0000-1000-8000-00805F9B34FB';
30  public characteristicUuid: string = '00001820-0000-1000-8000-00805F9B34FB';
31  public descriptorUuid: string = '00002902-0000-1000-8000-00805F9B34FB';
32  public characteristicValue: string = '';
33  public descriptorValue: string = '';
34}
35
36export class GattClientManager {
37  public device: string | undefined = undefined;
38  public gattClient: ble.GattClientDevice | undefined = undefined;
39  public connectState: ble.ProfileConnectionState = constant.ProfileConnectionState.STATE_DISCONNECTED;
40  public myServiceUuid: string = '00001810-0000-1000-8000-00805F9B34FB';
41  public myCharacteristicUuid: string = '00001820-0000-1000-8000-00805F9B34FB';
42  public myDescriptorUuid: string = '00002902-0000-1000-8000-00805F9B34FB'; // 2902一般用于notification或者indication
43  public myCharacteristicValue: string = ''
44  public myDescriptorValue: string = ''
45  public serviceResult: string = '';
46  public found: boolean = false;
47  public toastReport: ToastReport = new ToastReport();
48
49  // 构造BLEDescriptor
50  private initDescriptor(des: string, value: ArrayBuffer): ble.BLEDescriptor {
51    let descriptor: ble.BLEDescriptor = {
52      serviceUuid: this.myServiceUuid,
53      characteristicUuid: this.myCharacteristicUuid,
54      descriptorUuid: des,
55      descriptorValue: value
56    };
57    return descriptor;
58  }
59
60  // 构造BLECharacteristic
61  private initCharacteristic(): ble.BLECharacteristic {
62    let descriptors: ble.BLEDescriptor[] = [];
63    let desLength = this.myDescriptorValue.length;
64    let descBuffer = new ArrayBuffer(desLength);
65    let descValue = new Uint8Array(descBuffer);
66    encoder.encodeIntoUint8Array(this.myCharacteristicValue, descValue);
67    descriptors[0] = this.initDescriptor(this.myDescriptorUuid, descBuffer);
68    let length = this.myCharacteristicValue.length;
69    let charBuffer = new ArrayBuffer(length);
70    let charValue = new Uint8Array(charBuffer);
71    encoder.encodeIntoUint8Array(this.myCharacteristicValue, charValue);
72    let characteristic: ble.BLECharacteristic = {
73      serviceUuid: this.myServiceUuid,
74      characteristicUuid: this.myCharacteristicUuid,
75      characteristicValue: charBuffer,
76      descriptors: descriptors
77    };
78    return characteristic;
79  }
80
81  private logCharacteristic(char: ble.BLECharacteristic) {
82    let message = 'logCharacteristic uuid:' + char.characteristicUuid + '\n';
83    let value = new Uint8Array(char.characteristicValue);
84    message += 'logCharacteristic value: ';
85    for (let i = 0; i < char.characteristicValue.byteLength; i++) {
86      message += value[i] + ' ';
87    }
88    console.info(TAG, message);
89    // [StartExclude generic_attribute]
90    this.toastReport.showResult(message);
91    // [EndExclude generic_attribute]
92  }
93
94  private logDescriptor(des: ble.BLEDescriptor) {
95    let message = 'logDescriptor uuid:' + des.descriptorUuid + '\n';
96    let value = new Uint8Array(des.descriptorValue);
97    message += 'logDescriptor value: ';
98    for (let i = 0; i < des.descriptorValue.byteLength; i++) {
99      message += value[i] + ' ';
100    }
101    console.info(TAG, message);
102    // [StartExclude generic_attribute]
103    this.toastReport.showResult(message);
104    // [EndExclude generic_attribute]
105  }
106
107  private checkService(services: Array<ble.GattService>): boolean {
108    for (let i = 0; i < services.length; i++) {
109      if (services[i].serviceUuid != this.myServiceUuid) {
110        continue;
111      }
112      for (let j = 0; j < services[i].characteristics.length; j++) {
113        if (services[i].characteristics[j].characteristicUuid != this.myCharacteristicUuid) {
114          continue;
115        }
116        for (let k = 0; k < services[i].characteristics[j].descriptors.length; k++) {
117          if (services[i].characteristics[j].descriptors[k].descriptorUuid == this.myDescriptorUuid) {
118            console.info(TAG, 'find expected service from server');
119            return true;
120          }
121        }
122      }
123    }
124    console.error(TAG, 'no expected service from server');
125    return false;
126  }
127
128  public getResult(): string {
129    return this.serviceResult;
130  }
131
132  public setGattData(data: GattData) {
133    this.myServiceUuid = data.serviceUuid;
134    this.myCharacteristicUuid = data.characteristicUuid;
135    this.myDescriptorUuid = data.descriptorUuid;
136    this.myCharacteristicValue = data.characteristicValue;
137    this.myDescriptorValue = data.descriptorValue;
138  }
139
140  // 1. 订阅连接状态变化事件
141  public onGattClientStateChange() {
142    if (!this.gattClient) {
143      console.error(TAG, 'no gattClient');
144      return;
145    }
146    try {
147      this.gattClient.on('BLEConnectionStateChange', (stateInfo: ble.BLEConnectionChangeState) => {
148        let state = '';
149        switch (stateInfo.state) {
150          case 0:
151            state = 'DISCONNECTED';
152            break;
153          case 1:
154            state = 'CONNECTING';
155            break;
156          case 2:
157            state = 'CONNECTED';
158            break;
159          case 3:
160            state = 'DISCONNECTING';
161            break;
162          default:
163            state = 'undefined';
164            break;
165        }
166        console.info(TAG, 'onGattClientStateChange: device=' + stateInfo.deviceId + ', state=' + state);
167        if (stateInfo.deviceId == this.device) {
168          this.connectState = stateInfo.state;
169        }
170      });
171    } catch (err) {
172      console.error(TAG, 'errCode: ' + (err as BusinessError).code + ', errMessage: ' + (err as BusinessError).message);
173    }
174  }
175
176  // 2. client端主动连接时调用
177  public startConnect(peerDevice: string | undefined) { // 对端设备一般通过ble scan获取到
178    if (this.connectState != constant.ProfileConnectionState.STATE_DISCONNECTED || peerDevice == undefined) {
179      console.error(TAG, 'startConnect failed');
180      return;
181    }
182    console.info(TAG, 'startConnect ' + peerDevice);
183    this.device = peerDevice;
184    // 2.1 使用device构造gattClient,后续的交互都需要使用该实例
185    this.gattClient = ble.createGattClientDevice(peerDevice);
186    try {
187      this.onGattClientStateChange(); // 2.2 订阅连接状态
188      this.gattClient.connect(); // 2.3 发起连接
189      // [StartExclude generic_attribute]
190      this.toastReport.showResult('startConnect success');
191      console.info(TAG, 'startConnect success');
192      // [EndExclude generic_attribute]
193    } catch (err) {
194      // [StartExclude generic_attribute]
195      this.toastReport.showResult('startConnect fail');
196      // [EndExclude generic_attribute]
197      console.error(TAG, 'errCode: ' + (err as BusinessError).code + ', errMessage: ' + (err as BusinessError).message);
198    }
199  }
200
201  // 3. client端连接成功后,需要进行服务发现
202  public async discoverServices() {
203    if (!this.gattClient) {
204      console.info(TAG, 'no gattClient');
205      return;
206    }
207    console.info(TAG, 'discoverServices');
208    try {
209      await this.gattClient.getServices().then((result: Array<ble.GattService>) => {
210        console.info(TAG, 'getServices success: ' + JSON.stringify(result));
211        // [StartExclude generic_attribute]
212        this.serviceResult = JSON.stringify(result);
213        this.toastReport.showResult('getServices success: ' + JSON.stringify(result));
214        // [EndExclude generic_attribute]
215        this.found = this.checkService(result); // 要确保server端的服务内容有业务所需要的服务
216      });
217    } catch (err) {
218      // [StartExclude generic_attribute]
219      this.toastReport.showResult('getServices fail');
220      // [EndExclude generic_attribute]
221      console.error(TAG, 'errCode: ' + (err as BusinessError).code + ', errMessage: ' + (err as BusinessError).message);
222    }
223  }
224
225  // 4. 在确保拿到了server端的服务结果后,he
226  public readCharacteristicValue() {
227    if (!this.gattClient || this.connectState != constant.ProfileConnectionState.STATE_CONNECTED) {
228      console.error(TAG, 'no gattClient or not connected');
229      // [StartExclude generic_attribute]
230      this.toastReport.showResult('no gattClient or not connected');
231      // [EndExclude generic_attribute]
232      return;
233    }
234    if (!this.found) { // 要确保server端有对应的characteristic
235      console.error(TAG, 'no characteristic from server');
236      // [StartExclude generic_attribute]
237      this.toastReport.showResult('no characteristic from server');
238      // [EndExclude generic_attribute]
239      return;
240    }
241
242    let characteristic = this.initCharacteristic();
243    console.info(TAG, 'readCharacteristicValue');
244    try {
245      this.gattClient.readCharacteristicValue(characteristic).then((outData: ble.BLECharacteristic) => {
246        this.logCharacteristic(outData);
247      })
248      // [StartExclude generic_attribute]
249      this.toastReport.showResult('readCharacteristicValue success');
250      // [EndExclude generic_attribute]
251    } catch (err) {
252      // [StartExclude generic_attribute]
253      this.toastReport.showResult('readCharacteristicValue fail');
254      // [EndExclude generic_attribute]
255      console.error(TAG, 'errCode: ' + (err as BusinessError).code + ', errMessage: ' + (err as BusinessError).message);
256    }
257  }
258
259  // 5. 在确保拿到了server端的服务结果后,写入server端特定服务的特征值时调用
260  public writeCharacteristicValue() {
261    if (!this.gattClient || this.connectState != constant.ProfileConnectionState.STATE_CONNECTED) {
262      console.error(TAG, 'no gattClient or not connected');
263      // [StartExclude generic_attribute]
264      this.toastReport.showResult('no gattClient or not connected');
265      // [EndExclude generic_attribute]
266      return;
267    }
268    if (!this.found) { // 要确保server端有对应的characteristic
269      console.error(TAG, 'no characteristic from server');
270      // [StartExclude generic_attribute]
271      this.toastReport.showResult('no characteristic from server');
272      // [EndExclude generic_attribute]
273      return;
274    }
275
276    let characteristic = this.initCharacteristic();
277    console.info(TAG, 'writeCharacteristicValue');
278    try {
279      this.gattClient.writeCharacteristicValue(characteristic, ble.GattWriteType.WRITE, (err) => {
280        if (err) {
281          console.error(TAG,
282            'errCode: ' + (err as BusinessError).code + ', errMessage: ' + (err as BusinessError).message);
283          return;
284        }
285        // [StartExclude generic_attribute]
286        this.toastReport.showResult('writeCharacteristicValue success');
287        // [EndExclude generic_attribute]
288        console.info(TAG, 'writeCharacteristicValue success');
289      });
290    } catch (err) {
291      // [StartExclude generic_attribute]
292      console.info(TAG, 'writeCharacteristicValue fail');
293      this.toastReport.showResult('writeCharacteristicValue fail');
294      // [EndExclude generic_attribute]
295      console.error(TAG, 'errCode: ' + (err as BusinessError).code + ', errMessage: ' + (err as BusinessError).message);
296    }
297  }
298
299  // 6. 在确保拿到了server端的服务结果后,读取server端特定服务的描述符时调用
300  public readDescriptorValue() {
301    if (!this.gattClient || this.connectState != constant.ProfileConnectionState.STATE_CONNECTED) {
302      console.error(TAG, 'no gattClient or not connected');
303      // [StartExclude generic_attribute]
304      this.toastReport.showResult('no gattClient or not connected');
305      // [EndExclude generic_attribute]
306      return;
307    }
308    if (!this.found) { // 要确保server端有对应的descriptor
309      console.error(TAG, 'no descriptor from server');
310      // [StartExclude generic_attribute]
311      this.toastReport.showResult('no descriptor from server');
312      // [EndExclude generic_attribute]
313      return;
314    }
315
316    let descBuffer = new ArrayBuffer(0);
317    let descriptor = this.initDescriptor(this.myDescriptorUuid, descBuffer);
318    console.info(TAG, 'readDescriptorValue');
319    try {
320      this.gattClient.readDescriptorValue(descriptor).then((outData: ble.BLEDescriptor) => {
321        this.logDescriptor(outData);
322      });
323      // [StartExclude generic_attribute]
324      this.toastReport.showResult('readDescriptorValue success');
325      // [EndExclude generic_attribute]
326    } catch (err) {
327      // [StartExclude generic_attribute]
328      this.toastReport.showResult('readDescriptorValue fail');
329      // [EndExclude generic_attribute]
330      console.error(TAG, 'errCode: ' + (err as BusinessError).code + ', errMessage: ' + (err as BusinessError).message);
331    }
332  }
333
334  // 7. 在确保拿到了server端的服务结果后,写入server端特定服务的描述符时调用
335  public writeDescriptorValue() {
336    if (!this.gattClient || this.connectState != constant.ProfileConnectionState.STATE_CONNECTED) {
337      console.error(TAG, 'no gattClient or not connected');
338      // [StartExclude generic_attribute]
339      this.toastReport.showResult('no gattClient or not connected');
340      // [EndExclude generic_attribute]
341      return;
342    }
343    if (!this.found) { // 要确保server端有对应的descriptor
344      console.error(TAG, 'no descriptor from server');
345      // [StartExclude generic_attribute]
346      this.toastReport.showResult('no descriptor from server');
347      // [EndExclude generic_attribute]
348      return;
349    }
350
351    let length = this.myDescriptorValue.length;
352    let descBuffer = new ArrayBuffer(length);
353    let descValue = new Uint8Array(descBuffer);
354    encoder.encodeIntoUint8Array(this.myDescriptorValue, descValue);
355    let descriptor = this.initDescriptor(this.myDescriptorUuid, descBuffer);
356    console.info(TAG, 'writeDescriptorValue');
357    try {
358      this.gattClient.writeDescriptorValue(descriptor, (err) => {
359        if (err) {
360          console.error(TAG,
361            'errCode: ' + (err as BusinessError).code + ', errMessage: ' + (err as BusinessError).message);
362          return;
363        }
364        console.info(TAG, 'writeDescriptorValue success');
365        // [StartExclude generic_attribute]
366        this.toastReport.showResult('writeDescriptorValue success');
367        // [EndExclude generic_attribute]
368      });
369    } catch (err) {
370      // [StartExclude generic_attribute]
371      this.toastReport.showResult('writeDescriptorValue fail');
372      // [EndExclude generic_attribute]
373      console.error(TAG, 'errCode: ' + (err as BusinessError).code + ', errMessage: ' + (err as BusinessError).message);
374    }
375  }
376
377  // 8.client端主动断开时调用
378  public stopConnect() {
379    if (!this.gattClient || this.connectState != constant.ProfileConnectionState.STATE_CONNECTED) {
380      console.error(TAG, 'no gattClient or not connected');
381      return;
382    }
383
384    console.info(TAG, 'stopConnect ' + this.device);
385    try {
386      this.gattClient.disconnect(); // 8.1 断开连接
387      this.gattClient.off('BLEConnectionStateChange', (stateInfo: ble.BLEConnectionChangeState) => {
388      });
389      this.gattClient.close() // 8.2 如果不再使用此gattClient,则需要close
390      // [StartExclude generic_attribute]
391      this.toastReport.showResult('stopConnect success');
392      // [EndExclude generic_attribute]
393    } catch (err) {
394      // [StartExclude generic_attribute]
395      this.toastReport.showResult('stopConnect fail');
396      // [EndExclude generic_attribute]
397      console.error(TAG, 'errCode: ' + (err as BusinessError).code + ', errMessage: ' + (err as BusinessError).message);
398    }
399  }
400}
401
402let gattClientManager = new GattClientManager();
403
404export default gattClientManager as GattClientManager;
405// [End generic_attribute]