refactor(ble): 移除密钥鉴权流程,改为厂家确认的连接订阅流程

- controller/hook 删除 readMsgOnce 时序前置与 authorizeDeviceByMac 调用,
  连接后仅做发现服务、订阅 F301/F302,写 KEY 后由 WiFi 配网走 STATE=3
- 设备操作按钮改为按型号注册表渲染(EP832 仅展示网络配置),其余型号
  保留网络配置/参数配置/服务器配置
- 鉴权相关提示文案改为连接/设备未连接语义,与无鉴权流程对齐
This commit is contained in:
ozh 2026-08-19 10:06:35 +08:00
parent 388599e840
commit d1681ee08c
3 changed files with 91 additions and 76 deletions

View File

@ -798,16 +798,10 @@ export function createBluetoothDiscovery(options = {}) {
* WiFi配置专用流程连接 -> 发现 -> 订阅 -> MAC+0000 完成鉴权
*/
async function prepareWifiConfig(deviceId, options = {}) {
// 厂家确认:无需鉴权、无需读 MSG。
// 流程:连接 → 发现服务特征 → 订阅 F301完成后即可进入 WiFi 配置。
await connectDevice(deviceId)
// 厂家确认:密钥流程已不启用,写 KEY 后设备不回 STATE鉴权必然超时。
// 这里尽力尝试鉴权,失败则忽略并继续,由 WiFi 配网直接走(写 F401 后等 STATE=3
try {
return await authorizeDeviceByMac(options)
} catch (error) {
console.debug('[BLE][AUTH]', '鉴权失败已忽略(密钥流程已废弃)', error?.message || error)
return { mode: 'AUTH_SKIPPED', error: error?.message || String(error) }
}
return { mode: 'READY', deviceId }
}
async function readDeviceInfo() {

View File

@ -32,12 +32,15 @@
</view>
<text class="device-id">{{ device.deviceId }}</text>
<view class="device-actions">
<view class="action-btn" :class="{ disabled: isDevicePreparing(device.deviceId) }"
@click.stop="openWifiPanel(device.deviceId)">
{{ isDevicePreparing(device.deviceId) ? '鉴权中' : '网络配置' }}
<view
v-for="action in getDeviceActions(device)"
:key="action.key"
class="action-btn"
:class="{ disabled: isActionBusy(action, device) }"
@click.stop="invokeAction(action, device)"
>
{{ getActionLabel(action, device) }}
</view>
<view class="action-btn">参数配置</view>
<view class="action-btn">服务器配置</view>
</view>
</view>
</view>
@ -117,6 +120,30 @@ const WIFI_STATE_MESSAGES = Object.freeze({
4: '设备正在连接 WiFi请稍候'
})
// key
//
const DEVICE_ACTION_KEYS = Object.freeze({
EP832: ['WIFI'],
DEFAULT: ['WIFI', 'PARAMS', 'SERVER']
})
// label / / /
// key
const ACTION_REGISTRY = Object.freeze({
WIFI: {
label: '网络配置',
busyLabel: '连接中',
handler: 'openWifiPanel',
isBusy: 'isDevicePreparing'
},
PARAMS: {
label: '参数配置'
},
SERVER: {
label: '服务器配置'
}
})
function getErrorMessage(error) {
if (!error) {
return '操作失败,请重试'
@ -253,6 +280,47 @@ export default {
return Boolean(this.devicePreparingMap[safeDeviceId])
},
isEp832(device) {
const name = String(device?.showName || device?.name || device?.localName || '').toUpperCase()
return name.includes('EP832')
},
/**
* 根据设备型号返回该设备可见的操作按钮列表
* 列表中的 action 对象包含 keylabelhandlerisBusy 等元数据
* 模板里直接 v-for 渲染
*/
getDeviceActions(device) {
const identifier = this.isEp832(device) ? 'EP832' : 'DEFAULT'
const keys = DEVICE_ACTION_KEYS[identifier] || DEVICE_ACTION_KEYS.DEFAULT
return keys.map(key => ({ key, ...ACTION_REGISTRY[key] }))
},
isActionBusy(action, device) {
if (!action.isBusy) {
return false
}
const checker = this[action.isBusy]
return typeof checker === 'function' ? Boolean(checker.call(this, device.deviceId)) : false
},
getActionLabel(action, device) {
if (this.isActionBusy(action, device) && action.busyLabel) {
return action.busyLabel
}
return action.label
},
invokeAction(action, device) {
if (!action.handler) {
return
}
const handler = this[action.handler]
if (typeof handler === 'function') {
handler.call(this, device.deviceId)
}
},
getDeviceById(deviceId) {
const safeDeviceId = String(deviceId || '').trim()
if (!safeDeviceId) {
@ -314,7 +382,7 @@ export default {
}
if (this.activePreparingDeviceId && this.activePreparingDeviceId !== targetDeviceId) {
showToast('正在为其他设备鉴权,请稍候')
showToast('正在为其他设备准备,请稍候')
return
}
@ -331,27 +399,21 @@ export default {
this.setDevicePreparing(targetDeviceId, true)
uni.showLoading({
title: '密钥鉴权中',
title: '连接设备中',
mask: true
})
let authError = null
let prepError = null
try {
/**
* 先按项目固定已知密钥规则MAC+0000完成鉴权成功后再允许进入 WiFi 弹层
*/
await this.discoveryController.prepareWifiConfig(targetDeviceId, {
suffix: '0000',
timeout: 8000,
msgTimeout: 4000
})
// F301/MSG
await this.discoveryController.prepareWifiConfig(targetDeviceId)
this.wifiPrepared = true
this.wifiPanelVisible = true
// Vue DOM toast Vue
await this.$nextTick()
} catch (error) {
authError = error
prepError = error
await this.safeDisconnect()
} finally {
this.setDevicePreparing(targetDeviceId, false)
@ -362,16 +424,16 @@ export default {
uni.hideLoading()
}
if (authError) {
//
if (prepError) {
// /
uni.showModal({
title: '密钥鉴权失败',
content: getErrorMessage(authError),
title: '设备连接失败',
content: getErrorMessage(prepError),
showCancel: false,
confirmText: '知道了'
})
} else if (this.wifiPanelVisible) {
showToast('密钥鉴权成功请填写WiFi信息')
showToast('设备已连接请填写WiFi信息')
}
},
@ -463,7 +525,7 @@ export default {
}
if (!this.wifiPrepared) {
showToast('请先完成密钥鉴权')
showToast('设备未连接,请重新配置')
return
}

View File

@ -9,42 +9,6 @@ import {
} from '../protocols/profiles';
import { uuidEquals } from '../utils/uuid';
/**
* 订阅 F301 前先读一次 UUID_MSG(F501)满足厂家强制时序
* 先注册 protocol:msg 监听再触发读取避免回调早于监听丢失数据
*/
async function readMsgOnce(ble, chars, timeout = 4000) {
if (!chars || !chars.msg || !chars.msg.uuid) {
console.debug('[BLE][MSG]', 'readMsgOnce skipped: no MSG characteristic');
return null;
}
let timer = null;
let off = null;
try {
const payload = await new Promise((resolve, reject) => {
const finish = (cb) => {
if (timer) clearTimeout(timer);
if (off) off();
cb();
};
off = ble.on('protocol:msg', (data) => finish(() => resolve(data)));
timer = setTimeout(
() => finish(() => reject(new Error('读取 UUID_MSG 超时'))),
timeout
);
ble.readCharacteristic(chars.msg.uuid).catch((e) => finish(() => reject(e)));
});
const info = parseDeviceInfo(payload.value, ble.getProfile().id);
console.debug('[BLE][MSG]', 'readMsgOnce ok', JSON.stringify(info));
return info;
} catch (e) {
// 读 MSG 失败不阻断后续(仅作时序前置),但记录日志便于排查
console.debug('[BLE][MSG]', 'readMsgOnce failed', e?.message || e);
return null;
}
}
/**
* 创建特征值通知分发器
* 根据特征UUID将通知分发到对应协议事件state / msg / radar / raw
@ -193,22 +157,17 @@ export function createUnifiedBleController(options = {}) {
}
/**
* 连接后严格执行协议前置厂家要求顺序
* 连接后执行协议前置厂家确认流程
* 1) 发现服务和特征
* 2) 先读取 UUID_MSG(F501) 必须在订阅 F301 之前
* 3) 订阅 UUID_STATE(F301) / UUID_RADAR(F302)
* 2) 订阅 UUID_STATE(F301) / UUID_RADAR(F302)
*
* 厂家说明订阅 F301 前必须先读 F501否则后续设备不回 STATE 通知
* 表现为写 KEY/WIFI F301 零回执等待超时
* 厂家确认无需读 MSG无需鉴权订阅 F301 后直接写 F401 配网即可
*/
async function connectAndDiscover(deviceId) {
await ble.connect(deviceId);
await ble.discoverServices(deviceId);
const chars = await ble.discoverCharacteristics();
// 先读 UUID_MSG(F501),再订阅 F301 —— 厂家强制顺序
await readMsgOnce(ble, chars);
if (chars.state && chars.state.uuid) {
// 打印 STATE 特征属性,区分 notify / indicate定位通知不投递问题
console.debug('[BLE][SUB]', 'STATE props', JSON.stringify(chars.state.properties || {}));