SEC_Warehouse/utils/ble/core/controller.js

244 lines
7.0 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { createBleCore } from './bleCore';
import { createAuthModule } from '../modules/auth';
import { createWifiModule } from '../modules/wifi';
import { createRadarModule } from '../modules/radar';
import { parseStateCode, parseDeviceInfo } from '../parsers';
import {
DEVICE_PROFILE,
guessProfileByRadarFrame,
} from '../protocols/profiles';
import { uuidEquals } from '../utils/uuid';
/**
* 创建特征值通知分发器。
* 根据特征UUID将通知分发到对应协议事件state / msg / radar / raw。
* 雷达帧还能自动推断设备 profileED713/ED719
*/
function createCharacteristicDispatcher(ble) {
return (event) => {
const state = ble.getState();
const profile = ble.getProfile();
const current = state.characteristics;
if (
current.state &&
uuidEquals(event.characteristicId, current.state.uuid)
) {
const stateCode = parseStateCode(event.value);
ble.setAuthState({
lastStateCode: stateCode,
keyMatched: stateCode === 7 || stateCode === 5 || state.auth.keyMatched,
});
ble.emit('protocol:state', {
...event,
profileId: profile.id,
stateCode,
});
return;
}
if (current.msg && uuidEquals(event.characteristicId, current.msg.uuid)) {
ble.emit('protocol:msg', {
...event,
profileId: profile.id,
deviceInfo: parseDeviceInfo(event.value, profile.id),
});
return;
}
if (
current.radar &&
uuidEquals(event.characteristicId, current.radar.uuid)
) {
const guessed = guessProfileByRadarFrame(event.value);
if (
state.profileId === DEVICE_PROFILE.UNKNOWN &&
guessed !== DEVICE_PROFILE.UNKNOWN
) {
ble.setProfile(guessed);
}
ble.emit('protocol:radar', {
...event,
profileId: ble.getProfile().id,
});
return;
}
ble.emit('protocol:raw', {
...event,
profileId: profile.id,
});
};
}
/**
* 统一BLE控制器整合核心能力 + 认证/WiFi/雷达三大业务模块。
* 页面层应通过此控制器编排完整业务流程。
* @param {object} options 透传给 createBleCore 的配置(重试策略/日志/profileId等
*/
export function createUnifiedBleController(options = {}) {
const ble = createBleCore(options);
const auth = createAuthModule(ble);
const wifi = createWifiModule(ble);
const radar = createRadarModule(ble);
const characteristicDispatcher = createCharacteristicDispatcher(ble);
let offCharacteristic = null;
/** 绑定特征值分发器到 ble 事件总线。 */
function bindDispatcher() {
if (offCharacteristic) {
return;
}
offCharacteristic = ble.on(
'characteristic:value',
characteristicDispatcher,
);
}
/** 解绑特征值分发器。 */
function unbindDispatcher() {
if (!offCharacteristic) {
return;
}
offCharacteristic();
offCharacteristic = null;
}
/** 初始化蓝牙适配器并注册所有监听(适配器状态/连接/特征值/分发器)。 */
async function init() {
await ble.openAdapter();
ble.watchAdapterState();
ble.watchConnectionChange();
ble.watchCharacteristicValue();
bindDispatcher();
}
/**
* 扫描并选取目标设备。
* 先按服务UUID过滤扫描无结果时可退化为无过滤扫描。
* @param {object} params
* @param {string} params.serviceUuid 目标服务UUID默认取 profile 配置
* @param {number} params.scanDuration 扫描时长(ms)默认5000
* @param {boolean} params.fallbackWithoutFilter 首次无结果时是否退化为无过滤扫描默认true
* @returns {object|null} 匹配到的设备对象,未找到返回 null
*/
async function scanAndPickDevice({
serviceUuid,
scanDuration = 5000,
fallbackWithoutFilter = true,
} = {}) {
const profile = ble.getProfile();
const targetService = serviceUuid || profile.uuids.service;
ble.watchDeviceFound();
await ble.startDiscovery({ services: [targetService] });
await new Promise((resolve) => setTimeout(resolve, scanDuration));
await ble.stopDiscovery();
let selected = ble.pickDeviceByService(targetService);
if (!selected && fallbackWithoutFilter) {
await ble.startDiscovery({ services: [] });
await new Promise((resolve) => setTimeout(resolve, scanDuration));
await ble.stopDiscovery();
selected =
ble.pickDeviceByService(targetService) ||
ble.listDiscoveredDevices()[0] ||
null;
}
return selected;
}
/**
* 连接后执行协议前置(厂家确认流程):
* 1) 发现服务和特征
* 2) 订阅 UUID_STATE(F301) / UUID_RADAR(F302)
*
* 厂家确认:无需读 MSG、无需鉴权订阅 F301 后直接写 F401 配网即可。
*/
async function connectAndDiscover(deviceId) {
await ble.connect(deviceId);
await ble.discoverServices(deviceId);
const chars = await ble.discoverCharacteristics();
if (chars.state && chars.state.uuid) {
// 打印 STATE 特征属性,区分 notify / indicate定位通知不投递问题
console.debug('[BLE][SUB]', 'STATE props', JSON.stringify(chars.state.properties || {}));
// STATE 是鉴权/WiFi 的唯一回执通道,订阅失败必须显式抛出,
// 否则后续写 KEY 后设备零回执,表现为"等待 UUID_STATE 超时"难以定位。
try {
await ble.notifyCharacteristic(chars.state.uuid, true);
console.debug('[BLE][SUB]', 'notify STATE ok', chars.state.uuid);
} catch (e) {
throw new Error(`订阅 UUID_STATE(F301) 失败:${e?.message || e}`);
}
} else {
throw new Error('未发现 UUID_STATE(F301) 特征值,无法接收设备回执');
}
if (chars.radar && chars.radar.uuid) {
try {
await ble.notifyCharacteristic(chars.radar.uuid, true);
console.debug('[BLE][SUB]', 'notify RADAR ok', chars.radar.uuid);
} catch (e) {
console.debug('[BLE][SUB]', 'notify RADAR fail', e?.message || e);
}
}
return chars;
}
/**
* 标准流程:连接 -> 订阅 -> 鉴权。
* 鉴权内部会按 bindStatus 分支处理绑定/匹配。
*/
async function standardConnectFlow({ deviceId, key }) {
await init();
let targetDeviceId = deviceId;
if (!targetDeviceId) {
const device = await scanAndPickDevice();
if (!device) {
throw new Error('未找到可连接设备');
}
targetDeviceId = device.deviceId;
}
await connectAndDiscover(targetDeviceId);
const authResult = await auth.ensureAuthorized(key);
return {
deviceId: targetDeviceId,
authResult,
state: ble.getState(),
profile: ble.getProfile(),
};
}
/** 释放所有资源:解绑分发器 + 清理核心BLE停扫描/断连接/移监听/关适配器)。 */
async function cleanup() {
unbindDispatcher();
await ble.cleanup();
}
return {
ble,
auth,
wifi,
radar,
init,
scanAndPickDevice,
connectAndDiscover,
standardConnectFlow,
cleanup,
};
}