763 lines
23 KiB
JavaScript
763 lines
23 KiB
JavaScript
/**
|
||
* 蓝牙 BLE 采集服务
|
||
*/
|
||
const hex = require('../utils/hex.js');
|
||
|
||
// BLE 状态常量
|
||
const BLE_STATE = {
|
||
DISCONNECTED: 'disconnected',
|
||
CONNECTING: 'connecting',
|
||
CONNECTED: 'connected',
|
||
DISCOVERING: 'discovering',
|
||
READY: 'ready',
|
||
};
|
||
|
||
// 默认配置
|
||
const DEFAULT_CONFIG = {
|
||
// 扫描超时时间(毫秒)
|
||
scanTimeout: 10000,
|
||
// 连接超时时间(毫秒)
|
||
connectTimeout: 10000,
|
||
// 默认服务 UUID(需根据实际设备配置)
|
||
serviceUUIDs: [],
|
||
// 调试日志开关(高频数据禁止逐包打印,否则阻塞 BLE 接收)
|
||
debug: false,
|
||
// 掉线自动重连(工业场景建议开启)
|
||
autoReconnect: true,
|
||
};
|
||
|
||
class BleService {
|
||
constructor() {
|
||
this.state = BLE_STATE.DISCONNECTED;
|
||
this.deviceId = null;
|
||
this.serviceId = null;
|
||
this.notifyCharacteristicId = null;
|
||
this.writeCharacteristicId = null;
|
||
this.onDataCallback = null;
|
||
this.onStateChangeCallback = null;
|
||
this.config = { ...DEFAULT_CONFIG };
|
||
// 监听器注册标志(防止叠加)
|
||
this._listenersRegistered = false;
|
||
// 适配器是否已开启
|
||
this._openedAdapter = false;
|
||
// 收包计数/最近收包时间(诊断、静默探测用)
|
||
this._recvCount = 0;
|
||
this._lastRecvAt = 0;
|
||
// 当前会话已启用 notify 的特征列表
|
||
this._notifyCharacteristicIds = [];
|
||
// 自动重连相关
|
||
this._lastDeviceId = null;
|
||
this._lastServiceId = null;
|
||
this._reconnectTimer = null;
|
||
this._reconnectAttempts = 0;
|
||
this._manuallyDisconnected = false;
|
||
}
|
||
|
||
/**
|
||
* 设置配置
|
||
* @param {Object} config
|
||
*/
|
||
setConfig(config) {
|
||
this.config = { ...DEFAULT_CONFIG, ...config };
|
||
}
|
||
|
||
/**
|
||
* 设置数据回调
|
||
* @param {Function} callback - (data: {hex, buffer}) => void
|
||
*/
|
||
onData(callback) {
|
||
this.onDataCallback = callback;
|
||
}
|
||
|
||
/**
|
||
* 设置状态变化回调
|
||
* @param {Function} callback - (state) => void
|
||
*/
|
||
onStateChange(callback) {
|
||
this.onStateChangeCallback = callback;
|
||
}
|
||
|
||
/**
|
||
* 更新状态并通知
|
||
* @param {string} state
|
||
*/
|
||
_setState(state) {
|
||
this.state = state;
|
||
if (this.onStateChangeCallback) {
|
||
this.onStateChangeCallback(state);
|
||
}
|
||
console.log('[BLE] 状态变更:', state);
|
||
}
|
||
|
||
/**
|
||
* 初始化蓝牙适配器(幂等)
|
||
* wx 级全局监听只注册一次,不再每次 off/on 抖动,避免多页/重连切换时的丢包窗口。
|
||
* @returns {Promise}
|
||
*/
|
||
initBluetooth() {
|
||
return new Promise((resolve, reject) => {
|
||
// 已开启且监听已挂:直接复用
|
||
if (this._openedAdapter && this._listenersRegistered) {
|
||
resolve();
|
||
return;
|
||
}
|
||
|
||
// 已开适配器但监听未挂(异常恢复路径),补挂监听即可
|
||
const registerListeners = () => {
|
||
if (this._listenersRegistered) return;
|
||
|
||
wx.onBLEConnectionStateChange((res) => {
|
||
console.log('[BLE] 连接状态变化:', res);
|
||
if (!res.connected) {
|
||
const wasConnected = this.state === BLE_STATE.READY || this.state === BLE_STATE.CONNECTED;
|
||
this._setState(BLE_STATE.DISCONNECTED);
|
||
// 保留 deviceId 供重连使用(不再置空),掉线自动重连
|
||
if (wasConnected && this.config.autoReconnect && !this._manuallyDisconnected && this._lastDeviceId) {
|
||
this._scheduleReconnect();
|
||
}
|
||
}
|
||
});
|
||
|
||
wx.onBLECharacteristicValueChange((res) => {
|
||
// 只处理当前会话设备的值变化,防止切换设备后旧连接/残留连接串包
|
||
if (this.deviceId && res.deviceId && res.deviceId !== this.deviceId) {
|
||
return;
|
||
}
|
||
const hexData = hex.arrayBufferToHex(res.value);
|
||
this._recvCount++;
|
||
this._lastRecvAt = Date.now();
|
||
// 首包 + 每 20 包打印摘要,debug 时逐包打印,避免高频逐包日志阻塞接收
|
||
if (this.config.debug || this._recvCount === 1 || this._recvCount % 20 === 0) {
|
||
console.log(`[BLE] 收到数据 #${this._recvCount} (${hexData.length / 2}字节):`, hexData);
|
||
}
|
||
if (this.onDataCallback) {
|
||
this.onDataCallback({
|
||
hex: hexData,
|
||
buffer: res.value,
|
||
deviceId: res.deviceId,
|
||
serviceId: res.serviceId,
|
||
characteristicId: res.characteristicId,
|
||
});
|
||
}
|
||
});
|
||
|
||
this._listenersRegistered = true;
|
||
console.log('[BLE] 已注册全局监听');
|
||
};
|
||
|
||
if (this._openedAdapter) {
|
||
registerListeners();
|
||
resolve();
|
||
return;
|
||
}
|
||
|
||
wx.openBluetoothAdapter({
|
||
success: () => {
|
||
console.log('[BLE] 初始化成功');
|
||
this._openedAdapter = true;
|
||
registerListeners();
|
||
resolve();
|
||
},
|
||
fail: (err) => {
|
||
const msg = (err && err.errMsg) || '';
|
||
// 部分平台适配器已开启时 open 会报 already 类错误,视为成功
|
||
if (/already/i.test(msg)) {
|
||
console.log('[BLE] 适配器已开启(忽略 already)');
|
||
this._openedAdapter = true;
|
||
registerListeners();
|
||
resolve();
|
||
} else {
|
||
console.error('[BLE] 初始化失败:', err);
|
||
reject(new Error('蓝牙初始化失败: ' + msg || '未知错误'));
|
||
}
|
||
},
|
||
});
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 获取平台合适的 MTU 值
|
||
* @returns {number}
|
||
*/
|
||
_getMtu() {
|
||
try {
|
||
const platform = wx.getSystemInfoSync().platform;
|
||
// iOS 最大 185,超出会协商失败;Android 建议 247
|
||
return platform === 'ios' ? 185 : 247;
|
||
} catch (e) {
|
||
return 247;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 扫描蓝牙设备
|
||
* @param {Array<string>} serviceUUIDs - 服务 UUID 列表(可选)
|
||
* @param {number} timeout - 超时时间(毫秒)
|
||
* @returns {Promise<Array>} 设备列表
|
||
*/
|
||
scanDevices(serviceUUIDs = null, timeout = null) {
|
||
return new Promise((resolve, reject) => {
|
||
const uuids = serviceUUIDs || this.config.serviceUUIDs;
|
||
const scanTimeout = timeout || this.config.scanTimeout;
|
||
|
||
const devices = [];
|
||
const deviceMap = new Map();
|
||
|
||
// 移除旧的扫描监听器,防止叠加
|
||
try {
|
||
wx.offBluetoothDeviceFound();
|
||
} catch (e) {
|
||
// 忽略移除失败
|
||
}
|
||
|
||
// 开始扫描
|
||
wx.startBluetoothDevicesDiscovery({
|
||
services: uuids.length > 0 ? uuids : undefined,
|
||
allowDuplicatesKey: false,
|
||
success: () => {
|
||
console.log('[BLE] 开始扫描, 超时:', scanTimeout);
|
||
// 监听扫描结果
|
||
wx.onBluetoothDeviceFound((res) => {
|
||
res.devices.forEach((device) => {
|
||
// 去重
|
||
if (!deviceMap.has(device.deviceId)) {
|
||
deviceMap.set(device.deviceId, true);
|
||
devices.push({
|
||
deviceId: device.deviceId,
|
||
name: device.name || '未知设备',
|
||
localName: device.localName,
|
||
advertisData: device.advertisData,
|
||
RSSI: device.RSSI,
|
||
});
|
||
}
|
||
});
|
||
});
|
||
|
||
// 设置超时
|
||
setTimeout(() => {
|
||
wx.stopBluetoothDevicesDiscovery({
|
||
success: () => {
|
||
console.log('[BLE] 扫描完成, 发现设备:', devices.length);
|
||
// 移除扫描监听器
|
||
try {
|
||
wx.offBluetoothDeviceFound();
|
||
} catch (e) {
|
||
// 忽略
|
||
}
|
||
resolve(devices);
|
||
},
|
||
fail: () => {
|
||
try {
|
||
wx.offBluetoothDeviceFound();
|
||
} catch (e) {
|
||
// 忽略
|
||
}
|
||
resolve(devices);
|
||
},
|
||
});
|
||
}, scanTimeout);
|
||
},
|
||
fail: (err) => {
|
||
console.error('[BLE] 扫描失败:', err);
|
||
reject(new Error('扫描失败: ' + (err.errMsg || '未知错误')));
|
||
},
|
||
});
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 停止扫描
|
||
*/
|
||
stopScan() {
|
||
return new Promise((resolve) => {
|
||
wx.stopBluetoothDevicesDiscovery({
|
||
success: () => {
|
||
console.log('[BLE] 已停止扫描');
|
||
resolve();
|
||
},
|
||
fail: () => {
|
||
resolve();
|
||
},
|
||
});
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 连接设备
|
||
* @param {string} deviceId - 设备 ID
|
||
* @param {string} serviceId - 服务 UUID(可选,不传则自动发现)
|
||
* @returns {Promise}
|
||
*/
|
||
connectDevice(deviceId, serviceId = null) {
|
||
return new Promise((resolve, reject) => {
|
||
this._setState(BLE_STATE.CONNECTING);
|
||
this.deviceId = deviceId;
|
||
|
||
// 记录连接目标,供自动重连使用
|
||
this._lastDeviceId = deviceId;
|
||
this._lastServiceId = serviceId;
|
||
this._manuallyDisconnected = false;
|
||
this._clearReconnectTimer();
|
||
|
||
const connectTimeout = this.config.connectTimeout;
|
||
let timeoutId = null;
|
||
|
||
// 设置连接超时
|
||
timeoutId = setTimeout(() => {
|
||
wx.closeBLEConnection({
|
||
deviceId: deviceId,
|
||
complete: () => {
|
||
this._setState(BLE_STATE.DISCONNECTED);
|
||
reject(new Error('连接超时'));
|
||
},
|
||
});
|
||
}, connectTimeout);
|
||
|
||
// 发起连接
|
||
wx.createBLEConnection({
|
||
deviceId: deviceId,
|
||
success: () => {
|
||
clearTimeout(timeoutId);
|
||
console.log('[BLE] 连接成功:', deviceId);
|
||
// 连接成功,重置重连退避计数
|
||
this._reconnectAttempts = 0;
|
||
this._setState(BLE_STATE.CONNECTED);
|
||
|
||
// 获取 MTU(提高传输效率,iOS 最大 185,Android 建议 247)
|
||
wx.setBLEMTU({
|
||
deviceId: deviceId,
|
||
mtu: this._getMtu(),
|
||
complete: () => {
|
||
// 发现服务
|
||
this._discoverServices(deviceId, serviceId)
|
||
.then(resolve)
|
||
.catch(reject);
|
||
},
|
||
});
|
||
},
|
||
fail: (err) => {
|
||
clearTimeout(timeoutId);
|
||
const errMsg = (err && err.errMsg) || '';
|
||
|
||
// 如果报错 "already connect",先断开再重连
|
||
if (/already connect/i.test(errMsg)) {
|
||
console.warn('[BLE] 检测到已存在连接,先断开再重连');
|
||
wx.closeBLEConnection({
|
||
deviceId: deviceId,
|
||
complete: () => {
|
||
// 断开后重新连接
|
||
setTimeout(() => {
|
||
wx.createBLEConnection({
|
||
deviceId: deviceId,
|
||
success: () => {
|
||
console.log('[BLE] 重连成功:', deviceId);
|
||
this._reconnectAttempts = 0;
|
||
this._setState(BLE_STATE.CONNECTED);
|
||
wx.setBLEMTU({
|
||
deviceId: deviceId,
|
||
mtu: this._getMtu(),
|
||
complete: () => {
|
||
this._discoverServices(deviceId, serviceId)
|
||
.then(resolve)
|
||
.catch(reject);
|
||
},
|
||
});
|
||
},
|
||
fail: (err2) => {
|
||
this._setState(BLE_STATE.DISCONNECTED);
|
||
console.error('[BLE] 重连失败:', err2);
|
||
reject(new Error('重连失败: ' + (err2.errMsg || '未知错误')));
|
||
},
|
||
});
|
||
}, 500); // 等待 500ms 确保断开完成
|
||
},
|
||
});
|
||
return;
|
||
}
|
||
|
||
this._setState(BLE_STATE.DISCONNECTED);
|
||
console.error('[BLE] 连接失败:', err);
|
||
reject(new Error('连接失败: ' + errMsg || '未知错误'));
|
||
},
|
||
});
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 发现服务和特征值
|
||
* @param {string} deviceId
|
||
* @param {string} serviceId
|
||
* @returns {Promise}
|
||
*/
|
||
_discoverServices(deviceId, serviceId = null) {
|
||
return new Promise((resolve, reject) => {
|
||
this._setState(BLE_STATE.DISCOVERING);
|
||
|
||
wx.getBLEDeviceServices({
|
||
deviceId: deviceId,
|
||
success: (res) => {
|
||
console.log('[BLE] 发现服务:', res.services);
|
||
const services = res.services;
|
||
|
||
// 选择目标服务
|
||
let targetService = null;
|
||
if (serviceId) {
|
||
// 指定了服务 UUID
|
||
targetService = services.find(s => s.uuid.toLowerCase().includes(serviceId.toLowerCase()));
|
||
} else {
|
||
// 自动选择:排除通用服务(GAP 0x1800, GATT 0x1801 等)
|
||
const GENERIC_SERVICE_PREFIXES = ['00001800', '00001801', '0000180a', '0000180f'];
|
||
targetService = services.find(s =>
|
||
!GENERIC_SERVICE_PREFIXES.some(p => s.uuid.toLowerCase().startsWith(p))
|
||
);
|
||
// 如果没有非通用服务,选择第一个主服务
|
||
if (!targetService) {
|
||
targetService = services.find(s => s.isPrimary) || services[0];
|
||
}
|
||
}
|
||
|
||
if (!targetService) {
|
||
reject(new Error('未找到目标服务'));
|
||
return;
|
||
}
|
||
|
||
this.serviceId = targetService.uuid;
|
||
console.log('[BLE] 目标服务:', this.serviceId);
|
||
|
||
// 获取特征值
|
||
this._discoverCharacteristics(deviceId, this.serviceId)
|
||
.then(resolve)
|
||
.catch(reject);
|
||
},
|
||
fail: (err) => {
|
||
console.error('[BLE] 获取服务失败:', err);
|
||
reject(new Error('获取服务失败'));
|
||
},
|
||
});
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 发现特征值
|
||
* 目标服务下所有 notify/indicate 特征都尝试启用(数据特征未必是第一个 notify 特征),
|
||
* 单个特征启用失败仅告警跳过,至少一个成功即视为就绪。
|
||
* @param {string} deviceId
|
||
* @param {string} serviceId
|
||
* @returns {Promise}
|
||
*/
|
||
_discoverCharacteristics(deviceId, serviceId) {
|
||
return new Promise((resolve, reject) => {
|
||
wx.getBLEDeviceCharacteristics({
|
||
deviceId: deviceId,
|
||
serviceId: serviceId,
|
||
success: (res) => {
|
||
console.log('[BLE] 发现特征值:', res.characteristics.map(c => ({
|
||
uuid: c.uuid,
|
||
properties: c.properties,
|
||
})));
|
||
const characteristics = res.characteristics;
|
||
|
||
const notifyChars = characteristics.filter(c => c.properties.notify || c.properties.indicate);
|
||
const writeChar = characteristics.find(c => c.properties.write || c.properties.writeNoResponse);
|
||
if (writeChar) {
|
||
this.writeCharacteristicId = writeChar.uuid;
|
||
console.log('[BLE] 写入特征值:', this.writeCharacteristicId);
|
||
}
|
||
|
||
if (notifyChars.length === 0) {
|
||
// 没有通知特征值,但可能有写入特征值
|
||
console.warn('[BLE] ⚠️ 未找到 notify/indicate 特征值!设备将无法主动上报数据,只能被动写入。请核对设备的 GATT 服务/特征值。');
|
||
this._notifyCharacteristicIds = [];
|
||
this.notifyCharacteristicId = null;
|
||
this._lastRecvAt = Date.now();
|
||
this._setState(BLE_STATE.READY);
|
||
resolve({
|
||
serviceId: this.serviceId,
|
||
notifyCharacteristicIds: [],
|
||
notifyCharacteristicId: null,
|
||
writeCharacteristicId: this.writeCharacteristicId,
|
||
});
|
||
return;
|
||
}
|
||
|
||
// 串行逐个启用 notify
|
||
this._enableAllNotify(deviceId, serviceId, notifyChars).then((enabled) => {
|
||
this._notifyCharacteristicIds = enabled;
|
||
this.notifyCharacteristicId = enabled[0] || null;
|
||
if (enabled.length === 0) {
|
||
console.error('[BLE] 所有 notify 特征启用均失败');
|
||
reject(new Error('启用通知失败'));
|
||
return;
|
||
}
|
||
console.log('[BLE] 已启用 notify 特征:', enabled);
|
||
// 作为后续“静默/掉线”探测的基线
|
||
this._lastRecvAt = Date.now();
|
||
this._setState(BLE_STATE.READY);
|
||
resolve({
|
||
serviceId: this.serviceId,
|
||
notifyCharacteristicIds: enabled,
|
||
notifyCharacteristicId: this.notifyCharacteristicId,
|
||
writeCharacteristicId: this.writeCharacteristicId,
|
||
});
|
||
}).catch((err) => {
|
||
console.error('[BLE] 启用通知异常:', err);
|
||
reject(new Error('启用通知失败'));
|
||
});
|
||
},
|
||
fail: (err) => {
|
||
console.error('[BLE] 获取特征值失败:', err);
|
||
reject(new Error('获取特征值失败'));
|
||
},
|
||
});
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 串行启用多个 notify/indicate 特征
|
||
* @param {string} deviceId
|
||
* @param {string} serviceId
|
||
* @param {Array} chars - 特征数组(含 uuid)
|
||
* @returns {Promise<Array<string>>} 成功启用的特征 UUID 列表
|
||
*/
|
||
_enableAllNotify(deviceId, serviceId, chars) {
|
||
return chars.reduce((chain, c) => {
|
||
return chain.then((enabled) => {
|
||
return this._enableNotify(deviceId, serviceId, c.uuid).then((ok) => {
|
||
if (ok) enabled.push(c.uuid);
|
||
return enabled;
|
||
});
|
||
});
|
||
}, Promise.resolve([]));
|
||
}
|
||
|
||
/**
|
||
* 启用单个特征的 notify/indicate
|
||
* @returns {Promise<string|null>} 成功返回特征 UUID,失败返回 null(不抛错)
|
||
*/
|
||
_enableNotify(deviceId, serviceId, characteristicId) {
|
||
return new Promise((resolve) => {
|
||
wx.notifyBLECharacteristicValueChange({
|
||
deviceId: deviceId,
|
||
serviceId: serviceId,
|
||
characteristicId: characteristicId,
|
||
state: true,
|
||
success: () => {
|
||
console.log('[BLE] 启用通知:', characteristicId);
|
||
resolve(characteristicId);
|
||
},
|
||
fail: (err) => {
|
||
console.warn('[BLE] 启用通知失败(跳过):', characteristicId, err);
|
||
resolve(null);
|
||
},
|
||
});
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 写入数据
|
||
* @param {string} hexData - 十六进制字符串
|
||
* @returns {Promise}
|
||
*/
|
||
writeCommand(hexData) {
|
||
return new Promise((resolve, reject) => {
|
||
if (!this.deviceId || !this.serviceId || !this.writeCharacteristicId) {
|
||
reject(new Error('设备未连接或无写入特征值'));
|
||
return;
|
||
}
|
||
|
||
const buffer = hex.hexToArrayBuffer(hexData);
|
||
|
||
wx.writeBLECharacteristicValue({
|
||
deviceId: this.deviceId,
|
||
serviceId: this.serviceId,
|
||
characteristicId: this.writeCharacteristicId,
|
||
value: buffer,
|
||
success: () => {
|
||
console.log('[BLE] 写入成功:', hexData);
|
||
resolve();
|
||
},
|
||
fail: (err) => {
|
||
console.error('[BLE] 写入失败:', err);
|
||
reject(new Error('写入失败: ' + (err.errMsg || '未知错误')));
|
||
},
|
||
});
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 清除自动重连定时器
|
||
*/
|
||
_clearReconnectTimer() {
|
||
if (this._reconnectTimer) {
|
||
clearTimeout(this._reconnectTimer);
|
||
this._reconnectTimer = null;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 调度自动重连(指数退避:1s → 2s → 4s ... 最大 15s)
|
||
*/
|
||
_scheduleReconnect() {
|
||
if (this._reconnectTimer) return;
|
||
const delay = Math.min(1000 * Math.pow(2, this._reconnectAttempts), 15000);
|
||
this._reconnectAttempts++;
|
||
console.log(`[BLE] ${delay}ms 后自动重连(第 ${this._reconnectAttempts} 次)`);
|
||
this._reconnectTimer = setTimeout(() => {
|
||
this._reconnectTimer = null;
|
||
if (!this._lastDeviceId) return;
|
||
this.connectDevice(this._lastDeviceId, this._lastServiceId).catch((err) => {
|
||
console.error('[BLE] 自动重连失败:', err.message);
|
||
this._scheduleReconnect();
|
||
});
|
||
}, delay);
|
||
}
|
||
|
||
/**
|
||
* 断开连接
|
||
*/
|
||
disconnect() {
|
||
return new Promise((resolve) => {
|
||
// 用户主动断开,停止自动重连
|
||
this._manuallyDisconnected = true;
|
||
this._clearReconnectTimer();
|
||
|
||
if (!this.deviceId) {
|
||
if (this.state !== BLE_STATE.DISCONNECTED) {
|
||
this._setState(BLE_STATE.DISCONNECTED);
|
||
}
|
||
resolve();
|
||
return;
|
||
}
|
||
|
||
wx.closeBLEConnection({
|
||
deviceId: this.deviceId,
|
||
success: () => {
|
||
console.log('[BLE] 已断开连接');
|
||
},
|
||
complete: () => {
|
||
this._setState(BLE_STATE.DISCONNECTED);
|
||
this.deviceId = null;
|
||
this.serviceId = null;
|
||
this.notifyCharacteristicId = null;
|
||
this.writeCharacteristicId = null;
|
||
this._notifyCharacteristicIds = [];
|
||
this._lastRecvAt = 0;
|
||
resolve();
|
||
},
|
||
});
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 关闭蓝牙适配器
|
||
*/
|
||
closeBluetooth() {
|
||
return new Promise((resolve) => {
|
||
this.disconnect().then(() => {
|
||
wx.closeBluetoothAdapter({
|
||
success: () => {
|
||
console.log('[BLE] 已关闭蓝牙适配器');
|
||
},
|
||
complete: () => {
|
||
resolve();
|
||
},
|
||
});
|
||
});
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 获取当前状态
|
||
* @returns {string}
|
||
*/
|
||
getState() {
|
||
return this.state;
|
||
}
|
||
|
||
/**
|
||
* 是否已连接
|
||
* @returns {boolean}
|
||
*/
|
||
isConnected() {
|
||
return this.state === BLE_STATE.READY || this.state === BLE_STATE.CONNECTED;
|
||
}
|
||
|
||
/**
|
||
* 是否处于可接收数据的就绪态
|
||
* @returns {boolean}
|
||
*/
|
||
isReady() {
|
||
return this.state === BLE_STATE.READY || this.state === BLE_STATE.CONNECTED;
|
||
}
|
||
|
||
/**
|
||
* 是否正处于连接流程中(用于避免与自动重连并发发起 createBLEConnection)
|
||
* @returns {boolean}
|
||
*/
|
||
isConnecting() {
|
||
return this.state === BLE_STATE.CONNECTING || this.state === BLE_STATE.DISCOVERING;
|
||
}
|
||
|
||
/**
|
||
* 强制重连当前目标设备(用于静默/掉线恢复)
|
||
* @returns {Promise}
|
||
*/
|
||
forceReconnect() {
|
||
if (!this._lastDeviceId) {
|
||
return Promise.reject(new Error('无重连目标'));
|
||
}
|
||
this._reconnectAttempts = 0;
|
||
this._manuallyDisconnected = false;
|
||
this._clearReconnectTimer();
|
||
console.log('[BLE] 强制重连:', this._lastDeviceId);
|
||
return this.connectDevice(this._lastDeviceId, this._lastServiceId);
|
||
}
|
||
|
||
/**
|
||
* 探测连接是否仍活跃(超过 silentMs 无收包视为不活跃)
|
||
* @param {number} silentMs - 静默阈值(毫秒)
|
||
* @returns {boolean}
|
||
*/
|
||
probeConnection(silentMs = 30000) {
|
||
if (!this.isReady()) return false;
|
||
if (!this._lastRecvAt) return true; // 尚未收到任何包,不做静默判定
|
||
return (Date.now() - this._lastRecvAt) <= silentMs;
|
||
}
|
||
|
||
/**
|
||
* 诊断信息(真机排查“收不到/收一条即停”用)
|
||
* @returns {Object}
|
||
*/
|
||
getDiagnostics() {
|
||
return {
|
||
state: this.state,
|
||
adapterOpen: !!this._openedAdapter,
|
||
deviceId: this.deviceId,
|
||
serviceId: this.serviceId,
|
||
notifyCharacteristicId: this.notifyCharacteristicId,
|
||
notifyCharacteristicIds: this._notifyCharacteristicIds || [],
|
||
writeCharacteristicId: this.writeCharacteristicId,
|
||
recvCount: this._recvCount,
|
||
lastRecvAt: this._lastRecvAt,
|
||
autoReconnect: this.config.autoReconnect,
|
||
};
|
||
}
|
||
|
||
/**
|
||
* 清除数据回调(页面卸载时调用)
|
||
*/
|
||
clearCallbacks() {
|
||
this.onDataCallback = null;
|
||
this.onStateChangeCallback = null;
|
||
console.log('[BLE] 已清除回调');
|
||
}
|
||
}
|
||
|
||
// 导出单例
|
||
const bleService = new BleService();
|
||
|
||
module.exports = {
|
||
BLE_STATE,
|
||
BleService,
|
||
bleService,
|
||
}; |