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