diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..25eaf1f --- /dev/null +++ b/.editorconfig @@ -0,0 +1,26 @@ +root = true + +[*] +charset = utf-8 +indent_style = space +indent_size = 2 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true + +[*.{js,ts,css,less,scss,vue,html,json,md}] +indent_style = space +indent_size = 2 + +[*.{yml,yaml}] +indent_style = space +indent_size = 2 + +[*.md] +trim_trailing_whitespace = false + +[*.wxss] +indent_size = 2 + +[*.json] +indent_size = 2 diff --git a/hooks/useBluetoothDiscovery.js b/hooks/useBluetoothDiscovery.js index 55151ff..20fccc7 100644 --- a/hooks/useBluetoothDiscovery.js +++ b/hooks/useBluetoothDiscovery.js @@ -7,8 +7,8 @@ import { TARGET_SERVICE_UUID_COMPAT_FULL, TARGET_SHORT_UUID_CANDIDATES } from '@/constants/bluetooth' -import { DEVICE_PROFILE, createUnifiedBleController, shortUuid, uuidInList } from '@/utils/ble' -import { showToast } from '@/utils/toast' +import {DEVICE_PROFILE, createUnifiedBleController, shortUuid, uuidInList} from '@/utils/ble' +import {showToast} from '@/utils/toast' const FILTER_SCAN_TIMEOUT_MS = 4000 const RESTART_SCAN_DELAY_MS = 120 @@ -33,18 +33,30 @@ function promisifyUniApi(apiName, params = {}) { */ export function createBluetoothDiscovery(options = {}) { const { - onSearchingChange = () => {}, - onDeviceMapChange = () => {}, - onDeviceListChange = () => {}, - onConnectedChange = () => {}, - onProfileChange = () => {}, - onStageChange = () => {}, - onProtocolState = () => {}, - onProtocolMsg = () => {}, - onRadarData = () => {}, - onDeviceInfoChange = () => {}, - onAuthChange = () => {}, - onError = () => {} + onSearchingChange = () => { + }, + onDeviceMapChange = () => { + }, + onDeviceListChange = () => { + }, + onConnectedChange = () => { + }, + onProfileChange = () => { + }, + onStageChange = () => { + }, + onProtocolState = () => { + }, + onProtocolMsg = () => { + }, + onRadarData = () => { + }, + onDeviceInfoChange = () => { + }, + onAuthChange = () => { + }, + onError = () => { + } } = options const controller = createUnifiedBleController({ @@ -103,7 +115,7 @@ export function createBluetoothDiscovery(options = {}) { function setDeviceMap(nextMap) { deviceMap = nextMap - onDeviceMapChange({ ...deviceMap }) + onDeviceMapChange({...deviceMap}) emitDeviceList() } @@ -361,6 +373,7 @@ export function createBluetoothDiscovery(options = {}) { } offStateChange = controller.ble.on('state:change', (state) => { + console.debug('state:change', state) onStageChange(state.stage) if (state.profileId && state.profileId !== currentProfileId) { @@ -404,12 +417,18 @@ export function createBluetoothDiscovery(options = {}) { }) offProtocolState = controller.ble.on('protocol:state', (payload) => { - try { console.debug('[BLE][STATE]', 'code=', payload?.stateCode, 'char=', payload?.characteristicId) } catch (e) {} + try { + console.debug('[BLE][STATE]', 'code=', payload?.stateCode, 'char=', payload?.characteristicId) + } catch (e) { + } onProtocolState(payload) }) offProtocolMsg = controller.ble.on('protocol:msg', (payload) => { - try { console.debug('[BLE][MSG]', 'len=', payload?.value?.length || 0) } catch (e) {} + try { + console.debug('[BLE][MSG]', 'len=', payload || 0) + } catch (e) { + } onProtocolMsg(payload) if (payload?.deviceInfo) { lastDeviceInfo = payload.deviceInfo @@ -426,7 +445,10 @@ export function createBluetoothDiscovery(options = {}) { }) offError = controller.ble.on('error', (error) => { - try { console.debug('[BLE][ERR]', error?.message || error?.errMsg || String(error)) } catch (e) {} + try { + console.debug('[BLE][ERR]', error?.message || error?.errMsg || String(error)) + } catch (e) { + } onError(error) }) } @@ -589,7 +611,7 @@ export function createBluetoothDiscovery(options = {}) { const adapterState = await controller.ble.getAdapterState() if (adapterState?.available === false) { - throw { errCode: 10001 } + throw {errCode: 10001} } await stopDiscoverySilently() @@ -759,7 +781,11 @@ export function createBluetoothDiscovery(options = {}) { async function configureWifi(ssid, password, options = {}) { const profileId = controller.ble.getProfile().id const preDelayMs = options.preDelayMs ?? (profileId === DEVICE_PROFILE.ED713 ? 120 : 0) - return controller.wifi.configureWifi(ssid, password, { ...options, preDelayMs }) + try { + console.debug('[BLE][WiFi]', 'configureWifi called', {profileId, preDelayMs}) + } catch (e) { + } + return controller.wifi.configureWifi(ssid, password, {...options, preDelayMs}) } async function startRadar() { @@ -796,7 +822,7 @@ export function createBluetoothDiscovery(options = {}) { isConnected, currentDeviceId, currentProfileId: controller.ble.getProfile().id, - deviceMap: { ...deviceMap }, + deviceMap: {...deviceMap}, deviceList: getSortedDeviceList(), lastDeviceInfo, lastAuthResult, @@ -824,7 +850,8 @@ export function createBluetoothDiscovery(options = {}) { } initialized = false - controller.cleanup().catch(() => {}) + controller.cleanup().catch(() => { + }) } return { diff --git a/pages/device/config.vue b/pages/device/config.vue index e5afc2e..fb568c2 100644 --- a/pages/device/config.vue +++ b/pages/device/config.vue @@ -335,7 +335,7 @@ export default { */ await this.discoveryController.prepareWifiConfig(targetDeviceId, { suffix: '0000', - timeout: 8000 + timeout: 800000 }) this.wifiPrepared = true diff --git a/utils/ble/core/bleCore.js b/utils/ble/core/bleCore.js index c9f721b..69c96e6 100644 --- a/utils/ble/core/bleCore.js +++ b/utils/ble/core/bleCore.js @@ -89,6 +89,7 @@ export function createBleCore(options = {}) { return normalized } + /** 打开蓝牙适配器,成功后标记可用并进入 ADAPTER_OPENED 阶段。 */ async function openAdapter() { try { await promisifyUniApi('openBluetoothAdapter') @@ -100,6 +101,7 @@ export function createBleCore(options = {}) { } } + /** 关闭蓝牙适配器并重置全部运行时状态。 */ async function closeAdapter() { try { await promisifyUniApi('closeBluetoothAdapter') @@ -118,6 +120,7 @@ export function createBleCore(options = {}) { }) } + /** 读取当前适配器状态(可用/搜索中),同步到内部状态。 */ async function getAdapterState() { try { const result = await promisifyUniApi('getBluetoothAdapterState') @@ -131,6 +134,7 @@ export function createBleCore(options = {}) { } } + /** 注册适配器状态变更监听,蓝牙关闭时自动切换到 DISCONNECTED。 */ function watchAdapterState() { if (adapterStateHandler) { return @@ -151,6 +155,7 @@ export function createBleCore(options = {}) { uni.onBluetoothAdapterStateChange(adapterStateHandler) } + /** 移除适配器状态变更监听。 */ function unwatchAdapterState() { if (!adapterStateHandler) { return @@ -202,6 +207,7 @@ export function createBleCore(options = {}) { } } + /** 停止设备扫描。 */ async function stopDiscovery() { try { await promisifyUniApi('stopBluetoothDevicesDiscovery') @@ -212,6 +218,7 @@ export function createBleCore(options = {}) { patchState({ discovering: false }) } + /** 注册设备发现监听,按 deviceId 去重合并后广播 device:found。 */ function watchDeviceFound() { if (deviceFoundHandler) { return @@ -242,6 +249,7 @@ export function createBleCore(options = {}) { uni.onBluetoothDeviceFound(deviceFoundHandler) } + /** 移除设备发现监听。 */ function unwatchDeviceFound() { if (!deviceFoundHandler) { return @@ -254,6 +262,13 @@ export function createBleCore(options = {}) { deviceFoundHandler = null } + /** + * 建立BLE连接。 + * @param {string} deviceId 设备ID + * @param {object} options + * @param {number} options.timeout 连接超时(ms),默认15000 + * @param {boolean} options.autoReconnect 是否自动重试,默认跟随初始化配置 + */ async function connect(deviceId, options = {}) { if (!deviceId) { throw emitError(createBleError(10013, 'deviceId 不能为空')) @@ -290,6 +305,7 @@ export function createBleCore(options = {}) { } } + /** 断开BLE连接并重置连接相关状态。 */ async function disconnect(deviceId = state.deviceId) { if (!deviceId) { return @@ -305,6 +321,7 @@ export function createBleCore(options = {}) { setStage(BLE_STAGE.DISCONNECTED) } + /** 注册连接状态变更监听,断开时自动切换到 DISCONNECTED。 */ function watchConnectionChange() { if (connectionStateHandler) { return @@ -326,6 +343,7 @@ export function createBleCore(options = {}) { uni.onBLEConnectionStateChange(connectionStateHandler) } + /** 移除连接状态变更监听。 */ function unwatchConnectionChange() { if (!connectionStateHandler) { return @@ -338,6 +356,7 @@ export function createBleCore(options = {}) { connectionStateHandler = null } + /** 获取候选服务UUID列表:profile主服务 + 兼容固件的 00F4/F400/FFF4。 */ function getServiceCandidates() { const profileService = currentProfile?.uuids?.service const candidates = [ @@ -380,6 +399,11 @@ export function createBleCore(options = {}) { } } + /** + * 发现特征值并按业务用途缓存(wifi/key/cmd/msg/state/radar)。 + * @param {object} options 可选覆盖 deviceId/serviceId + * @returns {object} characteristicMap 各业务特征 + all 全量列表 + */ async function discoverCharacteristics(options = {}) { const deviceId = options.deviceId || state.deviceId const serviceId = options.serviceId || state.serviceId @@ -411,6 +435,7 @@ export function createBleCore(options = {}) { } } + /** 读取指定特征值(触发 onBLECharacteristicValueChange 回调)。 */ async function readCharacteristic(characteristicId, options = {}) { const deviceId = options.deviceId || state.deviceId const serviceId = options.serviceId || state.serviceId @@ -431,6 +456,7 @@ export function createBleCore(options = {}) { } } + /** 向指定特征值写入数据,自动将输入转为 ArrayBuffer。 */ async function writeCharacteristic(characteristicId, value, options = {}) { const deviceId = options.deviceId || state.deviceId const serviceId = options.serviceId || state.serviceId @@ -452,6 +478,7 @@ export function createBleCore(options = {}) { } } + /** 订阅/取消订阅指定特征值的通知。 */ async function notifyCharacteristic(characteristicId, stateFlag = true, options = {}) { const deviceId = options.deviceId || state.deviceId const serviceId = options.serviceId || state.serviceId @@ -489,6 +516,7 @@ export function createBleCore(options = {}) { uni.onBLECharacteristicValueChange(characteristicValueHandler) } + /** 移除特征值变更监听。 */ function unwatchCharacteristicValue() { if (!characteristicValueHandler) { return @@ -501,10 +529,12 @@ export function createBleCore(options = {}) { characteristicValueHandler = null } + /** 返回已发现设备列表。 */ function listDiscoveredDevices() { return Object.values(state.deviceMap) } + /** 从已发现设备中,按广播服务UUID匹配并返回第一个设备。 */ function pickDeviceByService(serviceUuid = currentProfile.uuids.service) { const list = listDiscoveredDevices() diff --git a/utils/ble/core/controller.js b/utils/ble/core/controller.js index 9906a3d..2dbe0c2 100644 --- a/utils/ble/core/controller.js +++ b/utils/ble/core/controller.js @@ -1,11 +1,16 @@ -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' +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。 + * 雷达帧还能自动推断设备 profile(ED713/ED719)。 + */ function createCharacteristicDispatcher(ble) { return (event) => { const state = ble.getState() @@ -56,6 +61,11 @@ function createCharacteristicDispatcher(ble) { } } +/** + * 统一BLE控制器:整合核心能力 + 认证/WiFi/雷达三大业务模块。 + * 页面层应通过此控制器编排完整业务流程。 + * @param {object} options 透传给 createBleCore 的配置(重试策略/日志/profileId等) + */ export function createUnifiedBleController(options = {}) { const ble = createBleCore(options) const auth = createAuthModule(ble) @@ -65,6 +75,7 @@ export function createUnifiedBleController(options = {}) { const characteristicDispatcher = createCharacteristicDispatcher(ble) let offCharacteristic = null + /** 绑定特征值分发器到 ble 事件总线。 */ function bindDispatcher() { if (offCharacteristic) { return @@ -73,6 +84,7 @@ export function createUnifiedBleController(options = {}) { offCharacteristic = ble.on('characteristic:value', characteristicDispatcher) } + /** 解绑特征值分发器。 */ function unbindDispatcher() { if (!offCharacteristic) { return @@ -82,6 +94,7 @@ export function createUnifiedBleController(options = {}) { offCharacteristic = null } + /** 初始化蓝牙适配器并注册所有监听(适配器状态/连接/特征值/分发器)。 */ async function init() { await ble.openAdapter() ble.watchAdapterState() @@ -90,24 +103,33 @@ export function createUnifiedBleController(options = {}) { 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 - } = {}) { + serviceUuid, + scanDuration = 5000, + fallbackWithoutFilter = true + } = {}) { const profile = ble.getProfile() const targetService = serviceUuid || profile.uuids.service ble.watchDeviceFound() - await ble.startDiscovery({ services: [targetService] }) + 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 ble.startDiscovery({services: []}) await new Promise((resolve) => setTimeout(resolve, scanDuration)) await ble.stopDiscovery() selected = ble.pickDeviceByService(targetService) || ble.listDiscoveredDevices()[0] || null @@ -127,11 +149,21 @@ export function createUnifiedBleController(options = {}) { const chars = await ble.discoverCharacteristics() if (chars.state && chars.state.uuid) { - try { await ble.notifyCharacteristic(chars.state.uuid, true); console.debug('[BLE][SUB]', 'notify STATE ok', chars.state.uuid) } catch (e) { console.debug('[BLE][SUB]', 'notify STATE fail', e?.message || e) } + try { + await ble.notifyCharacteristic(chars.state.uuid, true); + console.debug('[BLE][SUB]', 'notify STATE ok', chars.state.uuid) + } catch (e) { + console.debug('[BLE][SUB]', 'notify STATE fail', e?.message || e) + } } 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) } + 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 @@ -141,7 +173,7 @@ export function createUnifiedBleController(options = {}) { * 标准流程:连接 -> 订阅 -> 鉴权。 * 鉴权内部会按 bindStatus 分支处理绑定/匹配。 */ - async function standardConnectFlow({ deviceId, key }) { + async function standardConnectFlow({deviceId, key}) { await init() let targetDeviceId = deviceId @@ -165,6 +197,7 @@ export function createUnifiedBleController(options = {}) { } } + /** 释放所有资源:解绑分发器 + 清理核心BLE(停扫描/断连接/移监听/关适配器)。 */ async function cleanup() { unbindDispatcher() await ble.cleanup() diff --git a/utils/ble/core/eventBus.js b/utils/ble/core/eventBus.js index 2d1e227..9f539b5 100644 --- a/utils/ble/core/eventBus.js +++ b/utils/ble/core/eventBus.js @@ -4,6 +4,7 @@ export function createEventBus() { const listeners = {} + /** 订阅事件,返回取消订阅函数。 */ function on(eventName, handler) { if (!listeners[eventName]) { listeners[eventName] = new Set() @@ -17,6 +18,7 @@ export function createEventBus() { } } + /** 单次订阅:触发一次后自动取消。 */ function once(eventName, handler) { const unsubscribe = on(eventName, (payload) => { unsubscribe() @@ -26,6 +28,7 @@ export function createEventBus() { return unsubscribe } + /** 取消指定事件的某个监听。 */ function off(eventName, handler) { if (!listeners[eventName]) { return @@ -34,6 +37,7 @@ export function createEventBus() { listeners[eventName].delete(handler) } + /** 广播事件,所有监听器同步执行(内部已 try-catch 隔离)。 */ function emit(eventName, payload) { if (!listeners[eventName]) { return @@ -48,6 +52,7 @@ export function createEventBus() { }) } + /** 清空所有事件监听。 */ function clear() { Object.keys(listeners).forEach((eventName) => { listeners[eventName].clear() diff --git a/utils/ble/core/packet.js b/utils/ble/core/packet.js index b06fddc..918e7dd 100644 --- a/utils/ble/core/packet.js +++ b/utils/ble/core/packet.js @@ -1,5 +1,5 @@ -import { splitUint8Array, toUint8Array } from '../utils/bytes' -import { utf8ToBytes } from '../utils/hex' +import {splitUint8Array, toUint8Array} from '../utils/bytes' +import {utf8ToBytes} from '../utils/hex' /** * 按协议将 payload 拆成 A7 分包: diff --git a/utils/ble/core/stateWait.js b/utils/ble/core/stateWait.js index b272e69..0109c50 100644 --- a/utils/ble/core/stateWait.js +++ b/utils/ble/core/stateWait.js @@ -21,6 +21,10 @@ export function waitForProtocolState(ble, options = {}) { return new Promise((resolve, reject) => { let timer = null let off = null + try { + console.debug('[BLE][STATEWAIT]', 'start', {allowed: allowedCodes.join(',') || 'ANY', timeout}) + } catch (e) { + } const finish = (callback) => { if (timer) { @@ -35,6 +39,10 @@ export function waitForProtocolState(ble, options = {}) { } timer = setTimeout(() => { + try { + console.debug('[BLE][STATEWAIT]', 'timeout') + } catch (e) { + } finish(() => reject(new Error(timeoutMessage))) }, timeout) @@ -42,11 +50,19 @@ export function waitForProtocolState(ble, options = {}) { const stateCode = Number(payload?.stateCode) if (!allowedCodes.length || allowedCodes.includes(stateCode)) { + try { + console.debug('[BLE][STATEWAIT]', 'accept', stateCode) + } catch (e) { + } finish(() => resolve(payload)) return } if (rejectOnUnexpected) { + try { + console.debug('[BLE][STATEWAIT]', 'reject', stateCode) + } catch (e) { + } finish(() => reject(new Error(`${unexpectedMessage}: ${stateCode}`))) } }) diff --git a/utils/ble/modules/auth.js b/utils/ble/modules/auth.js index dd60891..d8f7e24 100644 --- a/utils/ble/modules/auth.js +++ b/utils/ble/modules/auth.js @@ -1,8 +1,9 @@ -import { writeA7Payload } from '../core/packet' -import { waitForProtocolState } from '../core/stateWait' -import { parseDeviceInfo } from '../parsers' -import { BLE_STAGE } from '../core/state' +import {writeA7Payload} from '../core/packet' +import {waitForProtocolState} from '../core/stateWait' +import {parseDeviceInfo} from '../parsers' +import {BLE_STAGE} from '../core/state' +/** 生成指定长度的随机密钥(排除易混淆字符 0/O/1/I/l)。 */ function generateRandomKey(length = 16) { const chars = 'ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnpqrstuvwxyz23456789' let result = '' @@ -15,8 +16,14 @@ function generateRandomKey(length = 16) { return result } -const authLog = (...args) => { try { console.debug('[BLE][AUTH]', ...args) } catch (e) {} } +const authLog = (...args) => { + try { + console.debug('[BLE][AUTH]', ...args) + } catch (e) { + } +} +/** 将 MAC 地址去除非十六进制字符并转大写,用于密钥拼接。 */ function normalizeMacForKey(macText = '') { return String(macText || '').replace(/[^0-9a-fA-F]/g, '').toUpperCase() } @@ -38,15 +45,21 @@ export function buildKeyFromMac(macText, suffix = '0000', expectedLength = 16) { throw new Error('密钥长度配置异常') } - if (normalizedMac.length !== requiredMacLength) { - throw new Error(`设备 MAC 长度异常,期望 ${requiredMacLength} 位,实际 ${normalizedMac.length} 位`) - } + // if (normalizedMac.length !== requiredMacLength) { + // throw new Error(`设备 MAC 长度异常,期望 ${requiredMacLength} 位,实际 ${normalizedMac.length} 位`) + // } return `${normalizedMac}${normalizedSuffix}` } +/** + * 将密钥通过A7分包写入 UUID_KEY 特征值。 + * @param {object} ble createBleCore 实例 + * @param {string} keyText 密钥文本 + * @returns {string} 实际写入的密钥值 + */ async function writePacketsToKeyCharacteristic(ble, keyText) { - const { key } = ble.getState().characteristics + const {key} = ble.getState().characteristics if (!key || !key.uuid) { throw new Error('未发现 UUID_KEY 特征值') @@ -55,15 +68,23 @@ async function writePacketsToKeyCharacteristic(ble, keyText) { const profile = ble.getProfile() const keyValue = String(keyText || '').trim() - if (keyValue.length !== profile.auth.keyLength) { - throw new Error(`密钥长度必须为 ${profile.auth.keyLength}`) - } + // if (keyValue.length !== profile.auth.keyLength) { + // throw new Error(`密钥长度必须为 ${profile.auth.keyLength}`) + // } await writeA7Payload(ble, key.uuid, keyValue, profile.packet) return keyValue } +/** + * 写入密钥并等待 UUID_STATE 返回预期状态码。 + * 成功后自动设置 AUTHORIZED 阶段和 keyMatched 状态。 + * @param {object} ble createBleCore 实例 + * @param {string} keyText 密钥文本 + * @param {number[]} expectedStateCodes 期望的状态码列表 + * @param {object} options 超时等配置 + */ async function writeKeyAndExpectStates(ble, keyText, expectedStateCodes, options = {}) { const waitPromise = waitForProtocolState(ble, { allowedCodes: expectedStateCodes, @@ -78,10 +99,16 @@ async function writeKeyAndExpectStates(ble, keyText, expectedStateCodes, options const stateCode = Number((await waitPromise).stateCode) authLog('STATE received for KEY=', stateCode) ble.setStage(BLE_STAGE.AUTHORIZED) - ble.setAuthState({ keyMatched: true, lastStateCode: stateCode }) - return { success: true, stateCode, key: keyValue } + ble.setAuthState({keyMatched: true, lastStateCode: stateCode}) + return {success: true, stateCode, key: keyValue} } +/** + * 根据设备绑定状态执行鉴权流程: + * - bindStatus=0(未绑定):生成随机密钥写入,等待 STATE=5(绑定成功) + * - bindStatus=1(已绑定):使用已知密钥匹配,等待 STATE=7(匹配成功) + * - 其他状态:尝试用提供的密钥匹配,等待 STATE=7 或 5 + */ async function ensureAuthorizedWithDeviceInfo(ble, info, keyText, options = {}) { const bindStatus = Number(info.bindStatus) const profile = ble.getProfile() @@ -134,9 +161,17 @@ async function ensureAuthorizedWithDeviceInfo(ble, info, keyText, options = {}) } } +/** + * 认证模块工厂:封装设备绑定/密钥匹配全流程。 + * @param {object} ble createBleCore 实例 + */ export function createAuthModule(ble) { + /** + * 读取 UUID_MSG 获取设备信息(MAC/绑定状态/产品ID等)。 + * 先订阅 protocol:msg 事件,再触发 read,保证不丢通知。 + */ async function readDeviceInfo() { - const { msg } = ble.getState().characteristics + const {msg} = ble.getState().characteristics if (!msg || !msg.uuid) { throw new Error('未发现 UUID_MSG 特征值') diff --git a/utils/ble/modules/radar.js b/utils/ble/modules/radar.js index ebe9673..2b83dbb 100644 --- a/utils/ble/modules/radar.js +++ b/utils/ble/modules/radar.js @@ -1,5 +1,10 @@ import { parseRadarData } from '../parsers' +/** + * 雷达控制模块:管理雷达启停、窄床模式、跌倒参数写入。 + * 所有命令发送前均校验鉴权状态。 + * @param {object} ble createBleCore 实例 + */ export function createRadarModule(ble) { function requireAuthorized() { const state = ble.getState() @@ -10,6 +15,10 @@ export function createRadarModule(ble) { } } + /** + * 向 UUID_CMD 写入单字节指令。 + * @param {number} commandByte 指令字节(如 0xA1 启动、0xA2 停止) + */ async function sendCommand(commandByte) { const { cmd } = ble.getState().characteristics @@ -20,6 +29,7 @@ export function createRadarModule(ble) { await ble.writeCharacteristic(cmd.uuid, new Uint8Array([commandByte])) } + /** 启动雷达数据流(写入 CMD = 0xA1)。 */ async function startRadarStream() { const profile = ble.getProfile() @@ -29,6 +39,7 @@ export function createRadarModule(ble) { return true } + /** 停止雷达数据流(写入 CMD = 0xA2)。 */ async function stopRadarStream() { const profile = ble.getProfile() @@ -38,6 +49,10 @@ export function createRadarModule(ble) { return true } + /** + * 设置窄床模式(仅 ED713 支持)。 + * @param {boolean} enabled true=开启(0x7D),false=关闭(0x7C) + */ async function setNarrowMode(enabled) { const profile = ble.getProfile() @@ -49,6 +64,10 @@ export function createRadarModule(ble) { await sendCommand(enabled ? 0x7D : 0x7C) } + /** + * 写入跌倒参数(仅 ED719 支持,指令前缀 0x67)。 + * @param {Uint8Array|number[]} rawParams 跌倒参数字节 + */ async function writeFallParams(rawParams) { const profile = ble.getProfile() @@ -71,6 +90,7 @@ export function createRadarModule(ble) { await ble.writeCharacteristic(cmd.uuid, packet) } + /** 按当前 profile 解析雷达通知数据。 */ function parseNotifyPayload(payload) { const profile = ble.getProfile() return parseRadarData(payload, profile.id) diff --git a/utils/ble/modules/wifi.js b/utils/ble/modules/wifi.js index f012484..9a15ad1 100644 --- a/utils/ble/modules/wifi.js +++ b/utils/ble/modules/wifi.js @@ -27,6 +27,7 @@ function validateWifi(profile, ssid, password) { } } +/** 校验鉴权状态,未完成密钥匹配则抛出异常。 */ function requireAuthorized(ble) { const state = ble.getState() @@ -37,6 +38,8 @@ function requireAuthorized(ble) { /** * WiFi 配网模块:负责打包发送 ssid|password,并等待 STATE 回执。 + * 使用前必须先完成密钥鉴权。 + * @param {object} ble createBleCore 实例 */ export function createWifiModule(ble) { function delay(ms) { @@ -44,17 +47,54 @@ export function createWifiModule(ble) { } const log = (...args) => { try { console.debug('[BLE][WiFi]', ...args) } catch (e) {} } + /** + * 发送WiFi配网指令。 + * 将 ssid|password 按 A7 分包写入 UUID_WIFI,等待 STATE 回执判定结果。 + * @param {string} ssid WiFi名称 + * @param {string} password WiFi密码 + * @param {object} options + * @param {number} options.timeout 等待状态回执超时(ms),默认12000 + * @param {number} options.preDelayMs 写入前延迟(ms),ED713 默认120ms + * @param {number} options.packetInterval 分包写入间隔(ms) + * @returns {{ success: boolean, stateCode: number }} stateCode=3 表示连接成功 + */ async function configureWifi(ssid, password, options = {}) { const profile = ble.getProfile() - const s = String(ssid || '').trim(), p = String(password || '').trim() - validateWifi(profile, s, p); requireAuthorized(ble) - const { wifi } = ble.getState().characteristics; if (!wifi || !wifi.uuid) throw new Error('未发现 UUID_WIFI 特征值') - const waitPromise = waitForProtocolState(ble, { allowedCodes: WIFI_FINAL_STATE_CODES, rejectOnUnexpected: false, timeout: options.timeout || 12000, timeoutMessage: '等待 WiFi 状态超时' }) - const pre = options.preDelayMs ?? (ble.getProfile().id === 'ED713' ? 120 : 0); if (pre) { log('preDelay before WiFi write(ms)=', pre); await delay(pre) } + const s = String(ssid || '').trim() + const p = String(password || '').trim() + validateWifi(profile, s, p) + requireAuthorized(ble) + + const { wifi } = ble.getState().characteristics + if (!wifi || !wifi.uuid) { + throw new Error('未发现 UUID_WIFI 特征值') + } + + // 先注册 STATE 等待,再写入,确保不漏回执 + const waitPromise = waitForProtocolState(ble, { + allowedCodes: WIFI_FINAL_STATE_CODES, + rejectOnUnexpected: false, + timeout: options.timeout || 12000, + timeoutMessage: '等待 WiFi 状态超时' + }) + + // ED713 固件需要写入前延迟,否则可能丢包 + const pre = options.preDelayMs ?? (profile.id === 'ED713' ? 120 : 0) + if (pre) { + log('preDelay before WiFi write(ms)=', pre) + await delay(pre) + } + log('write WiFi start', { service: ble.getState().serviceId, char: wifi.uuid }) - const sent = await writeA7Payload(ble, wifi.uuid, `${s}|${p}`, profile.packet, { packetInterval: Number(options.packetInterval) || 0 }) + const sent = await writeA7Payload(ble, wifi.uuid, `${s}|${p}`, profile.packet, { + packetInterval: Number(options.packetInterval) || 0 + }) log('write WiFi done, packets=', sent) - const code = Number((await waitPromise).stateCode); log('STATE(final) received for WiFi=', code) + + const code = Number((await waitPromise).stateCode) + log('STATE(final) received for WiFi=', code) + + // STATE=3 表示 WiFi 连接成功 return { success: code === 3, stateCode: code } } diff --git a/utils/ble/parsers/commonParser.js b/utils/ble/parsers/commonParser.js index cfcb298..f669207 100644 --- a/utils/ble/parsers/commonParser.js +++ b/utils/ble/parsers/commonParser.js @@ -1,5 +1,5 @@ -import { bytesToMac } from '../utils/hex' -import { joinLowHigh, readUint16LE, toUint8Array } from '../utils/bytes' +import {bytesToHex, bytesToMac} from '../utils/hex' +import {joinLowHigh, readUint16LE, toUint8Array} from '../utils/bytes' /** * 解析 UUID_STATE 的状态字节,转为有符号 8bit。 diff --git a/utils/ble/parsers/index.js b/utils/ble/parsers/index.js index 89ce317..ce18c23 100644 --- a/utils/ble/parsers/index.js +++ b/utils/ble/parsers/index.js @@ -1,3 +1,7 @@ +/** + * 解析器统一出口。 + * 对外暴露 parseStateCode / parseDeviceInfo / parseRadarData 三个顶层函数。 + */ import { parseDeviceInfo, parseEd713Radar, parseEd719Radar, parseStateCode } from './commonParser' import { DEVICE_PROFILE } from '../protocols/profiles' diff --git a/utils/ble/protocols/profiles.js b/utils/ble/protocols/profiles.js index c04ff5b..4cc2444 100644 --- a/utils/ble/protocols/profiles.js +++ b/utils/ble/protocols/profiles.js @@ -116,6 +116,11 @@ export const BLE_PROFILE_MAP = Object.freeze({ [DEVICE_PROFILE.ED719]: ed719Profile }) +/** + * 根据设备型号获取对应 BLE Profile 配置。 + * @param {string} profileId 设备型号ID,默认 UNKNOWN + * @returns {object} profile 配置对象 + */ export function getBleProfile(profileId = DEVICE_PROFILE.UNKNOWN) { return BLE_PROFILE_MAP[profileId] || BLE_PROFILE_MAP[DEVICE_PROFILE.UNKNOWN] }