From d1681ee08cba449902c12cc5171180e55bc97b32 Mon Sep 17 00:00:00 2001 From: ozh Date: Wed, 19 Aug 2026 10:06:35 +0800 Subject: [PATCH] =?UTF-8?q?refactor(ble):=20=E7=A7=BB=E9=99=A4=E5=AF=86?= =?UTF-8?q?=E9=92=A5=E9=89=B4=E6=9D=83=E6=B5=81=E7=A8=8B=EF=BC=8C=E6=94=B9?= =?UTF-8?q?=E4=B8=BA=E5=8E=82=E5=AE=B6=E7=A1=AE=E8=AE=A4=E7=9A=84=E8=BF=9E?= =?UTF-8?q?=E6=8E=A5=E8=AE=A2=E9=98=85=E6=B5=81=E7=A8=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - controller/hook 删除 readMsgOnce 时序前置与 authorizeDeviceByMac 调用, 连接后仅做发现服务、订阅 F301/F302,写 KEY 后由 WiFi 配网走 STATE=3 - 设备操作按钮改为按型号注册表渲染(EP832 仅展示网络配置),其余型号 保留网络配置/参数配置/服务器配置 - 鉴权相关提示文案改为连接/设备未连接语义,与无鉴权流程对齐 --- hooks/useBluetoothDiscovery.js | 12 +--- pages/device/config.vue | 108 ++++++++++++++++++++++++++------- utils/ble/core/controller.js | 47 +------------- 3 files changed, 91 insertions(+), 76 deletions(-) diff --git a/hooks/useBluetoothDiscovery.js b/hooks/useBluetoothDiscovery.js index c21b619..8d2a28f 100644 --- a/hooks/useBluetoothDiscovery.js +++ b/hooks/useBluetoothDiscovery.js @@ -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() { diff --git a/pages/device/config.vue b/pages/device/config.vue index 30790a5..19906ce 100644 --- a/pages/device/config.vue +++ b/pages/device/config.vue @@ -32,12 +32,15 @@ {{ device.deviceId }} - - {{ isDevicePreparing(device.deviceId) ? '鉴权中' : '网络配置' }} + + {{ getActionLabel(action, device) }} - 参数配置 - 服务器配置 @@ -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 对象包含 key、label、handler、isBusy 等元数据, + * 模板里直接 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 } diff --git a/utils/ble/core/controller.js b/utils/ble/core/controller.js index 16bc7e7..337bdea 100644 --- a/utils/ble/core/controller.js +++ b/utils/ble/core/controller.js @@ -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 || {}));