@fww_123/uni-ble-scanner 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +283 -0
- package/dist/core/base-scanner.d.ts +99 -0
- package/dist/core/base-scanner.d.ts.map +1 -0
- package/dist/factory.d.ts +44 -0
- package/dist/factory.d.ts.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.esm.js +1791 -0
- package/dist/index.js +1818 -0
- package/dist/platforms/android-native.d.ts +30 -0
- package/dist/platforms/android-native.d.ts.map +1 -0
- package/dist/platforms/harmony-native.d.ts +24 -0
- package/dist/platforms/harmony-native.d.ts.map +1 -0
- package/dist/platforms/ios-native.d.ts +27 -0
- package/dist/platforms/ios-native.d.ts.map +1 -0
- package/dist/platforms/uniapp-scanner.d.ts +30 -0
- package/dist/platforms/uniapp-scanner.d.ts.map +1 -0
- package/dist/types.d.ts +170 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/utils/data-parser.d.ts +32 -0
- package/dist/utils/data-parser.d.ts.map +1 -0
- package/dist/utils/errors.d.ts +27 -0
- package/dist/utils/errors.d.ts.map +1 -0
- package/dist/utils/permissions.d.ts +9 -0
- package/dist/utils/permissions.d.ts.map +1 -0
- package/dist/utils/platform.d.ts +18 -0
- package/dist/utils/platform.d.ts.map +1 -0
- package/package.json +58 -0
- package/src/core/base-scanner.ts +309 -0
- package/src/factory.ts +116 -0
- package/src/index.ts +54 -0
- package/src/platforms/android-native.ts +300 -0
- package/src/platforms/harmony-native.ts +267 -0
- package/src/platforms/ios-native.ts +264 -0
- package/src/platforms/uniapp-scanner.ts +171 -0
- package/src/types/global.d.ts +25 -0
- package/src/types/uni-types.d.ts +83 -0
- package/src/types.ts +178 -0
- package/src/utils/data-parser.ts +217 -0
- package/src/utils/errors.ts +105 -0
- package/src/utils/permissions.ts +244 -0
- package/src/utils/platform.ts +70 -0
|
@@ -0,0 +1,1791 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 扫描状态
|
|
3
|
+
*/
|
|
4
|
+
var ScanState;
|
|
5
|
+
(function (ScanState) {
|
|
6
|
+
ScanState["IDLE"] = "idle";
|
|
7
|
+
ScanState["SCANNING"] = "scanning";
|
|
8
|
+
ScanState["STOPPING"] = "stopping";
|
|
9
|
+
ScanState["ERROR"] = "error";
|
|
10
|
+
})(ScanState || (ScanState = {}));
|
|
11
|
+
/**
|
|
12
|
+
* 平台类型
|
|
13
|
+
*/
|
|
14
|
+
var Platform;
|
|
15
|
+
(function (Platform) {
|
|
16
|
+
Platform["ANDROID"] = "android";
|
|
17
|
+
Platform["IOS"] = "ios";
|
|
18
|
+
Platform["HARMONY"] = "harmony";
|
|
19
|
+
Platform["UNKNOWN"] = "unknown";
|
|
20
|
+
})(Platform || (Platform = {}));
|
|
21
|
+
|
|
22
|
+
/******************************************************************************
|
|
23
|
+
Copyright (c) Microsoft Corporation.
|
|
24
|
+
|
|
25
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
26
|
+
purpose with or without fee is hereby granted.
|
|
27
|
+
|
|
28
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
29
|
+
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
30
|
+
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
31
|
+
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
32
|
+
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
33
|
+
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
34
|
+
PERFORMANCE OF THIS SOFTWARE.
|
|
35
|
+
***************************************************************************** */
|
|
36
|
+
/* global Reflect, Promise, SuppressedError, Symbol, Iterator */
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
function __awaiter(thisArg, _arguments, P, generator) {
|
|
40
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
41
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
42
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
43
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
44
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
45
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
|
|
50
|
+
var e = new Error(message);
|
|
51
|
+
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* 错误码定义
|
|
56
|
+
*/
|
|
57
|
+
const ErrorCodes = {
|
|
58
|
+
// 通用错误
|
|
59
|
+
UNKNOWN_ERROR: 1000,
|
|
60
|
+
NOT_INITIALIZED: 1001,
|
|
61
|
+
ALREADY_SCANNING: 1002,
|
|
62
|
+
NOT_SCANNING: 1003,
|
|
63
|
+
PERMISSION_DENIED: 1004,
|
|
64
|
+
BLUETOOTH_DISABLED: 1005,
|
|
65
|
+
UNSUPPORTED_PLATFORM: 1006,
|
|
66
|
+
// 蓝牙特定错误
|
|
67
|
+
ADAPTER_NOT_AVAILABLE: 2001,
|
|
68
|
+
SCAN_FAILED: 2002,
|
|
69
|
+
DEVICE_NOT_FOUND: 2003,
|
|
70
|
+
CONNECTION_FAILED: 2004,
|
|
71
|
+
// 平台特定错误
|
|
72
|
+
ANDROID_ERROR: 3000,
|
|
73
|
+
IOS_ERROR: 4000,
|
|
74
|
+
HARMONY_ERROR: 5000,
|
|
75
|
+
};
|
|
76
|
+
/**
|
|
77
|
+
* 创建错误对象
|
|
78
|
+
*/
|
|
79
|
+
function createError(code, message, platformCode, originalError) {
|
|
80
|
+
return {
|
|
81
|
+
code,
|
|
82
|
+
message,
|
|
83
|
+
platformCode,
|
|
84
|
+
originalError
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* 通用错误
|
|
89
|
+
*/
|
|
90
|
+
const Errors = {
|
|
91
|
+
notInitialized: () => createError(ErrorCodes.NOT_INITIALIZED, '蓝牙适配器未初始化,请先调用 init()'),
|
|
92
|
+
alreadyScanning: () => createError(ErrorCodes.ALREADY_SCANNING, '扫描已在进行中'),
|
|
93
|
+
notScanning: () => createError(ErrorCodes.NOT_SCANNING, '扫描未在进行中'),
|
|
94
|
+
permissionDenied: () => createError(ErrorCodes.PERMISSION_DENIED, '蓝牙权限被拒绝'),
|
|
95
|
+
bluetoothDisabled: () => createError(ErrorCodes.BLUETOOTH_DISABLED, '蓝牙未开启'),
|
|
96
|
+
unsupportedPlatform: () => createError(ErrorCodes.UNSUPPORTED_PLATFORM, '当前平台不支持蓝牙功能'),
|
|
97
|
+
adapterNotAvailable: () => createError(ErrorCodes.ADAPTER_NOT_AVAILABLE, '蓝牙适配器不可用'),
|
|
98
|
+
scanFailed: (reason) => createError(ErrorCodes.SCAN_FAILED, `扫描失败: ${reason || '未知原因'}`),
|
|
99
|
+
};
|
|
100
|
+
/**
|
|
101
|
+
* 处理平台特定错误
|
|
102
|
+
*/
|
|
103
|
+
function handlePlatformError(error, platform) {
|
|
104
|
+
const platformCode = (error === null || error === void 0 ? void 0 : error.code) || (error === null || error === void 0 ? void 0 : error.errCode);
|
|
105
|
+
const message = (error === null || error === void 0 ? void 0 : error.message) || (error === null || error === void 0 ? void 0 : error.errMsg) || '未知错误';
|
|
106
|
+
let code = ErrorCodes.UNKNOWN_ERROR;
|
|
107
|
+
switch (platform) {
|
|
108
|
+
case 'android':
|
|
109
|
+
code = ErrorCodes.ANDROID_ERROR;
|
|
110
|
+
break;
|
|
111
|
+
case 'ios':
|
|
112
|
+
code = ErrorCodes.IOS_ERROR;
|
|
113
|
+
break;
|
|
114
|
+
case 'harmony':
|
|
115
|
+
code = ErrorCodes.HARMONY_ERROR;
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
return createError(code, `${platform}平台错误: ${message}`, platformCode, error);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* 解析广播数据
|
|
123
|
+
*/
|
|
124
|
+
function parseAdvertisementData(data) {
|
|
125
|
+
const result = {};
|
|
126
|
+
if (!data) {
|
|
127
|
+
return result;
|
|
128
|
+
}
|
|
129
|
+
let buffer;
|
|
130
|
+
if (typeof data === 'string') {
|
|
131
|
+
// Base64 解码
|
|
132
|
+
buffer = base64ToArrayBuffer(data);
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
buffer = data;
|
|
136
|
+
}
|
|
137
|
+
const view = new DataView(buffer);
|
|
138
|
+
let offset = 0;
|
|
139
|
+
const manufacturerData = {};
|
|
140
|
+
const serviceUuids = [];
|
|
141
|
+
while (offset < view.byteLength) {
|
|
142
|
+
const length = view.getUint8(offset);
|
|
143
|
+
if (length === 0)
|
|
144
|
+
break;
|
|
145
|
+
const type = view.getUint8(offset + 1);
|
|
146
|
+
const dataOffset = offset + 2;
|
|
147
|
+
const dataLength = length - 1;
|
|
148
|
+
switch (type) {
|
|
149
|
+
case 0x01: // Flags
|
|
150
|
+
const flags = view.getUint8(dataOffset);
|
|
151
|
+
result.isConnectable = (flags & 0x02) !== 0;
|
|
152
|
+
break;
|
|
153
|
+
case 0x02: // 16-bit Service UUIDs (partial)
|
|
154
|
+
case 0x03: // 16-bit Service UUIDs (complete)
|
|
155
|
+
for (let i = 0; i < dataLength; i += 2) {
|
|
156
|
+
const uuid = view.getUint16(dataOffset + i, true).toString(16).padStart(4, '0');
|
|
157
|
+
serviceUuids.push(`0000${uuid}-0000-1000-8000-00805f9b34fb`);
|
|
158
|
+
}
|
|
159
|
+
break;
|
|
160
|
+
case 0x04: // 32-bit Service UUIDs (partial)
|
|
161
|
+
case 0x05: // 32-bit Service UUIDs (complete)
|
|
162
|
+
for (let i = 0; i < dataLength; i += 4) {
|
|
163
|
+
const uuid = view.getUint32(dataOffset + i, true).toString(16).padStart(8, '0');
|
|
164
|
+
serviceUuids.push(`${uuid}-0000-1000-8000-00805f9b34fb`);
|
|
165
|
+
}
|
|
166
|
+
break;
|
|
167
|
+
case 0x06: // 128-bit Service UUIDs (partial)
|
|
168
|
+
case 0x07: // 128-bit Service UUIDs (complete)
|
|
169
|
+
for (let i = 0; i < dataLength; i += 16) {
|
|
170
|
+
const uuid = parseUUID(buffer.slice(dataOffset + i, dataOffset + i + 16));
|
|
171
|
+
serviceUuids.push(uuid);
|
|
172
|
+
}
|
|
173
|
+
break;
|
|
174
|
+
case 0x08: // Shortened Local Name
|
|
175
|
+
case 0x09: // Complete Local Name
|
|
176
|
+
result.localName = decodeUTF8(buffer.slice(dataOffset, dataOffset + dataLength));
|
|
177
|
+
break;
|
|
178
|
+
case 0x0A: // TX Power Level
|
|
179
|
+
result.txPowerLevel = view.getInt8(dataOffset);
|
|
180
|
+
break;
|
|
181
|
+
case 0xFF: // Manufacturer Specific Data
|
|
182
|
+
const companyId = view.getUint16(dataOffset, true);
|
|
183
|
+
const companyIdHex = companyId.toString(16).padStart(4, '0');
|
|
184
|
+
manufacturerData[companyIdHex] = buffer.slice(dataOffset + 2, dataOffset + dataLength);
|
|
185
|
+
break;
|
|
186
|
+
}
|
|
187
|
+
offset += length + 1;
|
|
188
|
+
}
|
|
189
|
+
if (Object.keys(manufacturerData).length > 0) {
|
|
190
|
+
result.manufacturerData = manufacturerData;
|
|
191
|
+
}
|
|
192
|
+
if (serviceUuids.length > 0) {
|
|
193
|
+
result.serviceUuids = serviceUuids;
|
|
194
|
+
}
|
|
195
|
+
return result;
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Base64 转 ArrayBuffer
|
|
199
|
+
*/
|
|
200
|
+
function base64ToArrayBuffer(base64) {
|
|
201
|
+
const binary = atob(base64);
|
|
202
|
+
const buffer = new ArrayBuffer(binary.length);
|
|
203
|
+
const view = new Uint8Array(buffer);
|
|
204
|
+
for (let i = 0; i < binary.length; i++) {
|
|
205
|
+
view[i] = binary.charCodeAt(i);
|
|
206
|
+
}
|
|
207
|
+
return buffer;
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* ArrayBuffer 转 Base64
|
|
211
|
+
*/
|
|
212
|
+
function arrayBufferToBase64(buffer) {
|
|
213
|
+
const bytes = new Uint8Array(buffer);
|
|
214
|
+
let binary = '';
|
|
215
|
+
for (let i = 0; i < bytes.byteLength; i++) {
|
|
216
|
+
binary += String.fromCharCode(bytes[i]);
|
|
217
|
+
}
|
|
218
|
+
return btoa(binary);
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* 解析 UUID
|
|
222
|
+
*/
|
|
223
|
+
function parseUUID(buffer) {
|
|
224
|
+
const view = new DataView(buffer);
|
|
225
|
+
const parts = [
|
|
226
|
+
view.getUint32(0, false).toString(16).padStart(8, '0'),
|
|
227
|
+
view.getUint16(4, false).toString(16).padStart(4, '0'),
|
|
228
|
+
view.getUint16(6, false).toString(16).padStart(4, '0'),
|
|
229
|
+
view.getUint16(8, false).toString(16).padStart(4, '0'),
|
|
230
|
+
view.getUint32(10, false).toString(16).padStart(8, '0') +
|
|
231
|
+
view.getUint16(14, false).toString(16).padStart(4, '0'),
|
|
232
|
+
];
|
|
233
|
+
return `${parts[0]}-${parts[1]}-${parts[2]}-${parts[3]}-${parts[4]}`;
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* UTF-8 解码
|
|
237
|
+
*/
|
|
238
|
+
function decodeUTF8(buffer) {
|
|
239
|
+
const decoder = new TextDecoder('utf-8');
|
|
240
|
+
return decoder.decode(buffer);
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* 标准化设备数据
|
|
244
|
+
*/
|
|
245
|
+
function normalizeDeviceData(device) {
|
|
246
|
+
const normalized = {
|
|
247
|
+
deviceId: device.deviceId || '',
|
|
248
|
+
name: device.name || device.localName,
|
|
249
|
+
RSSI: device.RSSI || device.rssi || -100,
|
|
250
|
+
};
|
|
251
|
+
// 处理广播数据
|
|
252
|
+
if (device.advertisData) {
|
|
253
|
+
normalized.advertisData = device.advertisData;
|
|
254
|
+
if (typeof device.advertisData === 'string') {
|
|
255
|
+
normalized.advertisDataBase64 = device.advertisData;
|
|
256
|
+
}
|
|
257
|
+
else {
|
|
258
|
+
normalized.advertisDataBase64 = arrayBufferToBase64(device.advertisData);
|
|
259
|
+
}
|
|
260
|
+
// 解析广播数据
|
|
261
|
+
const parsed = parseAdvertisementData(device.advertisData);
|
|
262
|
+
Object.assign(normalized, parsed);
|
|
263
|
+
}
|
|
264
|
+
// 处理制造商数据
|
|
265
|
+
if (device.manufacturerData) {
|
|
266
|
+
normalized.manufacturerData = device.manufacturerData;
|
|
267
|
+
}
|
|
268
|
+
// 处理服务UUID
|
|
269
|
+
if (device.serviceUuids) {
|
|
270
|
+
normalized.serviceUuids = device.serviceUuids;
|
|
271
|
+
}
|
|
272
|
+
// 平台特定数据
|
|
273
|
+
normalized.platformData = {
|
|
274
|
+
raw: device,
|
|
275
|
+
};
|
|
276
|
+
return normalized;
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* 格式化 UUID (统一格式)
|
|
280
|
+
*/
|
|
281
|
+
function formatUUID(uuid) {
|
|
282
|
+
// 移除所有非十六进制字符
|
|
283
|
+
const clean = uuid.toLowerCase().replace(/[^0-9a-f]/g, '');
|
|
284
|
+
if (clean.length === 4) {
|
|
285
|
+
// 16-bit UUID
|
|
286
|
+
return `0000${clean}-0000-1000-8000-00805f9b34fb`;
|
|
287
|
+
}
|
|
288
|
+
else if (clean.length === 8) {
|
|
289
|
+
// 32-bit UUID
|
|
290
|
+
return `${clean}-0000-1000-8000-00805f9b34fb`;
|
|
291
|
+
}
|
|
292
|
+
else if (clean.length === 32) {
|
|
293
|
+
// 128-bit UUID
|
|
294
|
+
return `${clean.slice(0, 8)}-${clean.slice(8, 12)}-${clean.slice(12, 16)}-${clean.slice(16, 20)}-${clean.slice(20)}`;
|
|
295
|
+
}
|
|
296
|
+
return uuid;
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* 检查 UUID 是否匹配
|
|
300
|
+
*/
|
|
301
|
+
function matchUUID(uuid1, uuid2) {
|
|
302
|
+
return formatUUID(uuid1) === formatUUID(uuid2);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* 扫描器基类
|
|
307
|
+
*/
|
|
308
|
+
class BaseScanner {
|
|
309
|
+
constructor() {
|
|
310
|
+
this.state = ScanState.IDLE;
|
|
311
|
+
this.discoveredDevices = new Map();
|
|
312
|
+
this.callbacks = {};
|
|
313
|
+
this.options = {};
|
|
314
|
+
this.platformConfig = {};
|
|
315
|
+
this.initialized = false;
|
|
316
|
+
this.timeoutId = null;
|
|
317
|
+
this.adapterStateListeners = [];
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* 开始扫描
|
|
321
|
+
*/
|
|
322
|
+
startScan() {
|
|
323
|
+
return __awaiter(this, arguments, void 0, function* (options = {}, callbacks = {}) {
|
|
324
|
+
var _a, _b;
|
|
325
|
+
if (this.state === ScanState.SCANNING) {
|
|
326
|
+
throw Errors.alreadyScanning();
|
|
327
|
+
}
|
|
328
|
+
if (!this.initialized) {
|
|
329
|
+
throw Errors.notInitialized();
|
|
330
|
+
}
|
|
331
|
+
// 检查权限
|
|
332
|
+
const hasPermission = yield this.checkPermissions();
|
|
333
|
+
if (!hasPermission) {
|
|
334
|
+
const granted = yield this.requestPermissions();
|
|
335
|
+
if (!granted) {
|
|
336
|
+
throw Errors.permissionDenied();
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
this.options = Object.assign(Object.assign({}, this.getDefaultOptions()), options);
|
|
340
|
+
this.callbacks = callbacks;
|
|
341
|
+
try {
|
|
342
|
+
this.setState(ScanState.SCANNING);
|
|
343
|
+
// 清空之前的设备列表(如果不需要重复上报)
|
|
344
|
+
if (!this.options.allowDuplicates) {
|
|
345
|
+
this.clearDevices();
|
|
346
|
+
}
|
|
347
|
+
// 设置超时
|
|
348
|
+
if (this.options.timeout && this.options.timeout > 0) {
|
|
349
|
+
this.timeoutId = window.setTimeout(() => {
|
|
350
|
+
this.handleTimeout();
|
|
351
|
+
}, this.options.timeout);
|
|
352
|
+
}
|
|
353
|
+
yield this.startScanInternal();
|
|
354
|
+
(_b = (_a = this.callbacks).onScanStart) === null || _b === void 0 ? void 0 : _b.call(_a);
|
|
355
|
+
}
|
|
356
|
+
catch (error) {
|
|
357
|
+
this.setState(ScanState.ERROR);
|
|
358
|
+
this.clearTimeout();
|
|
359
|
+
throw error;
|
|
360
|
+
}
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
/**
|
|
364
|
+
* 停止扫描
|
|
365
|
+
*/
|
|
366
|
+
stopScan() {
|
|
367
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
368
|
+
var _a, _b;
|
|
369
|
+
if (this.state !== ScanState.SCANNING) {
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
this.setState(ScanState.STOPPING);
|
|
373
|
+
this.clearTimeout();
|
|
374
|
+
try {
|
|
375
|
+
yield this.stopScanInternal();
|
|
376
|
+
this.setState(ScanState.IDLE);
|
|
377
|
+
(_b = (_a = this.callbacks).onScanStop) === null || _b === void 0 ? void 0 : _b.call(_a);
|
|
378
|
+
}
|
|
379
|
+
catch (error) {
|
|
380
|
+
this.setState(ScanState.ERROR);
|
|
381
|
+
throw error;
|
|
382
|
+
}
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
/**
|
|
386
|
+
* 获取已发现的设备列表
|
|
387
|
+
*/
|
|
388
|
+
getDiscoveredDevices() {
|
|
389
|
+
return Array.from(this.discoveredDevices.values());
|
|
390
|
+
}
|
|
391
|
+
/**
|
|
392
|
+
* 清空设备列表
|
|
393
|
+
*/
|
|
394
|
+
clearDevices() {
|
|
395
|
+
this.discoveredDevices.clear();
|
|
396
|
+
}
|
|
397
|
+
/**
|
|
398
|
+
* 获取当前扫描状态
|
|
399
|
+
*/
|
|
400
|
+
getState() {
|
|
401
|
+
return this.state;
|
|
402
|
+
}
|
|
403
|
+
/**
|
|
404
|
+
* 销毁扫描器
|
|
405
|
+
*/
|
|
406
|
+
destroy() {
|
|
407
|
+
this.stopScan();
|
|
408
|
+
this.clearDevices();
|
|
409
|
+
this.clearTimeout();
|
|
410
|
+
this.callbacks = {};
|
|
411
|
+
this.adapterStateListeners = [];
|
|
412
|
+
this.initialized = false;
|
|
413
|
+
}
|
|
414
|
+
/**
|
|
415
|
+
* 处理发现的设备
|
|
416
|
+
*/
|
|
417
|
+
handleDeviceFound(rawDevice) {
|
|
418
|
+
var _a, _b;
|
|
419
|
+
try {
|
|
420
|
+
const device = normalizeDeviceData(rawDevice);
|
|
421
|
+
// 应用过滤器
|
|
422
|
+
if (!this.filterDevice(device)) {
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
425
|
+
// 检查是否已存在
|
|
426
|
+
const existingDevice = this.discoveredDevices.get(device.deviceId);
|
|
427
|
+
if (existingDevice && !this.options.allowDuplicates) {
|
|
428
|
+
// 更新 RSSI
|
|
429
|
+
existingDevice.RSSI = device.RSSI;
|
|
430
|
+
return;
|
|
431
|
+
}
|
|
432
|
+
// 保存设备
|
|
433
|
+
this.discoveredDevices.set(device.deviceId, device);
|
|
434
|
+
// 触发回调
|
|
435
|
+
(_b = (_a = this.callbacks).onDeviceFound) === null || _b === void 0 ? void 0 : _b.call(_a, device);
|
|
436
|
+
}
|
|
437
|
+
catch (error) {
|
|
438
|
+
console.error('处理设备数据失败:', error);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
/**
|
|
442
|
+
* 过滤设备
|
|
443
|
+
*/
|
|
444
|
+
filterDevice(device) {
|
|
445
|
+
// 按名称过滤
|
|
446
|
+
if (this.options.nameFilter) {
|
|
447
|
+
const deviceName = device.name || '';
|
|
448
|
+
const filters = Array.isArray(this.options.nameFilter)
|
|
449
|
+
? this.options.nameFilter
|
|
450
|
+
: [this.options.nameFilter];
|
|
451
|
+
const nameMatch = filters.some(filter => {
|
|
452
|
+
if (typeof filter === 'string') {
|
|
453
|
+
return deviceName.includes(filter);
|
|
454
|
+
}
|
|
455
|
+
return filter.test(deviceName);
|
|
456
|
+
});
|
|
457
|
+
if (!nameMatch)
|
|
458
|
+
return false;
|
|
459
|
+
}
|
|
460
|
+
// 按信号强度过滤
|
|
461
|
+
if (this.options.rssiThreshold !== undefined) {
|
|
462
|
+
if (device.RSSI < this.options.rssiThreshold) {
|
|
463
|
+
return false;
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
// 按服务UUID过滤
|
|
467
|
+
if (this.options.services && this.options.services.length > 0) {
|
|
468
|
+
if (!device.serviceUuids)
|
|
469
|
+
return false;
|
|
470
|
+
const hasMatchingService = this.options.services.some(service => device.serviceUuids.some(uuid => uuid.toLowerCase().includes(service.toLowerCase())));
|
|
471
|
+
if (!hasMatchingService)
|
|
472
|
+
return false;
|
|
473
|
+
}
|
|
474
|
+
// 按制造商数据过滤
|
|
475
|
+
if (this.options.manufacturerFilter !== undefined) {
|
|
476
|
+
if (!device.manufacturerData)
|
|
477
|
+
return false;
|
|
478
|
+
const filters = Array.isArray(this.options.manufacturerFilter)
|
|
479
|
+
? this.options.manufacturerFilter
|
|
480
|
+
: [this.options.manufacturerFilter];
|
|
481
|
+
const manufacturerIds = Object.keys(device.manufacturerData).map(id => parseInt(id, 16));
|
|
482
|
+
const hasMatchingManufacturer = filters.some(filter => manufacturerIds.includes(filter));
|
|
483
|
+
if (!hasMatchingManufacturer)
|
|
484
|
+
return false;
|
|
485
|
+
}
|
|
486
|
+
return true;
|
|
487
|
+
}
|
|
488
|
+
/**
|
|
489
|
+
* 处理超时
|
|
490
|
+
*/
|
|
491
|
+
handleTimeout() {
|
|
492
|
+
var _a, _b;
|
|
493
|
+
this.stopScan();
|
|
494
|
+
(_b = (_a = this.callbacks).onTimeout) === null || _b === void 0 ? void 0 : _b.call(_a);
|
|
495
|
+
}
|
|
496
|
+
/**
|
|
497
|
+
* 处理错误
|
|
498
|
+
*/
|
|
499
|
+
handleError(error) {
|
|
500
|
+
var _a, _b;
|
|
501
|
+
const bleError = handlePlatformError(error, this.getPlatform());
|
|
502
|
+
this.setState(ScanState.ERROR);
|
|
503
|
+
(_b = (_a = this.callbacks).onError) === null || _b === void 0 ? void 0 : _b.call(_a, bleError);
|
|
504
|
+
}
|
|
505
|
+
/**
|
|
506
|
+
* 设置状态
|
|
507
|
+
*/
|
|
508
|
+
setState(state) {
|
|
509
|
+
this.state = state;
|
|
510
|
+
}
|
|
511
|
+
/**
|
|
512
|
+
* 清除超时定时器
|
|
513
|
+
*/
|
|
514
|
+
clearTimeout() {
|
|
515
|
+
if (this.timeoutId !== null) {
|
|
516
|
+
window.clearTimeout(this.timeoutId);
|
|
517
|
+
this.timeoutId = null;
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
/**
|
|
521
|
+
* 获取默认选项
|
|
522
|
+
*/
|
|
523
|
+
getDefaultOptions() {
|
|
524
|
+
return {
|
|
525
|
+
timeout: 10000,
|
|
526
|
+
allowDuplicates: false,
|
|
527
|
+
};
|
|
528
|
+
}
|
|
529
|
+
/**
|
|
530
|
+
* 触发适配器状态变化
|
|
531
|
+
*/
|
|
532
|
+
emitAdapterStateChange(state) {
|
|
533
|
+
this.adapterStateListeners.forEach(listener => listener(state));
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
/**
|
|
538
|
+
* 获取当前运行平台
|
|
539
|
+
*/
|
|
540
|
+
function getPlatform() {
|
|
541
|
+
var _a;
|
|
542
|
+
// #ifdef APP-PLUS
|
|
543
|
+
const systemInfo = uni.getSystemInfoSync();
|
|
544
|
+
const platform = ((_a = systemInfo.platform) === null || _a === void 0 ? void 0 : _a.toLowerCase()) || '';
|
|
545
|
+
if (platform === 'android') {
|
|
546
|
+
return Platform.ANDROID;
|
|
547
|
+
}
|
|
548
|
+
else if (platform === 'ios') {
|
|
549
|
+
return Platform.IOS;
|
|
550
|
+
}
|
|
551
|
+
else if (platform === 'harmony' || platform === 'harmonyos') {
|
|
552
|
+
return Platform.HARMONY;
|
|
553
|
+
}
|
|
554
|
+
// #endif
|
|
555
|
+
// #ifdef HARMONY
|
|
556
|
+
return Platform.HARMONY;
|
|
557
|
+
}
|
|
558
|
+
/**
|
|
559
|
+
* 检查是否在指定平台运行
|
|
560
|
+
*/
|
|
561
|
+
function isPlatform(platform) {
|
|
562
|
+
return getPlatform() === platform;
|
|
563
|
+
}
|
|
564
|
+
/**
|
|
565
|
+
* 检查是否是移动端APP环境
|
|
566
|
+
*/
|
|
567
|
+
function isApp() {
|
|
568
|
+
// #ifdef APP-PLUS
|
|
569
|
+
return true;
|
|
570
|
+
}
|
|
571
|
+
/**
|
|
572
|
+
* 获取平台名称
|
|
573
|
+
*/
|
|
574
|
+
function getPlatformName() {
|
|
575
|
+
const platform = getPlatform();
|
|
576
|
+
switch (platform) {
|
|
577
|
+
case Platform.ANDROID:
|
|
578
|
+
return 'Android';
|
|
579
|
+
case Platform.IOS:
|
|
580
|
+
return 'iOS';
|
|
581
|
+
case Platform.HARMONY:
|
|
582
|
+
return 'HarmonyOS';
|
|
583
|
+
default:
|
|
584
|
+
return 'Unknown';
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
/**
|
|
589
|
+
* 安卓权限列表
|
|
590
|
+
*/
|
|
591
|
+
const ANDROID_PERMISSIONS = {
|
|
592
|
+
// Android 12+ 需要的新权限
|
|
593
|
+
BLUETOOTH_SCAN: 'android.permission.BLUETOOTH_SCAN',
|
|
594
|
+
BLUETOOTH_CONNECT: 'android.permission.BLUETOOTH_CONNECT',
|
|
595
|
+
// Android 11 及以下需要的权限
|
|
596
|
+
BLUETOOTH: 'android.permission.BLUETOOTH',
|
|
597
|
+
BLUETOOTH_ADMIN: 'android.permission.BLUETOOTH_ADMIN',
|
|
598
|
+
// 位置权限(扫描需要)
|
|
599
|
+
ACCESS_FINE_LOCATION: 'android.permission.ACCESS_FINE_LOCATION'};
|
|
600
|
+
/**
|
|
601
|
+
* iOS 不需要显式请求蓝牙权限,由系统自动处理
|
|
602
|
+
* 但需要在 Info.plist 中配置 NSBluetoothAlwaysUsageDescription
|
|
603
|
+
*/
|
|
604
|
+
/**
|
|
605
|
+
* 鸿蒙权限列表
|
|
606
|
+
*/
|
|
607
|
+
const HARMONY_PERMISSIONS = {
|
|
608
|
+
ACCESS_BLUETOOTH: 'ohos.permission.ACCESS_BLUETOOTH',
|
|
609
|
+
LOCATION: 'ohos.permission.LOCATION',
|
|
610
|
+
APPROXIMATELY_LOCATION: 'ohos.permission.APPROXIMATELY_LOCATION',
|
|
611
|
+
};
|
|
612
|
+
/**
|
|
613
|
+
* 检查权限
|
|
614
|
+
*/
|
|
615
|
+
function checkPermissions() {
|
|
616
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
617
|
+
const platform = getPlatform();
|
|
618
|
+
switch (platform) {
|
|
619
|
+
case Platform.ANDROID:
|
|
620
|
+
return checkAndroidPermissions();
|
|
621
|
+
case Platform.IOS:
|
|
622
|
+
return checkIOSPermissions();
|
|
623
|
+
case Platform.HARMONY:
|
|
624
|
+
return checkHarmonyPermissions();
|
|
625
|
+
default:
|
|
626
|
+
return false;
|
|
627
|
+
}
|
|
628
|
+
});
|
|
629
|
+
}
|
|
630
|
+
/**
|
|
631
|
+
* 请求权限
|
|
632
|
+
*/
|
|
633
|
+
function requestPermissions() {
|
|
634
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
635
|
+
const platform = getPlatform();
|
|
636
|
+
switch (platform) {
|
|
637
|
+
case Platform.ANDROID:
|
|
638
|
+
return requestAndroidPermissions();
|
|
639
|
+
case Platform.IOS:
|
|
640
|
+
return requestIOSPermissions();
|
|
641
|
+
case Platform.HARMONY:
|
|
642
|
+
return requestHarmonyPermissions();
|
|
643
|
+
default:
|
|
644
|
+
return false;
|
|
645
|
+
}
|
|
646
|
+
});
|
|
647
|
+
}
|
|
648
|
+
/**
|
|
649
|
+
* 检查安卓权限
|
|
650
|
+
*/
|
|
651
|
+
function checkAndroidPermissions() {
|
|
652
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
653
|
+
return new Promise((resolve) => {
|
|
654
|
+
var _a;
|
|
655
|
+
// #ifdef APP-PLUS
|
|
656
|
+
const mainActivity = plus.android.runtimeMainActivity();
|
|
657
|
+
const PackageManager = plus.android.importClass('android.content.pm.PackageManager');
|
|
658
|
+
// 获取系统版本
|
|
659
|
+
const systemInfo = uni.getSystemInfoSync();
|
|
660
|
+
const osVersion = parseInt(((_a = systemInfo.system) === null || _a === void 0 ? void 0 : _a.split('.')[0]) || '0');
|
|
661
|
+
let permissions;
|
|
662
|
+
if (osVersion >= 12) {
|
|
663
|
+
// Android 12+ 使用新权限
|
|
664
|
+
permissions = [
|
|
665
|
+
ANDROID_PERMISSIONS.BLUETOOTH_SCAN,
|
|
666
|
+
ANDROID_PERMISSIONS.BLUETOOTH_CONNECT,
|
|
667
|
+
ANDROID_PERMISSIONS.ACCESS_FINE_LOCATION,
|
|
668
|
+
];
|
|
669
|
+
}
|
|
670
|
+
else {
|
|
671
|
+
// Android 11 及以下使用旧权限
|
|
672
|
+
permissions = [
|
|
673
|
+
ANDROID_PERMISSIONS.BLUETOOTH,
|
|
674
|
+
ANDROID_PERMISSIONS.BLUETOOTH_ADMIN,
|
|
675
|
+
ANDROID_PERMISSIONS.ACCESS_FINE_LOCATION,
|
|
676
|
+
];
|
|
677
|
+
}
|
|
678
|
+
for (const permission of permissions) {
|
|
679
|
+
const result = mainActivity.checkSelfPermission(permission);
|
|
680
|
+
if (result !== PackageManager.PERMISSION_GRANTED) {
|
|
681
|
+
resolve(false);
|
|
682
|
+
return;
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
resolve(true);
|
|
686
|
+
// #endif
|
|
687
|
+
// #ifndef APP-PLUS
|
|
688
|
+
resolve(true);
|
|
689
|
+
// #endif
|
|
690
|
+
});
|
|
691
|
+
});
|
|
692
|
+
}
|
|
693
|
+
/**
|
|
694
|
+
* 请求安卓权限
|
|
695
|
+
*/
|
|
696
|
+
function requestAndroidPermissions() {
|
|
697
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
698
|
+
return new Promise((resolve) => {
|
|
699
|
+
var _a;
|
|
700
|
+
// #ifdef APP-PLUS
|
|
701
|
+
plus.android.runtimeMainActivity();
|
|
702
|
+
const systemInfo = uni.getSystemInfoSync();
|
|
703
|
+
const osVersion = parseInt(((_a = systemInfo.system) === null || _a === void 0 ? void 0 : _a.split('.')[0]) || '0');
|
|
704
|
+
let permissions;
|
|
705
|
+
if (osVersion >= 12) {
|
|
706
|
+
permissions = [
|
|
707
|
+
ANDROID_PERMISSIONS.BLUETOOTH_SCAN,
|
|
708
|
+
ANDROID_PERMISSIONS.BLUETOOTH_CONNECT,
|
|
709
|
+
ANDROID_PERMISSIONS.ACCESS_FINE_LOCATION,
|
|
710
|
+
];
|
|
711
|
+
}
|
|
712
|
+
else {
|
|
713
|
+
permissions = [
|
|
714
|
+
ANDROID_PERMISSIONS.BLUETOOTH,
|
|
715
|
+
ANDROID_PERMISSIONS.BLUETOOTH_ADMIN,
|
|
716
|
+
ANDROID_PERMISSIONS.ACCESS_FINE_LOCATION,
|
|
717
|
+
];
|
|
718
|
+
}
|
|
719
|
+
// 使用 uniapp 的权限请求API
|
|
720
|
+
uni.requestAndroidPermission(permissions[0], (result) => {
|
|
721
|
+
if (result.granted) {
|
|
722
|
+
resolve(true);
|
|
723
|
+
}
|
|
724
|
+
else {
|
|
725
|
+
resolve(false);
|
|
726
|
+
}
|
|
727
|
+
}, (error) => {
|
|
728
|
+
console.error('请求权限失败:', error);
|
|
729
|
+
resolve(false);
|
|
730
|
+
});
|
|
731
|
+
// #endif
|
|
732
|
+
// #ifndef APP-PLUS
|
|
733
|
+
resolve(true);
|
|
734
|
+
// #endif
|
|
735
|
+
});
|
|
736
|
+
});
|
|
737
|
+
}
|
|
738
|
+
/**
|
|
739
|
+
* 检查 iOS 权限
|
|
740
|
+
*/
|
|
741
|
+
function checkIOSPermissions() {
|
|
742
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
743
|
+
// iOS 权限由系统自动管理
|
|
744
|
+
// 需要在 manifest.json 或原生工程中配置 Info.plist
|
|
745
|
+
return true;
|
|
746
|
+
});
|
|
747
|
+
}
|
|
748
|
+
/**
|
|
749
|
+
* 请求 iOS 权限
|
|
750
|
+
*/
|
|
751
|
+
function requestIOSPermissions() {
|
|
752
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
753
|
+
// iOS 权限由系统自动管理
|
|
754
|
+
return true;
|
|
755
|
+
});
|
|
756
|
+
}
|
|
757
|
+
/**
|
|
758
|
+
* 检查鸿蒙权限
|
|
759
|
+
*/
|
|
760
|
+
function checkHarmonyPermissions() {
|
|
761
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
762
|
+
// #ifdef HARMONY
|
|
763
|
+
// 鸿蒙权限检查逻辑
|
|
764
|
+
try {
|
|
765
|
+
const atManager = require('@ohos.abilityAccessCtrl').createAtManager();
|
|
766
|
+
const tokenId = require('@ohos.process').process.accessTokenId;
|
|
767
|
+
const permissions = [
|
|
768
|
+
HARMONY_PERMISSIONS.ACCESS_BLUETOOTH,
|
|
769
|
+
HARMONY_PERMISSIONS.LOCATION,
|
|
770
|
+
];
|
|
771
|
+
for (const permission of permissions) {
|
|
772
|
+
const result = yield atManager.verifyAccessToken(tokenId, permission);
|
|
773
|
+
if (result !== 0) { // PERMISSION_GRANTED = 0
|
|
774
|
+
return false;
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
return true;
|
|
778
|
+
}
|
|
779
|
+
catch (error) {
|
|
780
|
+
console.error('检查鸿蒙权限失败:', error);
|
|
781
|
+
return false;
|
|
782
|
+
}
|
|
783
|
+
// #endif
|
|
784
|
+
// #ifndef HARMONY
|
|
785
|
+
return true;
|
|
786
|
+
// #endif
|
|
787
|
+
});
|
|
788
|
+
}
|
|
789
|
+
/**
|
|
790
|
+
* 请求鸿蒙权限
|
|
791
|
+
*/
|
|
792
|
+
function requestHarmonyPermissions() {
|
|
793
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
794
|
+
// #ifdef HARMONY
|
|
795
|
+
try {
|
|
796
|
+
const atManager = require('@ohos.abilityAccessCtrl').createAtManager();
|
|
797
|
+
const context = getContext(this);
|
|
798
|
+
const permissions = [
|
|
799
|
+
HARMONY_PERMISSIONS.ACCESS_BLUETOOTH,
|
|
800
|
+
HARMONY_PERMISSIONS.LOCATION,
|
|
801
|
+
];
|
|
802
|
+
const result = yield atManager.requestPermissionsFromUser(context, permissions);
|
|
803
|
+
return result.authResults.every((authResult) => authResult === 0);
|
|
804
|
+
}
|
|
805
|
+
catch (error) {
|
|
806
|
+
console.error('请求鸿蒙权限失败:', error);
|
|
807
|
+
return false;
|
|
808
|
+
}
|
|
809
|
+
// #endif
|
|
810
|
+
// #ifndef HARMONY
|
|
811
|
+
return true;
|
|
812
|
+
// #endif
|
|
813
|
+
});
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
/**
|
|
817
|
+
* UniApp 蓝牙扫描器
|
|
818
|
+
* 支持安卓、iOS 和鸿蒙平台
|
|
819
|
+
*/
|
|
820
|
+
class UniAppScanner extends BaseScanner {
|
|
821
|
+
constructor() {
|
|
822
|
+
super(...arguments);
|
|
823
|
+
this.adapterStateChangeHandler = null;
|
|
824
|
+
}
|
|
825
|
+
getPlatform() {
|
|
826
|
+
var _a;
|
|
827
|
+
const systemInfo = uni.getSystemInfoSync();
|
|
828
|
+
const platform = ((_a = systemInfo.platform) === null || _a === void 0 ? void 0 : _a.toLowerCase()) || '';
|
|
829
|
+
if (platform === 'android') {
|
|
830
|
+
return Platform.ANDROID;
|
|
831
|
+
}
|
|
832
|
+
else if (platform === 'ios') {
|
|
833
|
+
return Platform.IOS;
|
|
834
|
+
}
|
|
835
|
+
else if (platform === 'harmony' || platform === 'harmonyos') {
|
|
836
|
+
return Platform.HARMONY;
|
|
837
|
+
}
|
|
838
|
+
return Platform.UNKNOWN;
|
|
839
|
+
}
|
|
840
|
+
init() {
|
|
841
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
842
|
+
return new Promise((resolve, reject) => {
|
|
843
|
+
// 打开蓝牙适配器
|
|
844
|
+
uni.openBluetoothAdapter({
|
|
845
|
+
success: () => {
|
|
846
|
+
this.initialized = true;
|
|
847
|
+
resolve();
|
|
848
|
+
},
|
|
849
|
+
fail: (err) => {
|
|
850
|
+
reject(createError(ErrorCodes.ADAPTER_NOT_AVAILABLE, `初始化蓝牙适配器失败: ${err.errMsg}`, err.errCode, err));
|
|
851
|
+
},
|
|
852
|
+
});
|
|
853
|
+
});
|
|
854
|
+
});
|
|
855
|
+
}
|
|
856
|
+
startScanInternal() {
|
|
857
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
858
|
+
return new Promise((resolve, reject) => {
|
|
859
|
+
const scanOptions = {
|
|
860
|
+
allowDuplicatesKey: this.options.allowDuplicates || false,
|
|
861
|
+
success: () => {
|
|
862
|
+
// 监听设备发现
|
|
863
|
+
this.startDeviceDiscovery();
|
|
864
|
+
resolve();
|
|
865
|
+
},
|
|
866
|
+
fail: (err) => {
|
|
867
|
+
reject(Errors.scanFailed(err.errMsg));
|
|
868
|
+
},
|
|
869
|
+
};
|
|
870
|
+
// 添加服务UUID过滤
|
|
871
|
+
if (this.options.services && this.options.services.length > 0) {
|
|
872
|
+
scanOptions.services = this.options.services;
|
|
873
|
+
}
|
|
874
|
+
uni.startBluetoothDevicesDiscovery(scanOptions);
|
|
875
|
+
});
|
|
876
|
+
});
|
|
877
|
+
}
|
|
878
|
+
stopScanInternal() {
|
|
879
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
880
|
+
return new Promise((resolve, reject) => {
|
|
881
|
+
uni.stopBluetoothDevicesDiscovery({
|
|
882
|
+
success: () => {
|
|
883
|
+
this.stopDeviceDiscovery();
|
|
884
|
+
resolve();
|
|
885
|
+
},
|
|
886
|
+
fail: (err) => {
|
|
887
|
+
reject(Errors.scanFailed(err.errMsg));
|
|
888
|
+
},
|
|
889
|
+
});
|
|
890
|
+
});
|
|
891
|
+
});
|
|
892
|
+
}
|
|
893
|
+
checkPermissions() {
|
|
894
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
895
|
+
return checkPermissions();
|
|
896
|
+
});
|
|
897
|
+
}
|
|
898
|
+
requestPermissions() {
|
|
899
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
900
|
+
return requestPermissions();
|
|
901
|
+
});
|
|
902
|
+
}
|
|
903
|
+
onAdapterStateChange(callback) {
|
|
904
|
+
this.adapterStateListeners.push(callback);
|
|
905
|
+
// 如果还没有监听,开始监听
|
|
906
|
+
if (!this.adapterStateChangeHandler) {
|
|
907
|
+
this.adapterStateChangeHandler = (res) => {
|
|
908
|
+
this.emitAdapterStateChange({
|
|
909
|
+
available: res.available,
|
|
910
|
+
discovering: res.discovering,
|
|
911
|
+
});
|
|
912
|
+
};
|
|
913
|
+
uni.onBluetoothAdapterStateChange(this.adapterStateChangeHandler);
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
destroy() {
|
|
917
|
+
super.destroy();
|
|
918
|
+
// 移除适配器状态监听
|
|
919
|
+
if (this.adapterStateChangeHandler) {
|
|
920
|
+
uni.offBluetoothAdapterStateChange(this.adapterStateChangeHandler);
|
|
921
|
+
this.adapterStateChangeHandler = null;
|
|
922
|
+
}
|
|
923
|
+
// 关闭蓝牙适配器
|
|
924
|
+
uni.closeBluetoothAdapter({
|
|
925
|
+
success: () => {
|
|
926
|
+
console.log('蓝牙适配器已关闭');
|
|
927
|
+
},
|
|
928
|
+
fail: (err) => {
|
|
929
|
+
console.error('关闭蓝牙适配器失败:', err);
|
|
930
|
+
},
|
|
931
|
+
});
|
|
932
|
+
}
|
|
933
|
+
/**
|
|
934
|
+
* 开始监听设备发现
|
|
935
|
+
*/
|
|
936
|
+
startDeviceDiscovery() {
|
|
937
|
+
uni.onBluetoothDeviceFound((res) => {
|
|
938
|
+
const devices = res.devices || [];
|
|
939
|
+
devices.forEach((device) => {
|
|
940
|
+
this.handleDeviceFound(device);
|
|
941
|
+
});
|
|
942
|
+
});
|
|
943
|
+
}
|
|
944
|
+
/**
|
|
945
|
+
* 停止监听设备发现
|
|
946
|
+
*/
|
|
947
|
+
stopDeviceDiscovery() {
|
|
948
|
+
uni.offBluetoothDeviceFound();
|
|
949
|
+
}
|
|
950
|
+
/**
|
|
951
|
+
* 获取蓝牙适配器状态
|
|
952
|
+
*/
|
|
953
|
+
getAdapterState() {
|
|
954
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
955
|
+
return new Promise((resolve, reject) => {
|
|
956
|
+
uni.getBluetoothAdapterState({
|
|
957
|
+
success: (res) => {
|
|
958
|
+
resolve({
|
|
959
|
+
available: res.available,
|
|
960
|
+
discovering: res.discovering,
|
|
961
|
+
});
|
|
962
|
+
},
|
|
963
|
+
fail: (err) => {
|
|
964
|
+
reject(Errors.adapterNotAvailable());
|
|
965
|
+
},
|
|
966
|
+
});
|
|
967
|
+
});
|
|
968
|
+
});
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
/**
|
|
973
|
+
* Android 原生蓝牙扫描器
|
|
974
|
+
* 使用 uni-app 的 Native.js 调用 Android 原生 API
|
|
975
|
+
*/
|
|
976
|
+
class AndroidNativeScanner extends BaseScanner {
|
|
977
|
+
constructor() {
|
|
978
|
+
super(...arguments);
|
|
979
|
+
this.bluetoothAdapter = null;
|
|
980
|
+
this.bluetoothLeScanner = null;
|
|
981
|
+
this.scanCallback = null;
|
|
982
|
+
this.adapterStateReceiver = null;
|
|
983
|
+
this.legacyScanCallback = null;
|
|
984
|
+
}
|
|
985
|
+
getPlatform() {
|
|
986
|
+
return Platform.ANDROID;
|
|
987
|
+
}
|
|
988
|
+
init() {
|
|
989
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
990
|
+
return new Promise((resolve, reject) => {
|
|
991
|
+
try {
|
|
992
|
+
// #ifdef APP-PLUS
|
|
993
|
+
const mainActivity = plus.android.runtimeMainActivity();
|
|
994
|
+
const BluetoothManager = plus.android.importClass('android.bluetooth.BluetoothManager');
|
|
995
|
+
const bluetoothManager = mainActivity.getSystemService('bluetooth');
|
|
996
|
+
this.bluetoothAdapter = bluetoothManager.getAdapter();
|
|
997
|
+
if (!this.bluetoothAdapter) {
|
|
998
|
+
reject(Errors.adapterNotAvailable());
|
|
999
|
+
return;
|
|
1000
|
+
}
|
|
1001
|
+
if (!this.bluetoothAdapter.isEnabled()) {
|
|
1002
|
+
reject(Errors.bluetoothDisabled());
|
|
1003
|
+
return;
|
|
1004
|
+
}
|
|
1005
|
+
this.initialized = true;
|
|
1006
|
+
resolve();
|
|
1007
|
+
// #endif
|
|
1008
|
+
// #ifndef APP-PLUS
|
|
1009
|
+
reject(createError(ErrorCodes.UNSUPPORTED_PLATFORM, '非 APP 环境不支持原生 Android 扫描'));
|
|
1010
|
+
// #endif
|
|
1011
|
+
}
|
|
1012
|
+
catch (error) {
|
|
1013
|
+
reject(handlePlatformError(error, 'android'));
|
|
1014
|
+
}
|
|
1015
|
+
});
|
|
1016
|
+
});
|
|
1017
|
+
}
|
|
1018
|
+
startScanInternal() {
|
|
1019
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1020
|
+
return new Promise((resolve, reject) => {
|
|
1021
|
+
var _a;
|
|
1022
|
+
try {
|
|
1023
|
+
// #ifdef APP-PLUS
|
|
1024
|
+
const systemInfo = uni.getSystemInfoSync();
|
|
1025
|
+
const osVersion = parseInt(((_a = systemInfo.system) === null || _a === void 0 ? void 0 : _a.split('.')[0]) || '0');
|
|
1026
|
+
if (osVersion >= 21 && this.bluetoothAdapter.getBluetoothLeScanner) {
|
|
1027
|
+
// Android 5.0+ 使用新的扫描 API
|
|
1028
|
+
this.startNewScanAPI(resolve, reject);
|
|
1029
|
+
}
|
|
1030
|
+
else {
|
|
1031
|
+
// 旧版扫描 API
|
|
1032
|
+
this.startLegacyScan(resolve, reject);
|
|
1033
|
+
}
|
|
1034
|
+
// #endif
|
|
1035
|
+
// #ifndef APP-PLUS
|
|
1036
|
+
reject(Errors.unsupportedPlatform());
|
|
1037
|
+
// #endif
|
|
1038
|
+
}
|
|
1039
|
+
catch (error) {
|
|
1040
|
+
reject(handlePlatformError(error, 'android'));
|
|
1041
|
+
}
|
|
1042
|
+
});
|
|
1043
|
+
});
|
|
1044
|
+
}
|
|
1045
|
+
stopScanInternal() {
|
|
1046
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1047
|
+
return new Promise((resolve, reject) => {
|
|
1048
|
+
try {
|
|
1049
|
+
// #ifdef APP-PLUS
|
|
1050
|
+
if (this.bluetoothLeScanner && this.scanCallback) {
|
|
1051
|
+
this.bluetoothLeScanner.stopScan(this.scanCallback);
|
|
1052
|
+
}
|
|
1053
|
+
else if (this.bluetoothAdapter) {
|
|
1054
|
+
this.bluetoothAdapter.stopLeScan(this.legacyScanCallback);
|
|
1055
|
+
}
|
|
1056
|
+
resolve();
|
|
1057
|
+
// #endif
|
|
1058
|
+
// #ifndef APP-PLUS
|
|
1059
|
+
reject(Errors.unsupportedPlatform());
|
|
1060
|
+
// #endif
|
|
1061
|
+
}
|
|
1062
|
+
catch (error) {
|
|
1063
|
+
reject(handlePlatformError(error, 'android'));
|
|
1064
|
+
}
|
|
1065
|
+
});
|
|
1066
|
+
});
|
|
1067
|
+
}
|
|
1068
|
+
checkPermissions() {
|
|
1069
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1070
|
+
var _a;
|
|
1071
|
+
// #ifdef APP-PLUS
|
|
1072
|
+
const mainActivity = plus.android.runtimeMainActivity();
|
|
1073
|
+
const PackageManager = plus.android.importClass('android.content.pm.PackageManager');
|
|
1074
|
+
const systemInfo = uni.getSystemInfoSync();
|
|
1075
|
+
const osVersion = parseInt(((_a = systemInfo.system) === null || _a === void 0 ? void 0 : _a.split('.')[0]) || '0');
|
|
1076
|
+
let permissions;
|
|
1077
|
+
if (osVersion >= 12) {
|
|
1078
|
+
permissions = [
|
|
1079
|
+
'android.permission.BLUETOOTH_SCAN',
|
|
1080
|
+
'android.permission.BLUETOOTH_CONNECT',
|
|
1081
|
+
'android.permission.ACCESS_FINE_LOCATION',
|
|
1082
|
+
];
|
|
1083
|
+
}
|
|
1084
|
+
else {
|
|
1085
|
+
permissions = [
|
|
1086
|
+
'android.permission.BLUETOOTH',
|
|
1087
|
+
'android.permission.BLUETOOTH_ADMIN',
|
|
1088
|
+
'android.permission.ACCESS_FINE_LOCATION',
|
|
1089
|
+
];
|
|
1090
|
+
}
|
|
1091
|
+
for (const permission of permissions) {
|
|
1092
|
+
const result = mainActivity.checkSelfPermission(permission);
|
|
1093
|
+
if (result !== PackageManager.PERMISSION_GRANTED) {
|
|
1094
|
+
return false;
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
return true;
|
|
1098
|
+
// #endif
|
|
1099
|
+
});
|
|
1100
|
+
}
|
|
1101
|
+
requestPermissions() {
|
|
1102
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1103
|
+
// #ifdef APP-PLUS
|
|
1104
|
+
return new Promise((resolve) => {
|
|
1105
|
+
var _a;
|
|
1106
|
+
const systemInfo = uni.getSystemInfoSync();
|
|
1107
|
+
const osVersion = parseInt(((_a = systemInfo.system) === null || _a === void 0 ? void 0 : _a.split('.')[0]) || '0');
|
|
1108
|
+
let permissions;
|
|
1109
|
+
if (osVersion >= 12) {
|
|
1110
|
+
permissions = [
|
|
1111
|
+
'android.permission.BLUETOOTH_SCAN',
|
|
1112
|
+
'android.permission.BLUETOOTH_CONNECT',
|
|
1113
|
+
'android.permission.ACCESS_FINE_LOCATION',
|
|
1114
|
+
];
|
|
1115
|
+
}
|
|
1116
|
+
else {
|
|
1117
|
+
permissions = [
|
|
1118
|
+
'android.permission.BLUETOOTH',
|
|
1119
|
+
'android.permission.BLUETOOTH_ADMIN',
|
|
1120
|
+
'android.permission.ACCESS_FINE_LOCATION',
|
|
1121
|
+
];
|
|
1122
|
+
}
|
|
1123
|
+
uni.requestAndroidPermission(permissions[0], (result) => {
|
|
1124
|
+
resolve(result.granted);
|
|
1125
|
+
}, () => {
|
|
1126
|
+
resolve(false);
|
|
1127
|
+
});
|
|
1128
|
+
});
|
|
1129
|
+
// #endif
|
|
1130
|
+
});
|
|
1131
|
+
}
|
|
1132
|
+
onAdapterStateChange(callback) {
|
|
1133
|
+
this.adapterStateListeners.push(callback);
|
|
1134
|
+
// #ifdef APP-PLUS
|
|
1135
|
+
if (!this.adapterStateReceiver) {
|
|
1136
|
+
plus.android.runtimeMainActivity();
|
|
1137
|
+
const IntentFilter = plus.android.importClass('android.content.IntentFilter');
|
|
1138
|
+
const BluetoothAdapter = plus.android.importClass('android.bluetooth.BluetoothAdapter');
|
|
1139
|
+
const filter = new IntentFilter();
|
|
1140
|
+
filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
|
|
1141
|
+
// 这里简化处理,实际应该创建 BroadcastReceiver
|
|
1142
|
+
// 由于 Native.js 限制,建议使用 uni-app 的标准 API 监听状态
|
|
1143
|
+
}
|
|
1144
|
+
// #endif
|
|
1145
|
+
}
|
|
1146
|
+
destroy() {
|
|
1147
|
+
super.destroy();
|
|
1148
|
+
this.bluetoothAdapter = null;
|
|
1149
|
+
this.bluetoothLeScanner = null;
|
|
1150
|
+
this.scanCallback = null;
|
|
1151
|
+
}
|
|
1152
|
+
/**
|
|
1153
|
+
* 使用新版扫描 API (Android 5.0+)
|
|
1154
|
+
*/
|
|
1155
|
+
startNewScanAPI(resolve, reject) {
|
|
1156
|
+
var _a;
|
|
1157
|
+
// #ifdef APP-PLUS
|
|
1158
|
+
try {
|
|
1159
|
+
const ScanSettings = plus.android.importClass('android.bluetooth.le.ScanSettings');
|
|
1160
|
+
const ScanFilter = plus.android.importClass('android.bluetooth.le.ScanFilter');
|
|
1161
|
+
const ScanCallback = plus.android.importClass('android.bluetooth.le.ScanCallback');
|
|
1162
|
+
this.bluetoothLeScanner = this.bluetoothAdapter.getBluetoothLeScanner();
|
|
1163
|
+
// 构建扫描设置
|
|
1164
|
+
const builder = new ScanSettings.Builder();
|
|
1165
|
+
// 设置扫描模式
|
|
1166
|
+
const scanMode = ((_a = this.platformConfig.android) === null || _a === void 0 ? void 0 : _a.scanMode) || ScanSettings.SCAN_MODE_LOW_LATENCY;
|
|
1167
|
+
builder.setScanMode(scanMode);
|
|
1168
|
+
const settings = builder.build();
|
|
1169
|
+
// 构建扫描过滤器
|
|
1170
|
+
const filters = [];
|
|
1171
|
+
if (this.options.services && this.options.services.length > 0) {
|
|
1172
|
+
// 可以添加服务 UUID 过滤
|
|
1173
|
+
}
|
|
1174
|
+
// 创建扫描回调
|
|
1175
|
+
const self = this;
|
|
1176
|
+
this.scanCallback = new ScanCallback({
|
|
1177
|
+
onScanResult: (callbackType, result) => {
|
|
1178
|
+
const device = result.getDevice();
|
|
1179
|
+
const rssi = result.getRssi();
|
|
1180
|
+
const scanRecord = result.getScanRecord();
|
|
1181
|
+
self.handleDeviceFound({
|
|
1182
|
+
deviceId: device.getAddress(),
|
|
1183
|
+
name: device.getName(),
|
|
1184
|
+
RSSI: rssi,
|
|
1185
|
+
advertisData: scanRecord ? scanRecord.getBytes() : null,
|
|
1186
|
+
});
|
|
1187
|
+
},
|
|
1188
|
+
onBatchScanResults: (results) => {
|
|
1189
|
+
results.forEach((result) => {
|
|
1190
|
+
this.scanCallback.onScanResult(0, result);
|
|
1191
|
+
});
|
|
1192
|
+
},
|
|
1193
|
+
onScanFailed: (errorCode) => {
|
|
1194
|
+
reject(Errors.scanFailed(`错误码: ${errorCode}`));
|
|
1195
|
+
},
|
|
1196
|
+
});
|
|
1197
|
+
this.bluetoothLeScanner.startScan(filters, settings, this.scanCallback);
|
|
1198
|
+
resolve();
|
|
1199
|
+
}
|
|
1200
|
+
catch (error) {
|
|
1201
|
+
reject(handlePlatformError(error, 'android'));
|
|
1202
|
+
}
|
|
1203
|
+
// #endif
|
|
1204
|
+
}
|
|
1205
|
+
/**
|
|
1206
|
+
* 使用旧版扫描 API
|
|
1207
|
+
*/
|
|
1208
|
+
startLegacyScan(resolve, reject) {
|
|
1209
|
+
// #ifdef APP-PLUS
|
|
1210
|
+
try {
|
|
1211
|
+
const self = this;
|
|
1212
|
+
this.legacyScanCallback = new plus.android.implements('android.bluetooth.BluetoothAdapter.LeScanCallback', {
|
|
1213
|
+
onLeScan: (device, rssi, scanRecord) => {
|
|
1214
|
+
self.handleDeviceFound({
|
|
1215
|
+
deviceId: device.getAddress(),
|
|
1216
|
+
name: device.getName(),
|
|
1217
|
+
RSSI: rssi,
|
|
1218
|
+
advertisData: scanRecord,
|
|
1219
|
+
});
|
|
1220
|
+
},
|
|
1221
|
+
});
|
|
1222
|
+
let serviceUuids = null;
|
|
1223
|
+
if (this.options.services && this.options.services.length > 0) {
|
|
1224
|
+
serviceUuids = this.options.services;
|
|
1225
|
+
}
|
|
1226
|
+
const started = this.bluetoothAdapter.startLeScan(serviceUuids, this.legacyScanCallback);
|
|
1227
|
+
if (started) {
|
|
1228
|
+
resolve();
|
|
1229
|
+
}
|
|
1230
|
+
else {
|
|
1231
|
+
reject(Errors.scanFailed('启动扫描失败'));
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
catch (error) {
|
|
1235
|
+
reject(handlePlatformError(error, 'android'));
|
|
1236
|
+
}
|
|
1237
|
+
// #endif
|
|
1238
|
+
}
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
/**
|
|
1242
|
+
* iOS 原生蓝牙扫描器
|
|
1243
|
+
* 使用 uni-app 的 Native.js 调用 iOS CoreBluetooth API
|
|
1244
|
+
*/
|
|
1245
|
+
class IOSNativeScanner extends BaseScanner {
|
|
1246
|
+
constructor() {
|
|
1247
|
+
super(...arguments);
|
|
1248
|
+
this.centralManager = null;
|
|
1249
|
+
this.delegate = null;
|
|
1250
|
+
}
|
|
1251
|
+
getPlatform() {
|
|
1252
|
+
return Platform.IOS;
|
|
1253
|
+
}
|
|
1254
|
+
init() {
|
|
1255
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1256
|
+
return new Promise((resolve, reject) => {
|
|
1257
|
+
var _a;
|
|
1258
|
+
try {
|
|
1259
|
+
// #ifdef APP-PLUS
|
|
1260
|
+
// 导入 iOS CoreBluetooth 框架
|
|
1261
|
+
const CBCentralManager = plus.ios.importClass('CBCentralManager');
|
|
1262
|
+
const CBUUID = plus.ios.importClass('CBUUID');
|
|
1263
|
+
const dispatch_queue_attr_make_with_qos_class = plus.ios.importClass('dispatch_queue_attr_make_with_qos_class');
|
|
1264
|
+
const dispatch_queue_create = plus.ios.importClass('dispatch_queue_create');
|
|
1265
|
+
// 创建队列
|
|
1266
|
+
const queue = dispatch_queue_create('com.ble.scanner', null);
|
|
1267
|
+
// 创建中央管理器
|
|
1268
|
+
this.centralManager = new CBCentralManager({
|
|
1269
|
+
queue: queue,
|
|
1270
|
+
options: {
|
|
1271
|
+
'CBCentralManagerOptionShowPowerAlertKey': true,
|
|
1272
|
+
'CBCentralManagerOptionRestoreIdentifierKey': (_a = this.platformConfig.ios) === null || _a === void 0 ? void 0 : _a.restoreIdentifier,
|
|
1273
|
+
},
|
|
1274
|
+
});
|
|
1275
|
+
if (!this.centralManager) {
|
|
1276
|
+
reject(Errors.adapterNotAvailable());
|
|
1277
|
+
return;
|
|
1278
|
+
}
|
|
1279
|
+
// 设置委托
|
|
1280
|
+
const self = this;
|
|
1281
|
+
this.delegate = plus.ios.implements('CBCentralManagerDelegate', {
|
|
1282
|
+
centralManagerDidUpdateState: (central) => {
|
|
1283
|
+
const state = central.state();
|
|
1284
|
+
// CBManagerStatePoweredOn = 5
|
|
1285
|
+
if (state === 5) {
|
|
1286
|
+
self.initialized = true;
|
|
1287
|
+
resolve();
|
|
1288
|
+
}
|
|
1289
|
+
else if (state === 4) { // CBManagerStatePoweredOff
|
|
1290
|
+
reject(Errors.bluetoothDisabled());
|
|
1291
|
+
}
|
|
1292
|
+
else {
|
|
1293
|
+
reject(createError(ErrorCodes.ADAPTER_NOT_AVAILABLE, `蓝牙适配器状态异常: ${state}`));
|
|
1294
|
+
}
|
|
1295
|
+
},
|
|
1296
|
+
centralManagerDidDiscoverPeripheralAdvertisementDataRSSI: (central, peripheral, advertisementData, RSSI) => {
|
|
1297
|
+
self.handlePeripheral(peripheral, advertisementData, RSSI);
|
|
1298
|
+
},
|
|
1299
|
+
});
|
|
1300
|
+
this.centralManager.setDelegate(this.delegate);
|
|
1301
|
+
// 检查当前状态
|
|
1302
|
+
const currentState = this.centralManager.state();
|
|
1303
|
+
if (currentState === 5) {
|
|
1304
|
+
this.initialized = true;
|
|
1305
|
+
resolve();
|
|
1306
|
+
}
|
|
1307
|
+
// 否则等待委托回调
|
|
1308
|
+
// #endif
|
|
1309
|
+
// #ifndef APP-PLUS
|
|
1310
|
+
reject(createError(ErrorCodes.UNSUPPORTED_PLATFORM, '非 APP 环境不支持原生 iOS 扫描'));
|
|
1311
|
+
// #endif
|
|
1312
|
+
}
|
|
1313
|
+
catch (error) {
|
|
1314
|
+
reject(handlePlatformError(error, 'ios'));
|
|
1315
|
+
}
|
|
1316
|
+
});
|
|
1317
|
+
});
|
|
1318
|
+
}
|
|
1319
|
+
startScanInternal() {
|
|
1320
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1321
|
+
return new Promise((resolve, reject) => {
|
|
1322
|
+
try {
|
|
1323
|
+
// #ifdef APP-PLUS
|
|
1324
|
+
const CBUUID = plus.ios.importClass('CBUUID');
|
|
1325
|
+
const NSArray = plus.ios.importClass('NSArray');
|
|
1326
|
+
const NSMutableDictionary = plus.ios.importClass('NSMutableDictionary');
|
|
1327
|
+
// 构建服务 UUID 列表
|
|
1328
|
+
let serviceUUIDs = null;
|
|
1329
|
+
if (this.options.services && this.options.services.length > 0) {
|
|
1330
|
+
const uuids = [];
|
|
1331
|
+
this.options.services.forEach((uuid) => {
|
|
1332
|
+
const cbUuid = CBUUID.UUIDWithString(uuid);
|
|
1333
|
+
uuids.push(cbUuid);
|
|
1334
|
+
});
|
|
1335
|
+
serviceUUIDs = NSArray.arrayWithArray(uuids);
|
|
1336
|
+
}
|
|
1337
|
+
// 构建扫描选项
|
|
1338
|
+
const options = NSMutableDictionary.dictionary();
|
|
1339
|
+
options.setObjectForKey(this.options.allowDuplicates ? 1 : 0, 'CBCentralManagerScanOptionAllowDuplicatesKey');
|
|
1340
|
+
// 开始扫描
|
|
1341
|
+
this.centralManager.scanForPeripheralsWithServicesOptions(serviceUUIDs, options);
|
|
1342
|
+
resolve();
|
|
1343
|
+
// #endif
|
|
1344
|
+
// #ifndef APP-PLUS
|
|
1345
|
+
reject(Errors.unsupportedPlatform());
|
|
1346
|
+
// #endif
|
|
1347
|
+
}
|
|
1348
|
+
catch (error) {
|
|
1349
|
+
reject(handlePlatformError(error, 'ios'));
|
|
1350
|
+
}
|
|
1351
|
+
});
|
|
1352
|
+
});
|
|
1353
|
+
}
|
|
1354
|
+
stopScanInternal() {
|
|
1355
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1356
|
+
return new Promise((resolve, reject) => {
|
|
1357
|
+
try {
|
|
1358
|
+
// #ifdef APP-PLUS
|
|
1359
|
+
if (this.centralManager) {
|
|
1360
|
+
this.centralManager.stopScan();
|
|
1361
|
+
}
|
|
1362
|
+
resolve();
|
|
1363
|
+
// #endif
|
|
1364
|
+
// #ifndef APP-PLUS
|
|
1365
|
+
reject(Errors.unsupportedPlatform());
|
|
1366
|
+
// #endif
|
|
1367
|
+
}
|
|
1368
|
+
catch (error) {
|
|
1369
|
+
reject(handlePlatformError(error, 'ios'));
|
|
1370
|
+
}
|
|
1371
|
+
});
|
|
1372
|
+
});
|
|
1373
|
+
}
|
|
1374
|
+
checkPermissions() {
|
|
1375
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1376
|
+
// iOS 权限由系统自动管理
|
|
1377
|
+
// 需要在原生工程的 Info.plist 中配置 NSBluetoothAlwaysUsageDescription
|
|
1378
|
+
return true;
|
|
1379
|
+
});
|
|
1380
|
+
}
|
|
1381
|
+
requestPermissions() {
|
|
1382
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1383
|
+
// iOS 权限由系统自动管理
|
|
1384
|
+
return true;
|
|
1385
|
+
});
|
|
1386
|
+
}
|
|
1387
|
+
onAdapterStateChange(callback) {
|
|
1388
|
+
this.adapterStateListeners.push(callback);
|
|
1389
|
+
// 委托中已处理状态变化
|
|
1390
|
+
}
|
|
1391
|
+
destroy() {
|
|
1392
|
+
super.destroy();
|
|
1393
|
+
// #ifdef APP-PLUS
|
|
1394
|
+
if (this.centralManager) {
|
|
1395
|
+
this.centralManager.stopScan();
|
|
1396
|
+
plus.ios.deleteObject(this.centralManager);
|
|
1397
|
+
}
|
|
1398
|
+
if (this.delegate) {
|
|
1399
|
+
plus.ios.deleteObject(this.delegate);
|
|
1400
|
+
}
|
|
1401
|
+
// #endif
|
|
1402
|
+
this.centralManager = null;
|
|
1403
|
+
this.delegate = null;
|
|
1404
|
+
}
|
|
1405
|
+
/**
|
|
1406
|
+
* 处理发现的外设
|
|
1407
|
+
*/
|
|
1408
|
+
handlePeripheral(peripheral, advertisementData, RSSI) {
|
|
1409
|
+
try {
|
|
1410
|
+
// #ifdef APP-PLUS
|
|
1411
|
+
const deviceId = peripheral.identifier().UUIDString();
|
|
1412
|
+
const name = peripheral.name();
|
|
1413
|
+
// 解析广播数据
|
|
1414
|
+
const localName = advertisementData.objectForKey('kCBAdvDataLocalName');
|
|
1415
|
+
const serviceUUIDs = advertisementData.objectForKey('kCBAdvDataServiceUUIDs');
|
|
1416
|
+
const manufacturerData = advertisementData.objectForKey('kCBAdvDataManufacturerData');
|
|
1417
|
+
const isConnectable = advertisementData.objectForKey('kCBAdvDataIsConnectable');
|
|
1418
|
+
const txPowerLevel = advertisementData.objectForKey('kCBAdvDataTxPowerLevel');
|
|
1419
|
+
// 转换服务 UUID
|
|
1420
|
+
const serviceUuids = [];
|
|
1421
|
+
if (serviceUUIDs) {
|
|
1422
|
+
const count = serviceUUIDs.count();
|
|
1423
|
+
for (let i = 0; i < count; i++) {
|
|
1424
|
+
const uuid = serviceUUIDs.objectAtIndex(i).UUIDString();
|
|
1425
|
+
serviceUuids.push(uuid);
|
|
1426
|
+
}
|
|
1427
|
+
}
|
|
1428
|
+
// 转换制造商数据
|
|
1429
|
+
let manufacturerDataBuffer;
|
|
1430
|
+
if (manufacturerData) {
|
|
1431
|
+
const length = manufacturerData.length();
|
|
1432
|
+
const bytes = new Uint8Array(length);
|
|
1433
|
+
manufacturerData.getBytes(bytes);
|
|
1434
|
+
manufacturerDataBuffer = bytes.buffer;
|
|
1435
|
+
}
|
|
1436
|
+
this.handleDeviceFound({
|
|
1437
|
+
deviceId,
|
|
1438
|
+
name: name || localName,
|
|
1439
|
+
RSSI,
|
|
1440
|
+
advertisData: manufacturerDataBuffer,
|
|
1441
|
+
serviceUuids,
|
|
1442
|
+
localName,
|
|
1443
|
+
isConnectable,
|
|
1444
|
+
txPowerLevel,
|
|
1445
|
+
});
|
|
1446
|
+
// #endif
|
|
1447
|
+
}
|
|
1448
|
+
catch (error) {
|
|
1449
|
+
console.error('处理外设数据失败:', error);
|
|
1450
|
+
}
|
|
1451
|
+
}
|
|
1452
|
+
/**
|
|
1453
|
+
* 获取 iOS 蓝牙适配器状态
|
|
1454
|
+
*/
|
|
1455
|
+
getAdapterState() {
|
|
1456
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1457
|
+
return new Promise((resolve, reject) => {
|
|
1458
|
+
// #ifdef APP-PLUS
|
|
1459
|
+
try {
|
|
1460
|
+
const state = this.centralManager.state();
|
|
1461
|
+
// CBManagerStatePoweredOn = 5
|
|
1462
|
+
resolve({
|
|
1463
|
+
available: state === 5,
|
|
1464
|
+
discovering: this.state === 'scanning',
|
|
1465
|
+
});
|
|
1466
|
+
}
|
|
1467
|
+
catch (error) {
|
|
1468
|
+
reject(handlePlatformError(error, 'ios'));
|
|
1469
|
+
}
|
|
1470
|
+
// #endif
|
|
1471
|
+
// #ifndef APP-PLUS
|
|
1472
|
+
reject(Errors.unsupportedPlatform());
|
|
1473
|
+
// #endif
|
|
1474
|
+
});
|
|
1475
|
+
});
|
|
1476
|
+
}
|
|
1477
|
+
}
|
|
1478
|
+
|
|
1479
|
+
/**
|
|
1480
|
+
* 鸿蒙原生蓝牙扫描器
|
|
1481
|
+
* 使用鸿蒙原生 API 进行蓝牙扫描
|
|
1482
|
+
*/
|
|
1483
|
+
class HarmonyNativeScanner extends BaseScanner {
|
|
1484
|
+
constructor() {
|
|
1485
|
+
super(...arguments);
|
|
1486
|
+
this.bluetoothManager = null;
|
|
1487
|
+
this.bleScanner = null;
|
|
1488
|
+
this.scanCallback = null;
|
|
1489
|
+
}
|
|
1490
|
+
getPlatform() {
|
|
1491
|
+
return Platform.HARMONY;
|
|
1492
|
+
}
|
|
1493
|
+
init() {
|
|
1494
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1495
|
+
return new Promise((resolve, reject) => {
|
|
1496
|
+
try {
|
|
1497
|
+
// #ifdef HARMONY
|
|
1498
|
+
// 导入鸿蒙蓝牙模块
|
|
1499
|
+
const bluetooth = require('@ohos.bluetooth.ble');
|
|
1500
|
+
// 获取蓝牙管理器
|
|
1501
|
+
this.bluetoothManager = bluetooth.BLE;
|
|
1502
|
+
if (!this.bluetoothManager) {
|
|
1503
|
+
reject(Errors.adapterNotAvailable());
|
|
1504
|
+
return;
|
|
1505
|
+
}
|
|
1506
|
+
// 检查蓝牙是否开启
|
|
1507
|
+
const state = this.bluetoothManager.getState();
|
|
1508
|
+
if (state !== 2) { // STATE_ON = 2
|
|
1509
|
+
reject(Errors.bluetoothDisabled());
|
|
1510
|
+
return;
|
|
1511
|
+
}
|
|
1512
|
+
this.initialized = true;
|
|
1513
|
+
resolve();
|
|
1514
|
+
// #endif
|
|
1515
|
+
// #ifndef HARMONY
|
|
1516
|
+
reject(createError(ErrorCodes.UNSUPPORTED_PLATFORM, '非鸿蒙环境不支持原生鸿蒙扫描'));
|
|
1517
|
+
// #endif
|
|
1518
|
+
}
|
|
1519
|
+
catch (error) {
|
|
1520
|
+
reject(handlePlatformError(error, 'harmony'));
|
|
1521
|
+
}
|
|
1522
|
+
});
|
|
1523
|
+
});
|
|
1524
|
+
}
|
|
1525
|
+
startScanInternal() {
|
|
1526
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1527
|
+
return new Promise((resolve, reject) => {
|
|
1528
|
+
try {
|
|
1529
|
+
// #ifdef HARMONY
|
|
1530
|
+
const bluetooth = require('@ohos.bluetooth.ble');
|
|
1531
|
+
// 构建扫描过滤器
|
|
1532
|
+
const filters = [];
|
|
1533
|
+
if (this.options.services && this.options.services.length > 0) {
|
|
1534
|
+
this.options.services.forEach((uuid) => {
|
|
1535
|
+
const filter = {
|
|
1536
|
+
serviceUuid: uuid,
|
|
1537
|
+
};
|
|
1538
|
+
filters.push(filter);
|
|
1539
|
+
});
|
|
1540
|
+
}
|
|
1541
|
+
if (this.options.nameFilter) {
|
|
1542
|
+
const names = Array.isArray(this.options.nameFilter)
|
|
1543
|
+
? this.options.nameFilter
|
|
1544
|
+
: [this.options.nameFilter];
|
|
1545
|
+
names.forEach((name) => {
|
|
1546
|
+
if (typeof name === 'string') {
|
|
1547
|
+
filters.push({ deviceName: name });
|
|
1548
|
+
}
|
|
1549
|
+
});
|
|
1550
|
+
}
|
|
1551
|
+
// 构建扫描配置
|
|
1552
|
+
const scanOptions = {
|
|
1553
|
+
interval: 0,
|
|
1554
|
+
dutyMode: bluetooth.ScanDuty.SCAN_MODE_LOW_LATENCY,
|
|
1555
|
+
matchMode: bluetooth.MatchMode.MATCH_MODE_AGGRESSIVE,
|
|
1556
|
+
};
|
|
1557
|
+
// 创建扫描回调
|
|
1558
|
+
const self = this;
|
|
1559
|
+
this.scanCallback = {
|
|
1560
|
+
onScanResult: (result) => {
|
|
1561
|
+
const device = result.device;
|
|
1562
|
+
const data = result.data;
|
|
1563
|
+
const rssi = result.rssi;
|
|
1564
|
+
self.handleDeviceFound({
|
|
1565
|
+
deviceId: device.deviceId,
|
|
1566
|
+
name: device.deviceName,
|
|
1567
|
+
RSSI: rssi,
|
|
1568
|
+
advertisData: data,
|
|
1569
|
+
serviceUuids: result.serviceUuids,
|
|
1570
|
+
});
|
|
1571
|
+
},
|
|
1572
|
+
onScanFailed: (errorCode) => {
|
|
1573
|
+
console.error('扫描失败:', errorCode);
|
|
1574
|
+
},
|
|
1575
|
+
};
|
|
1576
|
+
// 开始扫描
|
|
1577
|
+
this.bluetoothManager.startBLEScan(filters, scanOptions, this.scanCallback);
|
|
1578
|
+
resolve();
|
|
1579
|
+
// #endif
|
|
1580
|
+
// #ifndef HARMONY
|
|
1581
|
+
reject(Errors.unsupportedPlatform());
|
|
1582
|
+
// #endif
|
|
1583
|
+
}
|
|
1584
|
+
catch (error) {
|
|
1585
|
+
reject(handlePlatformError(error, 'harmony'));
|
|
1586
|
+
}
|
|
1587
|
+
});
|
|
1588
|
+
});
|
|
1589
|
+
}
|
|
1590
|
+
stopScanInternal() {
|
|
1591
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1592
|
+
return new Promise((resolve, reject) => {
|
|
1593
|
+
try {
|
|
1594
|
+
// #ifdef HARMONY
|
|
1595
|
+
if (this.bluetoothManager) {
|
|
1596
|
+
this.bluetoothManager.stopBLEScan();
|
|
1597
|
+
}
|
|
1598
|
+
resolve();
|
|
1599
|
+
// #endif
|
|
1600
|
+
// #ifndef HARMONY
|
|
1601
|
+
reject(Errors.unsupportedPlatform());
|
|
1602
|
+
// #endif
|
|
1603
|
+
}
|
|
1604
|
+
catch (error) {
|
|
1605
|
+
reject(handlePlatformError(error, 'harmony'));
|
|
1606
|
+
}
|
|
1607
|
+
});
|
|
1608
|
+
});
|
|
1609
|
+
}
|
|
1610
|
+
checkPermissions() {
|
|
1611
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1612
|
+
// #ifdef HARMONY
|
|
1613
|
+
try {
|
|
1614
|
+
const atManager = require('@ohos.abilityAccessCtrl').createAtManager();
|
|
1615
|
+
const tokenId = require('@ohos.process').process.accessTokenId;
|
|
1616
|
+
const permissions = [
|
|
1617
|
+
'ohos.permission.ACCESS_BLUETOOTH',
|
|
1618
|
+
'ohos.permission.LOCATION',
|
|
1619
|
+
];
|
|
1620
|
+
for (const permission of permissions) {
|
|
1621
|
+
const result = yield atManager.verifyAccessToken(tokenId, permission);
|
|
1622
|
+
if (result !== 0) {
|
|
1623
|
+
return false;
|
|
1624
|
+
}
|
|
1625
|
+
}
|
|
1626
|
+
return true;
|
|
1627
|
+
}
|
|
1628
|
+
catch (error) {
|
|
1629
|
+
console.error('检查权限失败:', error);
|
|
1630
|
+
return false;
|
|
1631
|
+
}
|
|
1632
|
+
// #endif
|
|
1633
|
+
// #ifndef HARMONY
|
|
1634
|
+
return false;
|
|
1635
|
+
// #endif
|
|
1636
|
+
});
|
|
1637
|
+
}
|
|
1638
|
+
requestPermissions() {
|
|
1639
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1640
|
+
// #ifdef HARMONY
|
|
1641
|
+
try {
|
|
1642
|
+
const atManager = require('@ohos.abilityAccessCtrl').createAtManager();
|
|
1643
|
+
const context = getContext(this);
|
|
1644
|
+
const permissions = [
|
|
1645
|
+
'ohos.permission.ACCESS_BLUETOOTH',
|
|
1646
|
+
'ohos.permission.LOCATION',
|
|
1647
|
+
];
|
|
1648
|
+
const result = yield atManager.requestPermissionsFromUser(context, permissions);
|
|
1649
|
+
return result.authResults.every((authResult) => authResult === 0);
|
|
1650
|
+
}
|
|
1651
|
+
catch (error) {
|
|
1652
|
+
console.error('请求权限失败:', error);
|
|
1653
|
+
return false;
|
|
1654
|
+
}
|
|
1655
|
+
// #endif
|
|
1656
|
+
// #ifndef HARMONY
|
|
1657
|
+
return false;
|
|
1658
|
+
// #endif
|
|
1659
|
+
});
|
|
1660
|
+
}
|
|
1661
|
+
onAdapterStateChange(callback) {
|
|
1662
|
+
this.adapterStateListeners.push(callback);
|
|
1663
|
+
// #ifdef HARMONY
|
|
1664
|
+
try {
|
|
1665
|
+
const bluetooth = require('@ohos.bluetooth.ble');
|
|
1666
|
+
// 监听蓝牙状态变化
|
|
1667
|
+
bluetooth.on('stateChange', (data) => {
|
|
1668
|
+
this.emitAdapterStateChange({
|
|
1669
|
+
available: data.state === 2, // STATE_ON = 2
|
|
1670
|
+
discovering: this.state === 'scanning',
|
|
1671
|
+
});
|
|
1672
|
+
});
|
|
1673
|
+
}
|
|
1674
|
+
catch (error) {
|
|
1675
|
+
console.error('监听状态变化失败:', error);
|
|
1676
|
+
}
|
|
1677
|
+
// #endif
|
|
1678
|
+
}
|
|
1679
|
+
destroy() {
|
|
1680
|
+
super.destroy();
|
|
1681
|
+
// #ifdef HARMONY
|
|
1682
|
+
try {
|
|
1683
|
+
const bluetooth = require('@ohos.bluetooth.ble');
|
|
1684
|
+
// 停止扫描
|
|
1685
|
+
if (this.bluetoothManager) {
|
|
1686
|
+
this.bluetoothManager.stopBLEScan();
|
|
1687
|
+
}
|
|
1688
|
+
// 移除状态监听
|
|
1689
|
+
bluetooth.off('stateChange');
|
|
1690
|
+
}
|
|
1691
|
+
catch (error) {
|
|
1692
|
+
console.error('销毁失败:', error);
|
|
1693
|
+
}
|
|
1694
|
+
// #endif
|
|
1695
|
+
this.bluetoothManager = null;
|
|
1696
|
+
this.scanCallback = null;
|
|
1697
|
+
}
|
|
1698
|
+
/**
|
|
1699
|
+
* 获取鸿蒙蓝牙适配器状态
|
|
1700
|
+
*/
|
|
1701
|
+
getAdapterState() {
|
|
1702
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1703
|
+
return new Promise((resolve, reject) => {
|
|
1704
|
+
// #ifdef HARMONY
|
|
1705
|
+
try {
|
|
1706
|
+
const state = this.bluetoothManager.getState();
|
|
1707
|
+
resolve({
|
|
1708
|
+
available: state === 2,
|
|
1709
|
+
discovering: this.state === 'scanning',
|
|
1710
|
+
});
|
|
1711
|
+
}
|
|
1712
|
+
catch (error) {
|
|
1713
|
+
reject(handlePlatformError(error, 'harmony'));
|
|
1714
|
+
}
|
|
1715
|
+
// #endif
|
|
1716
|
+
// #ifndef HARMONY
|
|
1717
|
+
reject(Errors.unsupportedPlatform());
|
|
1718
|
+
// #endif
|
|
1719
|
+
});
|
|
1720
|
+
});
|
|
1721
|
+
}
|
|
1722
|
+
}
|
|
1723
|
+
|
|
1724
|
+
/**
|
|
1725
|
+
* 创建蓝牙扫描器
|
|
1726
|
+
* @param options 创建选项
|
|
1727
|
+
* @returns 蓝牙扫描器实例
|
|
1728
|
+
*/
|
|
1729
|
+
function createScanner(options = {}) {
|
|
1730
|
+
const { type = 'auto', platformConfig = {} } = options;
|
|
1731
|
+
const platform = getPlatform();
|
|
1732
|
+
const app = isApp();
|
|
1733
|
+
// 根据类型和平台选择合适的扫描器
|
|
1734
|
+
if (type === 'uniapp' || (type === 'auto' && !app)) {
|
|
1735
|
+
// 使用 UniApp 标准 API
|
|
1736
|
+
const scanner = new UniAppScanner();
|
|
1737
|
+
Object.assign(scanner, { platformConfig });
|
|
1738
|
+
return scanner;
|
|
1739
|
+
}
|
|
1740
|
+
if (type === 'native' || type === 'auto') {
|
|
1741
|
+
// 使用原生 API
|
|
1742
|
+
switch (platform) {
|
|
1743
|
+
case Platform.ANDROID:
|
|
1744
|
+
const androidScanner = new AndroidNativeScanner();
|
|
1745
|
+
Object.assign(androidScanner, { platformConfig });
|
|
1746
|
+
return androidScanner;
|
|
1747
|
+
case Platform.IOS:
|
|
1748
|
+
const iosScanner = new IOSNativeScanner();
|
|
1749
|
+
Object.assign(iosScanner, { platformConfig });
|
|
1750
|
+
return iosScanner;
|
|
1751
|
+
case Platform.HARMONY:
|
|
1752
|
+
const harmonyScanner = new HarmonyNativeScanner();
|
|
1753
|
+
Object.assign(harmonyScanner, { platformConfig });
|
|
1754
|
+
return harmonyScanner;
|
|
1755
|
+
default:
|
|
1756
|
+
// 未知平台使用 UniApp API
|
|
1757
|
+
const defaultScanner = new UniAppScanner();
|
|
1758
|
+
Object.assign(defaultScanner, { platformConfig });
|
|
1759
|
+
return defaultScanner;
|
|
1760
|
+
}
|
|
1761
|
+
}
|
|
1762
|
+
// 默认使用 UniApp API
|
|
1763
|
+
const defaultScanner = new UniAppScanner();
|
|
1764
|
+
Object.assign(defaultScanner, { platformConfig });
|
|
1765
|
+
return defaultScanner;
|
|
1766
|
+
}
|
|
1767
|
+
/**
|
|
1768
|
+
* 创建 UniApp 扫描器
|
|
1769
|
+
* 使用 UniApp 标准蓝牙 API,兼容所有平台
|
|
1770
|
+
*/
|
|
1771
|
+
function createUniAppScanner(platformConfig) {
|
|
1772
|
+
return createScanner({ type: 'uniapp', platformConfig });
|
|
1773
|
+
}
|
|
1774
|
+
/**
|
|
1775
|
+
* 创建原生扫描器
|
|
1776
|
+
* 使用平台原生 API,功能更强大但需要特定条件
|
|
1777
|
+
*/
|
|
1778
|
+
function createNativeScanner(platformConfig) {
|
|
1779
|
+
return createScanner({ type: 'native', platformConfig });
|
|
1780
|
+
}
|
|
1781
|
+
/**
|
|
1782
|
+
* 检查当前环境是否支持蓝牙
|
|
1783
|
+
*/
|
|
1784
|
+
function isBluetoothSupported() {
|
|
1785
|
+
// #ifdef APP-PLUS
|
|
1786
|
+
return true;
|
|
1787
|
+
}
|
|
1788
|
+
|
|
1789
|
+
// 导出枚举
|
|
1790
|
+
|
|
1791
|
+
export { AndroidNativeScanner, BaseScanner, ErrorCodes, Errors, HarmonyNativeScanner, IOSNativeScanner, Platform, ScanState, UniAppScanner, arrayBufferToBase64, base64ToArrayBuffer, checkPermissions, createError, createNativeScanner, createScanner, createUniAppScanner, createScanner as default, formatUUID, getPlatform, getPlatformName, isApp, isBluetoothSupported, isPlatform, matchUUID, parseAdvertisementData, requestPermissions };
|