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 open_close_scan] 17import { ble } from '@kit.ConnectivityKit'; 18import { BusinessError } from '@kit.BasicServicesKit'; 19import { ScanData } from '../common/ScanData'; 20import { ToastReport } from '../common/ToastReport'; 21 22const TAG: string = 'BleScanManager'; 23 24const BLE_ADV_TYPE_FLAG = 0x01; 25const BLE_ADV_TYPE_16_BIT_SERVICE_UUIDS_INCOMPLETE = 0x02; 26const BLE_ADV_TYPE_16_BIT_SERVICE_UUIDS_COMPLETE = 0x03; 27const BLE_ADV_TYPE_32_BIT_SERVICE_UUIDS_INCOMPLETE = 0x04; 28const BLE_ADV_TYPE_32_BIT_SERVICE_UUIDS_COMPLETE = 0x05; 29const BLE_ADV_TYPE_128_BIT_SERVICE_UUIDS_INCOMPLETE = 0x06; 30const BLE_ADV_TYPE_128_BIT_SERVICE_UUIDS_COMPLETE = 0x07; 31const BLE_ADV_TYPE_LOCAL_NAME_SHORT = 0x08; 32const BLE_ADV_TYPE_LOCAL_NAME_COMPLETE = 0x09; 33const BLE_ADV_TYPE_TX_POWER_LEVEL = 0x0A; 34const BLE_ADV_TYPE_16_BIT_SERVICE_SOLICITATION_UUIDS = 0x14; 35const BLE_ADV_TYPE_128_BIT_SERVICE_SOLICITATION_UUIDS = 0x15; 36const BLE_ADV_TYPE_32_BIT_SERVICE_SOLICITATION_UUIDS = 0x1F; 37const BLE_ADV_TYPE_16_BIT_SERVICE_DATA = 0x16; 38const BLE_ADV_TYPE_32_BIT_SERVICE_DATA = 0x20; 39const BLE_ADV_TYPE_128_BIT_SERVICE_DATA = 0x21; 40const BLE_ADV_TYPE_MANUFACTURER_SPECIFIC_DATA = 0xFF; 41 42const BLUETOOTH_UUID_16_BIT_LENGTH = 2; 43const BLUETOOTH_UUID_32_BIT_LENGTH = 4; 44const BLUETOOTH_UUID_128_BIT_LENGTH = 16; 45 46const BLUETOOTH_MANUFACTURE_ID_LENGTH = 2; 47 48export class BleScanManager { 49 private interval: number = 0; 50 private manufactureId: number = 4567; 51 private deviceName: string | undefined = ''; 52 private deviceId: string | undefined = ''; 53 private scanDataList: ScanData[] = []; 54 public toastReport: ToastReport = new ToastReport(); 55 56 public setScanParameter(interval?: string, manufactureId?: string, deviceName?: string | undefined, 57 deviceId?: string | undefined) { 58 if (interval != undefined) { 59 this.interval = Number(interval); 60 console.info(TAG, 'interval is' + interval); 61 } 62 if (manufactureId != undefined) { 63 console.info(TAG, 'manufactureId is' + manufactureId); 64 this.manufactureId = Number(manufactureId); 65 } 66 this.deviceName = deviceName; 67 this.deviceId = deviceId; 68 } 69 70 public getScanData(): ScanData[] { 71 return this.scanDataList; 72 } 73 74 // 1 订阅扫描结果 75 public onScanResult() { 76 ble.on('BLEDeviceFind', (data: Array<ble.ScanResult>) => { 77 if (data.length > 0) { 78 console.info(TAG, 'BLE scan result = ' + data[0].deviceId); 79 this.parseScanResult(data[0].data); 80 // [StartExclude open_close_scan] 81 this.toastReport.showResult(data[0].deviceId); 82 if (!this.scanDataList.some(item => item.getDeviceName() === data[0].deviceName)) { 83 this.scanDataList.push(new ScanData(data[0].deviceId, data[0].deviceName, data[0].rssi, data[0].connectable, 84 new Uint8Array(data[0].data))); 85 } 86 // [EndExclude open_close_scan] 87 } 88 }); 89 } 90 91 private parseScanResult(data: ArrayBuffer) { 92 let advData = new Uint8Array(data); 93 if (advData.byteLength == 0) { 94 console.warn(TAG, 'nothing, adv data length is 0'); 95 return; 96 } 97 console.info(TAG, 'advData: ' + JSON.stringify(advData)); 98 99 let advFlags: number = -1; 100 let txPowerLevel: number = -1; 101 let localName: string = ''; 102 let serviceUuids: string[] = []; 103 let serviceSolicitationUuids: string[] = []; 104 let serviceDatas: Record<string, Uint8Array> = {}; 105 let manufactureSpecificDatas: Record<number, Uint8Array> = {}; 106 107 let curPos = 0; 108 while (curPos < advData.byteLength) { 109 let length = advData[curPos++]; 110 if (length == 0) { 111 break; 112 } 113 let advDataLength = length - 1; 114 let advDataType = advData[curPos++]; 115 switch (advDataType) { 116 case BLE_ADV_TYPE_FLAG: 117 advFlags = advData[curPos]; 118 break; 119 case BLE_ADV_TYPE_LOCAL_NAME_SHORT: 120 case BLE_ADV_TYPE_LOCAL_NAME_COMPLETE: 121 localName = advData.slice(curPos, curPos + advDataLength).toString(); 122 break; 123 case BLE_ADV_TYPE_TX_POWER_LEVEL: 124 txPowerLevel = advData[curPos]; 125 break; 126 case BLE_ADV_TYPE_16_BIT_SERVICE_UUIDS_INCOMPLETE: 127 case BLE_ADV_TYPE_16_BIT_SERVICE_UUIDS_COMPLETE: 128 this.parseServiceUuid(BLUETOOTH_UUID_16_BIT_LENGTH, curPos, advDataLength, advData, serviceUuids); 129 break; 130 case BLE_ADV_TYPE_32_BIT_SERVICE_UUIDS_INCOMPLETE: 131 case BLE_ADV_TYPE_32_BIT_SERVICE_UUIDS_COMPLETE: 132 this.parseServiceUuid(BLUETOOTH_UUID_32_BIT_LENGTH, curPos, advDataLength, advData, serviceUuids); 133 break; 134 case BLE_ADV_TYPE_128_BIT_SERVICE_UUIDS_INCOMPLETE: 135 case BLE_ADV_TYPE_128_BIT_SERVICE_UUIDS_COMPLETE: 136 this.parseServiceUuid(BLUETOOTH_UUID_128_BIT_LENGTH, curPos, advDataLength, advData, serviceUuids); 137 break; 138 case BLE_ADV_TYPE_16_BIT_SERVICE_SOLICITATION_UUIDS: 139 this.parseServiceSolicitationUuid(BLUETOOTH_UUID_16_BIT_LENGTH, curPos, advDataLength, 140 advData, serviceSolicitationUuids); 141 break; 142 case BLE_ADV_TYPE_32_BIT_SERVICE_SOLICITATION_UUIDS: 143 this.parseServiceSolicitationUuid(BLUETOOTH_UUID_32_BIT_LENGTH, curPos, advDataLength, 144 advData, serviceSolicitationUuids); 145 break; 146 case BLE_ADV_TYPE_128_BIT_SERVICE_SOLICITATION_UUIDS: 147 this.parseServiceSolicitationUuid(BLUETOOTH_UUID_128_BIT_LENGTH, curPos, advDataLength, 148 advData, serviceSolicitationUuids); 149 break; 150 case BLE_ADV_TYPE_16_BIT_SERVICE_DATA: 151 this.parseServiceData(BLUETOOTH_UUID_16_BIT_LENGTH, curPos, advDataLength, advData, serviceDatas); 152 break; 153 case BLE_ADV_TYPE_32_BIT_SERVICE_DATA: 154 this.parseServiceData(BLUETOOTH_UUID_32_BIT_LENGTH, curPos, advDataLength, advData, serviceDatas); 155 break; 156 case BLE_ADV_TYPE_128_BIT_SERVICE_DATA: 157 this.parseServiceData(BLUETOOTH_UUID_128_BIT_LENGTH, curPos, advDataLength, advData, serviceDatas); 158 break; 159 case BLE_ADV_TYPE_MANUFACTURER_SPECIFIC_DATA: 160 this.parseManufactureData(curPos, advDataLength, advData, manufactureSpecificDatas); 161 break; 162 default: 163 break; 164 } 165 curPos += advDataLength; 166 } 167 } 168 169 private parseServiceUuid(uuidLength: number, curPos: number, advDataLength: number, 170 advData: Uint8Array, serviceUuids: string[]) { 171 while (advDataLength > 0) { 172 let tmpData: Uint8Array = advData.slice(curPos, curPos + uuidLength); 173 serviceUuids.push(this.getUuidFromUint8Array(uuidLength, tmpData)); 174 advDataLength -= uuidLength; 175 curPos += uuidLength; 176 } 177 } 178 179 private parseServiceSolicitationUuid(uuidLength: number, curPos: number, advDataLength: number, 180 advData: Uint8Array, serviceSolicitationUuids: string[]) { 181 while (advDataLength > 0) { 182 let tmpData: Uint8Array = advData.slice(curPos, curPos + uuidLength); 183 serviceSolicitationUuids.push(this.getUuidFromUint8Array(uuidLength, tmpData)); 184 advDataLength -= uuidLength; 185 curPos += uuidLength; 186 } 187 } 188 189 private getUuidFromUint8Array(uuidLength: number, uuidData: Uint8Array): string { 190 let uuid = ''; 191 let temp: string = ''; 192 for (let i = uuidLength - 1; i > -1; i--) { 193 temp += uuidData[i].toString(16).padStart(2, '0'); 194 } 195 switch (uuidLength) { 196 case BLUETOOTH_UUID_16_BIT_LENGTH: 197 uuid = `0000${temp}-0000-1000-8000-00805F9B34FB`; 198 break; 199 case BLUETOOTH_UUID_32_BIT_LENGTH: 200 uuid = `${temp}-0000-1000-8000-00805F9B34FB`; 201 break; 202 case BLUETOOTH_UUID_128_BIT_LENGTH: 203 uuid = `${temp.substring(0, 8)}-${temp.substring(8, 12)}-${temp.substring(12, 16)}-${temp.substring(16, 204 20)}-${temp.substring(20, 32)}`; 205 break; 206 default: 207 break; 208 } 209 return uuid; 210 } 211 212 private parseServiceData(uuidLength: number, curPos: number, advDataLength: number, 213 advData: Uint8Array, serviceDatas: Record<string, Uint8Array>) { 214 let tmpUuid: Uint8Array = advData.slice(curPos, curPos + uuidLength); 215 let tmpValue: Uint8Array = advData.slice(curPos + uuidLength, curPos + advDataLength); 216 serviceDatas[tmpUuid.toString()] = tmpValue; 217 } 218 219 private parseManufactureData(curPos: number, advDataLength: number, 220 advData: Uint8Array, manufactureSpecificDatas: Record<number, Uint8Array>) { 221 let manufactureId: number = (advData[curPos + 1] << 8) + advData[curPos]; 222 let tmpValue: Uint8Array = advData.slice(curPos + BLUETOOTH_MANUFACTURE_ID_LENGTH, curPos + advDataLength); 223 manufactureSpecificDatas[manufactureId] = tmpValue; 224 } 225 226 // 2 开启扫描 227 public startScan() { 228 console.info(TAG, 'startBleScan success 1'); 229 // 2.1 构造扫描过滤器,需要能够匹配预期的广播包内容 230 let manufactureId = this.manufactureId; 231 let manufactureDataMask: Uint8Array = new Uint8Array([0xFF, 0xFF, 0xFF, 0xFF]); 232 let scanFilter: ble.ScanFilter = { 233 // 根据业务实际情况定义过滤器 234 manufactureId: manufactureId, 235 // manufactureData: manufactureData.buffer, 236 manufactureDataMask: manufactureDataMask.buffer, 237 }; 238 // [StartExclude open_close_scan] 239 if (this.deviceName) { 240 console.info('===lyk=== deviceName is not null ' + this.deviceName) 241 scanFilter.name = this.deviceName; 242 } 243 if (this.deviceId) { 244 scanFilter.deviceId = this.deviceId; 245 } 246 // [EndExclude open_close_scan] 247 248 // 2.2 构造扫描参数 249 let scanOptions: ble.ScanOptions = { 250 interval: this.interval, 251 dutyMode: ble.ScanDuty.SCAN_MODE_LOW_POWER, 252 matchMode: ble.MatchMode.MATCH_MODE_AGGRESSIVE, 253 } 254 try { 255 // [StartExclude open_close_scan] 256 this.scanDataList = []; 257 // [EndExclude open_close_scan] 258 this.onScanResult(); // 订阅扫描结果 259 ble.startBLEScan([scanFilter], scanOptions); 260 // [StartExclude open_close_scan] 261 this.toastReport.showResult('startBleScan success'); 262 // [EndExclude open_close_scan] 263 console.info(TAG, 'startBleScan success'); 264 } catch (err) { 265 // [StartExclude open_close_scan] 266 this.toastReport.showResult('startBleScan fail'); 267 // [EndExclude open_close_scan] 268 console.error(TAG, 'errCode: ' + (err as BusinessError).code + ', errMessage: ' + (err as BusinessError).message); 269 } 270 } 271 272 // 3 关闭扫描 273 public stopScan() { 274 try { 275 ble.off('BLEDeviceFind', (data: Array<ble.ScanResult>) => { // 取消订阅扫描结果 276 console.info(TAG, 'off success'); 277 }); 278 ble.stopBLEScan(); 279 // [StartExclude open_close_scan] 280 this.toastReport.showResult('stopBleScan success'); 281 // [EndExclude open_close_scan] 282 console.info(TAG, 'stopBleScan success'); 283 } catch (err) { 284 // [StartExclude open_close_scan] 285 this.toastReport.showResult('stopBleScan fail'); 286 // [EndExclude open_close_scan] 287 console.error(TAG, 'errCode: ' + (err as BusinessError).code + ', errMessage: ' + (err as BusinessError).message); 288 } 289 } 290} 291 292let bleScanManager = new BleScanManager(); 293 294export default bleScanManager as BleScanManager; 295// [End open_close_scan] 296