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]