• 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 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