refactor(ble): 移除密钥鉴权流程,改为厂家确认的连接订阅流程
- controller/hook 删除 readMsgOnce 时序前置与 authorizeDeviceByMac 调用, 连接后仅做发现服务、订阅 F301/F302,写 KEY 后由 WiFi 配网走 STATE=3 - 设备操作按钮改为按型号注册表渲染(EP832 仅展示网络配置),其余型号 保留网络配置/参数配置/服务器配置 - 鉴权相关提示文案改为连接/设备未连接语义,与无鉴权流程对齐
This commit is contained in:
parent
388599e840
commit
d1681ee08c
|
|
@ -798,16 +798,10 @@ export function createBluetoothDiscovery(options = {}) {
|
||||||
* WiFi配置专用流程:连接 -> 发现 -> 订阅 -> 按 MAC+0000 完成鉴权。
|
* WiFi配置专用流程:连接 -> 发现 -> 订阅 -> 按 MAC+0000 完成鉴权。
|
||||||
*/
|
*/
|
||||||
async function prepareWifiConfig(deviceId, options = {}) {
|
async function prepareWifiConfig(deviceId, options = {}) {
|
||||||
|
// 厂家确认:无需鉴权、无需读 MSG。
|
||||||
|
// 流程:连接 → 发现服务特征 → 订阅 F301,完成后即可进入 WiFi 配置。
|
||||||
await connectDevice(deviceId)
|
await connectDevice(deviceId)
|
||||||
|
return { mode: 'READY', 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) }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function readDeviceInfo() {
|
async function readDeviceInfo() {
|
||||||
|
|
|
||||||
|
|
@ -32,12 +32,15 @@
|
||||||
</view>
|
</view>
|
||||||
<text class="device-id">{{ device.deviceId }}</text>
|
<text class="device-id">{{ device.deviceId }}</text>
|
||||||
<view class="device-actions">
|
<view class="device-actions">
|
||||||
<view class="action-btn" :class="{ disabled: isDevicePreparing(device.deviceId) }"
|
<view
|
||||||
@click.stop="openWifiPanel(device.deviceId)">
|
v-for="action in getDeviceActions(device)"
|
||||||
{{ isDevicePreparing(device.deviceId) ? '鉴权中' : '网络配置' }}
|
:key="action.key"
|
||||||
|
class="action-btn"
|
||||||
|
:class="{ disabled: isActionBusy(action, device) }"
|
||||||
|
@click.stop="invokeAction(action, device)"
|
||||||
|
>
|
||||||
|
{{ getActionLabel(action, device) }}
|
||||||
</view>
|
</view>
|
||||||
<view class="action-btn">参数配置</view>
|
|
||||||
<view class="action-btn">服务器配置</view>
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
@ -117,6 +120,30 @@ const WIFI_STATE_MESSAGES = Object.freeze({
|
||||||
4: '设备正在连接 WiFi,请稍候'
|
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) {
|
function getErrorMessage(error) {
|
||||||
if (!error) {
|
if (!error) {
|
||||||
return '操作失败,请重试'
|
return '操作失败,请重试'
|
||||||
|
|
@ -253,6 +280,47 @@ export default {
|
||||||
return Boolean(this.devicePreparingMap[safeDeviceId])
|
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) {
|
getDeviceById(deviceId) {
|
||||||
const safeDeviceId = String(deviceId || '').trim()
|
const safeDeviceId = String(deviceId || '').trim()
|
||||||
if (!safeDeviceId) {
|
if (!safeDeviceId) {
|
||||||
|
|
@ -314,7 +382,7 @@ export default {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.activePreparingDeviceId && this.activePreparingDeviceId !== targetDeviceId) {
|
if (this.activePreparingDeviceId && this.activePreparingDeviceId !== targetDeviceId) {
|
||||||
showToast('正在为其他设备鉴权,请稍候')
|
showToast('正在为其他设备准备,请稍候')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -331,27 +399,21 @@ export default {
|
||||||
this.setDevicePreparing(targetDeviceId, true)
|
this.setDevicePreparing(targetDeviceId, true)
|
||||||
|
|
||||||
uni.showLoading({
|
uni.showLoading({
|
||||||
title: '密钥鉴权中',
|
title: '连接设备中',
|
||||||
mask: true
|
mask: true
|
||||||
})
|
})
|
||||||
|
|
||||||
let authError = null
|
let prepError = null
|
||||||
try {
|
try {
|
||||||
/**
|
// 厂家确认流程:连接 → 发现服务特征 → 订阅 F301,无需鉴权/读MSG。
|
||||||
* 先按项目固定已知密钥规则(MAC+0000)完成鉴权,成功后再允许进入 WiFi 弹层。
|
await this.discoveryController.prepareWifiConfig(targetDeviceId)
|
||||||
*/
|
|
||||||
await this.discoveryController.prepareWifiConfig(targetDeviceId, {
|
|
||||||
suffix: '0000',
|
|
||||||
timeout: 8000,
|
|
||||||
msgTimeout: 4000
|
|
||||||
})
|
|
||||||
|
|
||||||
this.wifiPrepared = true
|
this.wifiPrepared = true
|
||||||
this.wifiPanelVisible = true
|
this.wifiPanelVisible = true
|
||||||
// 确保 Vue 完成 DOM 更新后再调原生 toast,避免原生层覆盖 Vue 弹层
|
// 确保 Vue 完成 DOM 更新后再调原生 toast,避免原生层覆盖 Vue 弹层
|
||||||
await this.$nextTick()
|
await this.$nextTick()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
authError = error
|
prepError = error
|
||||||
await this.safeDisconnect()
|
await this.safeDisconnect()
|
||||||
} finally {
|
} finally {
|
||||||
this.setDevicePreparing(targetDeviceId, false)
|
this.setDevicePreparing(targetDeviceId, false)
|
||||||
|
|
@ -362,16 +424,16 @@ export default {
|
||||||
uni.hideLoading()
|
uni.hideLoading()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (authError) {
|
if (prepError) {
|
||||||
// 鉴权失败用模态弹窗,需用户主动确认,确保错误诊断信息可见可读
|
// 连接/订阅失败用模态弹窗,需用户主动确认,确保错误诊断信息可见可读
|
||||||
uni.showModal({
|
uni.showModal({
|
||||||
title: '密钥鉴权失败',
|
title: '设备连接失败',
|
||||||
content: getErrorMessage(authError),
|
content: getErrorMessage(prepError),
|
||||||
showCancel: false,
|
showCancel: false,
|
||||||
confirmText: '知道了'
|
confirmText: '知道了'
|
||||||
})
|
})
|
||||||
} else if (this.wifiPanelVisible) {
|
} else if (this.wifiPanelVisible) {
|
||||||
showToast('密钥鉴权成功,请填写WiFi信息')
|
showToast('设备已连接,请填写WiFi信息')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -463,7 +525,7 @@ export default {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.wifiPrepared) {
|
if (!this.wifiPrepared) {
|
||||||
showToast('请先完成密钥鉴权')
|
showToast('设备未连接,请重新配置')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,42 +9,6 @@ import {
|
||||||
} from '../protocols/profiles';
|
} from '../protocols/profiles';
|
||||||
import { uuidEquals } from '../utils/uuid';
|
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。
|
* 根据特征UUID将通知分发到对应协议事件:state / msg / radar / raw。
|
||||||
|
|
@ -193,22 +157,17 @@ export function createUnifiedBleController(options = {}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 连接后严格执行协议前置(厂家要求顺序):
|
* 连接后执行协议前置(厂家确认流程):
|
||||||
* 1) 发现服务和特征
|
* 1) 发现服务和特征
|
||||||
* 2) 先读取 UUID_MSG(F501) —— 必须在订阅 F301 之前
|
* 2) 订阅 UUID_STATE(F301) / UUID_RADAR(F302)
|
||||||
* 3) 订阅 UUID_STATE(F301) / UUID_RADAR(F302)
|
|
||||||
*
|
*
|
||||||
* 厂家说明:订阅 F301 前必须先读 F501,否则后续设备不回 STATE 通知,
|
* 厂家确认:无需读 MSG、无需鉴权,订阅 F301 后直接写 F401 配网即可。
|
||||||
* 表现为写 KEY/WIFI 后 F301 零回执、等待超时。
|
|
||||||
*/
|
*/
|
||||||
async function connectAndDiscover(deviceId) {
|
async function connectAndDiscover(deviceId) {
|
||||||
await ble.connect(deviceId);
|
await ble.connect(deviceId);
|
||||||
await ble.discoverServices(deviceId);
|
await ble.discoverServices(deviceId);
|
||||||
const chars = await ble.discoverCharacteristics();
|
const chars = await ble.discoverCharacteristics();
|
||||||
|
|
||||||
// 先读 UUID_MSG(F501),再订阅 F301 —— 厂家强制顺序
|
|
||||||
await readMsgOnce(ble, chars);
|
|
||||||
|
|
||||||
if (chars.state && chars.state.uuid) {
|
if (chars.state && chars.state.uuid) {
|
||||||
// 打印 STATE 特征属性,区分 notify / indicate,定位通知不投递问题
|
// 打印 STATE 特征属性,区分 notify / indicate,定位通知不投递问题
|
||||||
console.debug('[BLE][SUB]', 'STATE props', JSON.stringify(chars.state.properties || {}));
|
console.debug('[BLE][SUB]', 'STATE props', JSON.stringify(chars.state.properties || {}));
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue