fix(ble): 成功搜索到ED719设备
This commit is contained in:
parent
640772ef0c
commit
fd617947f1
|
|
@ -6,55 +6,98 @@ import {
|
|||
TARGET_SERVICE_UUID_ALT_FULL,
|
||||
TARGET_SHORT_UUID
|
||||
} from '@/constants/bluetooth'
|
||||
import { DEVICE_PROFILE, createUnifiedBleController } from '@/utils/ble'
|
||||
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
|
||||
const DISCOVERY_POLL_INTERVAL_MS = 1200
|
||||
const TARGET_NAME_KEYWORDS = Object.freeze(['ED713', 'ED719'])
|
||||
|
||||
function promisifyUniApi(apiName, params = {}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
uni[apiName]({
|
||||
...params,
|
||||
success: (res) => resolve(res),
|
||||
fail: (err) => reject(err)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 蓝牙设备搜索逻辑封装。
|
||||
* 基于 utils/ble 统一蓝牙控制器实现。
|
||||
* 统一蓝牙接入层(ED713/ED719):
|
||||
* 1) 搜索(过滤、去重、可停止)
|
||||
* 2) 连接 + 服务/特征发现 + 订阅
|
||||
* 3) 鉴权、读取设备信息、WiFi 配置、雷达控制
|
||||
*/
|
||||
export function createBluetoothDiscovery(options = {}) {
|
||||
const {
|
||||
onSearchingChange = () => {},
|
||||
onDeviceMapChange = () => {},
|
||||
onDeviceListChange = () => {}
|
||||
onDeviceListChange = () => {},
|
||||
onConnectedChange = () => {},
|
||||
onProfileChange = () => {},
|
||||
onStageChange = () => {},
|
||||
onProtocolState = () => {},
|
||||
onProtocolMsg = () => {},
|
||||
onRadarData = () => {},
|
||||
onDeviceInfoChange = () => {},
|
||||
onAuthChange = () => {},
|
||||
onError = () => {}
|
||||
} = options
|
||||
|
||||
const controller = createUnifiedBleController({
|
||||
profileId: DEVICE_PROFILE.UNKNOWN
|
||||
})
|
||||
|
||||
let initialized = false
|
||||
let isSearching = false
|
||||
let isConnected = false
|
||||
let deviceMap = {}
|
||||
|
||||
let initialized = false
|
||||
let filterFallbackTimer = null
|
||||
|
||||
let offDeviceFound = null
|
||||
let offAdapterState = null
|
||||
let currentDeviceId = ''
|
||||
let currentProfileId = DEVICE_PROFILE.UNKNOWN
|
||||
let lastDeviceInfo = null
|
||||
let lastAuthResult = null
|
||||
|
||||
let searchSessionId = 0
|
||||
let acceptingDeviceEvents = false
|
||||
let filterFallbackTimer = null
|
||||
let discoveryPollTimer = null
|
||||
|
||||
let offDeviceFound = null
|
||||
let offAdapterState = null
|
||||
let offConnectionChange = null
|
||||
let offStateChange = null
|
||||
let offProtocolState = null
|
||||
let offProtocolMsg = null
|
||||
let offProtocolRadar = null
|
||||
let offError = null
|
||||
|
||||
function setSearching(value) {
|
||||
isSearching = Boolean(value)
|
||||
onSearchingChange(isSearching)
|
||||
}
|
||||
|
||||
function emitDeviceList() {
|
||||
const list = Object.values(deviceMap).sort((a, b) => {
|
||||
if (a.hasTargetService !== b.hasTargetService) {
|
||||
return a.hasTargetService ? -1 : 1
|
||||
function setConnected(value) {
|
||||
isConnected = Boolean(value)
|
||||
onConnectedChange(isConnected)
|
||||
}
|
||||
|
||||
const aSignal = typeof a.RSSI === 'number' ? a.RSSI : -999
|
||||
const bSignal = typeof b.RSSI === 'number' ? b.RSSI : -999
|
||||
return bSignal - aSignal
|
||||
})
|
||||
function getSortedDeviceList(mapValue = deviceMap) {
|
||||
return Object.values(mapValue).sort((left, right) => {
|
||||
if (left.hasTargetService !== right.hasTargetService) {
|
||||
return left.hasTargetService ? -1 : 1
|
||||
}
|
||||
|
||||
onDeviceListChange(list)
|
||||
const leftSignal = typeof left.RSSI === 'number' ? left.RSSI : -999
|
||||
const rightSignal = typeof right.RSSI === 'number' ? right.RSSI : -999
|
||||
return rightSignal - leftSignal
|
||||
})
|
||||
}
|
||||
|
||||
function emitDeviceList() {
|
||||
onDeviceListChange(getSortedDeviceList())
|
||||
}
|
||||
|
||||
function setDeviceMap(nextMap) {
|
||||
|
|
@ -71,29 +114,117 @@ export function createBluetoothDiscovery(options = {}) {
|
|||
return String(uuid || '').replace(/[^0-9a-fA-F]/g, '').toUpperCase()
|
||||
}
|
||||
|
||||
function inferProfileByName(device) {
|
||||
const text = `${device?.name || ''} ${device?.localName || ''}`.toUpperCase()
|
||||
|
||||
if (text.includes('ED719')) {
|
||||
return DEVICE_PROFILE.ED719
|
||||
}
|
||||
|
||||
if (text.includes('ED713')) {
|
||||
return DEVICE_PROFILE.ED713
|
||||
}
|
||||
|
||||
return DEVICE_PROFILE.UNKNOWN
|
||||
}
|
||||
|
||||
/**
|
||||
* 从广播包中解析 16-bit service UUID(AD Type 0x02/0x03)。
|
||||
*/
|
||||
function extractShortUuidsFromAdvertisData(advertisData) {
|
||||
if (!advertisData) {
|
||||
return []
|
||||
}
|
||||
|
||||
const bytes = advertisData instanceof Uint8Array ? advertisData : new Uint8Array(advertisData)
|
||||
const shortUuids = []
|
||||
|
||||
let offset = 0
|
||||
while (offset < bytes.length) {
|
||||
const len = bytes[offset]
|
||||
if (!len) {
|
||||
break
|
||||
}
|
||||
|
||||
const typeIndex = offset + 1
|
||||
const dataStart = offset + 2
|
||||
const dataEnd = offset + 1 + len
|
||||
|
||||
if (dataEnd > bytes.length) {
|
||||
break
|
||||
}
|
||||
|
||||
const type = bytes[typeIndex]
|
||||
if (type === 0x02 || type === 0x03) {
|
||||
for (let index = dataStart; index + 1 < dataEnd; index += 2) {
|
||||
const value = ((bytes[index + 1] << 8) | bytes[index]).toString(16).toUpperCase().padStart(4, '0')
|
||||
shortUuids.push(value)
|
||||
}
|
||||
}
|
||||
|
||||
offset += len + 1
|
||||
}
|
||||
|
||||
return shortUuids
|
||||
}
|
||||
|
||||
function hasTargetService(device) {
|
||||
if (!Array.isArray(device.advertisServiceUUIDs)) {
|
||||
const serviceList = Array.isArray(device?.advertisServiceUUIDs) ? device.advertisServiceUUIDs : []
|
||||
const adShortUuids = extractShortUuidsFromAdvertisData(device?.advertisData)
|
||||
|
||||
if (!serviceList.length && !adShortUuids.length) {
|
||||
return false
|
||||
}
|
||||
|
||||
const serviceSet = new Set([
|
||||
normalizeUuid(TARGET_SERVICE_UUID_FULL),
|
||||
normalizeUuid(TARGET_SERVICE_UUID_ALT_FULL)
|
||||
])
|
||||
|
||||
return device.advertisServiceUUIDs.some((uuid) => {
|
||||
const value = normalizeUuid(uuid)
|
||||
|
||||
if (value.endsWith(TARGET_SHORT_UUID)) {
|
||||
return true
|
||||
return uuidInList(TARGET_SERVICE_UUID_FULL, serviceList)
|
||||
|| uuidInList(TARGET_SERVICE_UUID_ALT_FULL, serviceList)
|
||||
|| serviceList.some((item) => shortUuid(item) === TARGET_SHORT_UUID)
|
||||
|| adShortUuids.includes(TARGET_SHORT_UUID)
|
||||
|| adShortUuids.includes('F400')
|
||||
}
|
||||
|
||||
return serviceSet.has(value)
|
||||
})
|
||||
function bufferToPrintableText(buffer) {
|
||||
if (!buffer) {
|
||||
return ''
|
||||
}
|
||||
|
||||
const bytes = buffer instanceof Uint8Array ? buffer : new Uint8Array(buffer)
|
||||
return Array.from(bytes)
|
||||
.map((value) => (value >= 32 && value <= 126 ? String.fromCharCode(value) : ' '))
|
||||
.join(' ')
|
||||
.toUpperCase()
|
||||
}
|
||||
|
||||
function hasTargetKeywordText(text) {
|
||||
return TARGET_NAME_KEYWORDS.some((keyword) => text.includes(keyword))
|
||||
}
|
||||
|
||||
function hasTargetName(device) {
|
||||
const text = `${String(device?.name || '')} ${String(device?.localName || '')}`.toUpperCase()
|
||||
return hasTargetKeywordText(text)
|
||||
}
|
||||
|
||||
function hasTargetKeywordInPayload(device) {
|
||||
const adText = bufferToPrintableText(device?.advertisData)
|
||||
const manufacturerText = bufferToPrintableText(device?.manufacturerData)
|
||||
return hasTargetKeywordText(`${adText} ${manufacturerText}`)
|
||||
}
|
||||
|
||||
function matchTargetDevice(device) {
|
||||
const byService = hasTargetService(device)
|
||||
const byName = hasTargetName(device)
|
||||
const byPayload = hasTargetKeywordInPayload(device)
|
||||
|
||||
return {
|
||||
isTarget: byService || byName || byPayload,
|
||||
byService,
|
||||
byName,
|
||||
byPayload
|
||||
}
|
||||
}
|
||||
|
||||
function hasTargetDeviceInMap() {
|
||||
return Object.values(deviceMap).some((item) => item.hasTargetService)
|
||||
return Object.values(deviceMap).some((item) => item.isTargetDevice)
|
||||
}
|
||||
|
||||
function getDeviceStableSignature(device = {}) {
|
||||
|
|
@ -102,7 +233,9 @@ export function createBluetoothDiscovery(options = {}) {
|
|||
device.RSSI ?? '',
|
||||
device.name || '',
|
||||
device.localName || '',
|
||||
Array.isArray(device.advertisServiceUUIDs) ? device.advertisServiceUUIDs.map((item) => normalizeUuid(item)).sort().join('|') : '',
|
||||
Array.isArray(device.advertisServiceUUIDs)
|
||||
? device.advertisServiceUUIDs.map((item) => normalizeUuid(item)).sort().join('|')
|
||||
: '',
|
||||
device.advertisData ? JSON.stringify(Array.from(new Uint8Array(device.advertisData))) : ''
|
||||
].join('#')
|
||||
}
|
||||
|
|
@ -116,6 +249,15 @@ export function createBluetoothDiscovery(options = {}) {
|
|||
filterFallbackTimer = null
|
||||
}
|
||||
|
||||
function clearDiscoveryPollTimer() {
|
||||
if (!discoveryPollTimer) {
|
||||
return
|
||||
}
|
||||
|
||||
clearTimeout(discoveryPollTimer)
|
||||
discoveryPollTimer = null
|
||||
}
|
||||
|
||||
function delay(ms) {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(resolve, ms)
|
||||
|
|
@ -144,9 +286,7 @@ export function createBluetoothDiscovery(options = {}) {
|
|||
})
|
||||
})
|
||||
|
||||
const authSetting = settings.authSetting || {}
|
||||
const locationSetting = authSetting['scope.userLocation']
|
||||
|
||||
const locationSetting = settings?.authSetting?.['scope.userLocation']
|
||||
if (locationSetting === true) {
|
||||
return true
|
||||
}
|
||||
|
|
@ -171,8 +311,8 @@ export function createBluetoothDiscovery(options = {}) {
|
|||
return
|
||||
}
|
||||
|
||||
const targetMatched = hasTargetService(rawDevice)
|
||||
if (!targetMatched) {
|
||||
const matched = matchTargetDevice(rawDevice)
|
||||
if (!matched.isTarget) {
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -181,7 +321,11 @@ export function createBluetoothDiscovery(options = {}) {
|
|||
...existing,
|
||||
...rawDevice,
|
||||
showName: rawDevice.name || rawDevice.localName || existing.showName || '未命名设备',
|
||||
hasTargetService: true
|
||||
isTargetDevice: true,
|
||||
hasTargetService: matched.byService,
|
||||
matchedByName: matched.byName,
|
||||
matchedByPayload: matched.byPayload,
|
||||
profileHint: inferProfileByName(rawDevice)
|
||||
}
|
||||
|
||||
const prevSignature = getDeviceStableSignature(existing)
|
||||
|
|
@ -197,13 +341,30 @@ export function createBluetoothDiscovery(options = {}) {
|
|||
})
|
||||
}
|
||||
|
||||
async function ensureInitialized() {
|
||||
if (initialized) {
|
||||
function getBluetoothErrorText(error) {
|
||||
const errCode = error?.errCode ?? error?.code
|
||||
const text = BLUETOOTH_ERROR_MESSAGES[errCode] || DEFAULT_BLUETOOTH_ERROR_MESSAGE
|
||||
|
||||
if (errCode === undefined || errCode === null) {
|
||||
return text
|
||||
}
|
||||
|
||||
return `${text}(${errCode})`
|
||||
}
|
||||
|
||||
function bindControllerEvents() {
|
||||
if (offStateChange) {
|
||||
return
|
||||
}
|
||||
|
||||
await controller.init()
|
||||
controller.ble.watchDeviceFound()
|
||||
offStateChange = controller.ble.on('state:change', (state) => {
|
||||
onStageChange(state.stage)
|
||||
|
||||
if (state.profileId && state.profileId !== currentProfileId) {
|
||||
currentProfileId = state.profileId
|
||||
onProfileChange(currentProfileId)
|
||||
}
|
||||
})
|
||||
|
||||
offDeviceFound = controller.ble.on('device:found', (devices) => {
|
||||
if (!acceptingDeviceEvents) {
|
||||
|
|
@ -221,23 +382,113 @@ export function createBluetoothDiscovery(options = {}) {
|
|||
})
|
||||
|
||||
offAdapterState = controller.ble.on('adapter:state', (state) => {
|
||||
if (!state || state.available !== false) {
|
||||
if (state?.available !== false) {
|
||||
return
|
||||
}
|
||||
|
||||
acceptingDeviceEvents = false
|
||||
setSearching(false)
|
||||
setConnected(false)
|
||||
showToast('蓝牙已关闭,请先开启手机蓝牙')
|
||||
})
|
||||
|
||||
offConnectionChange = controller.ble.on('connection:change', (payload) => {
|
||||
setConnected(Boolean(payload?.connected))
|
||||
|
||||
if (!payload?.connected) {
|
||||
currentDeviceId = ''
|
||||
}
|
||||
})
|
||||
|
||||
offProtocolState = controller.ble.on('protocol:state', (payload) => {
|
||||
onProtocolState(payload)
|
||||
})
|
||||
|
||||
offProtocolMsg = controller.ble.on('protocol:msg', (payload) => {
|
||||
onProtocolMsg(payload)
|
||||
if (payload?.deviceInfo) {
|
||||
lastDeviceInfo = payload.deviceInfo
|
||||
onDeviceInfoChange(lastDeviceInfo)
|
||||
}
|
||||
})
|
||||
|
||||
offProtocolRadar = controller.ble.on('protocol:radar', (payload) => {
|
||||
const parsed = controller.radar.parseNotifyPayload(payload.value)
|
||||
onRadarData({
|
||||
...payload,
|
||||
parsed
|
||||
})
|
||||
})
|
||||
|
||||
offError = controller.ble.on('error', (error) => {
|
||||
onError(error)
|
||||
})
|
||||
}
|
||||
|
||||
function unbindControllerEvents() {
|
||||
if (offDeviceFound) {
|
||||
offDeviceFound()
|
||||
offDeviceFound = null
|
||||
}
|
||||
|
||||
if (offAdapterState) {
|
||||
offAdapterState()
|
||||
offAdapterState = null
|
||||
}
|
||||
|
||||
if (offConnectionChange) {
|
||||
offConnectionChange()
|
||||
offConnectionChange = null
|
||||
}
|
||||
|
||||
if (offStateChange) {
|
||||
offStateChange()
|
||||
offStateChange = null
|
||||
}
|
||||
|
||||
if (offProtocolState) {
|
||||
offProtocolState()
|
||||
offProtocolState = null
|
||||
}
|
||||
|
||||
if (offProtocolMsg) {
|
||||
offProtocolMsg()
|
||||
offProtocolMsg = null
|
||||
}
|
||||
|
||||
if (offProtocolRadar) {
|
||||
offProtocolRadar()
|
||||
offProtocolRadar = null
|
||||
}
|
||||
|
||||
if (offError) {
|
||||
offError()
|
||||
offError = null
|
||||
}
|
||||
}
|
||||
|
||||
async function ensureInitialized() {
|
||||
if (initialized) {
|
||||
return
|
||||
}
|
||||
|
||||
await controller.init()
|
||||
controller.ble.watchDeviceFound()
|
||||
|
||||
bindControllerEvents()
|
||||
initialized = true
|
||||
|
||||
currentProfileId = controller.ble.getProfile().id
|
||||
onProfileChange(currentProfileId)
|
||||
}
|
||||
|
||||
async function startDiscoveryWithServices(services = []) {
|
||||
await controller.ble.startDiscovery({
|
||||
services,
|
||||
allowDuplicatesKey: true,
|
||||
interval: 0
|
||||
interval: 0,
|
||||
// 兼容低版本客户端:不强制传 powerLevel。
|
||||
powerLevel: null
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -248,6 +499,45 @@ export function createBluetoothDiscovery(options = {}) {
|
|||
}
|
||||
}
|
||||
|
||||
async function pullDiscoveredDevicesOnce(sessionId) {
|
||||
if (!isSearching || !acceptingDeviceEvents || sessionId !== searchSessionId) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await promisifyUniApi('getBluetoothDevices')
|
||||
const list = Array.isArray(result?.devices) ? result.devices : []
|
||||
list.forEach((device) => {
|
||||
if (!isSearching || !acceptingDeviceEvents || sessionId !== searchSessionId) {
|
||||
return
|
||||
}
|
||||
|
||||
upsertDevice(device)
|
||||
})
|
||||
} catch (error) {
|
||||
}
|
||||
}
|
||||
|
||||
function startDiscoveryPolling(sessionId) {
|
||||
clearDiscoveryPollTimer()
|
||||
|
||||
const run = async () => {
|
||||
if (!isSearching || !acceptingDeviceEvents || sessionId !== searchSessionId) {
|
||||
return
|
||||
}
|
||||
|
||||
await pullDiscoveredDevicesOnce(sessionId)
|
||||
|
||||
if (!isSearching || !acceptingDeviceEvents || sessionId !== searchSessionId) {
|
||||
return
|
||||
}
|
||||
|
||||
discoveryPollTimer = setTimeout(run, DISCOVERY_POLL_INTERVAL_MS)
|
||||
}
|
||||
|
||||
discoveryPollTimer = setTimeout(run, 300)
|
||||
}
|
||||
|
||||
async function fallbackToUnfilteredDiscoveryIfNeeded(sessionId) {
|
||||
clearFallbackTimer()
|
||||
|
||||
|
|
@ -258,7 +548,7 @@ export function createBluetoothDiscovery(options = {}) {
|
|||
|
||||
try {
|
||||
await stopDiscoverySilently()
|
||||
await delay(120)
|
||||
await delay(RESTART_SCAN_DELAY_MS)
|
||||
|
||||
if (!isSearching || !acceptingDeviceEvents || sessionId !== searchSessionId) {
|
||||
return
|
||||
|
|
@ -270,23 +560,18 @@ export function createBluetoothDiscovery(options = {}) {
|
|||
}, FILTER_SCAN_TIMEOUT_MS)
|
||||
}
|
||||
|
||||
function getBluetoothErrorText(error) {
|
||||
const errCode = (error && (error.errCode ?? error.code))
|
||||
const text = BLUETOOTH_ERROR_MESSAGES[errCode] || DEFAULT_BLUETOOTH_ERROR_MESSAGE
|
||||
|
||||
if (errCode === undefined || errCode === null) {
|
||||
return text
|
||||
}
|
||||
|
||||
return `${text}(${errCode})`
|
||||
}
|
||||
|
||||
/**
|
||||
* 设备搜索:
|
||||
* 优先按 0x00F4/0xF400 过滤;若无结果自动切到兜底扫描并轮询 getBluetoothDevices。
|
||||
*/
|
||||
async function startSearch() {
|
||||
if (isSearching) {
|
||||
return
|
||||
}
|
||||
|
||||
clearFallbackTimer()
|
||||
clearDiscoveryPollTimer()
|
||||
|
||||
searchSessionId += 1
|
||||
const sessionId = searchSessionId
|
||||
|
||||
|
|
@ -297,7 +582,7 @@ export function createBluetoothDiscovery(options = {}) {
|
|||
await ensureInitialized()
|
||||
|
||||
const adapterState = await controller.ble.getAdapterState()
|
||||
if (adapterState && adapterState.available === false) {
|
||||
if (adapterState?.available === false) {
|
||||
throw { errCode: 10001 }
|
||||
}
|
||||
|
||||
|
|
@ -316,6 +601,7 @@ export function createBluetoothDiscovery(options = {}) {
|
|||
}
|
||||
|
||||
setSearching(true)
|
||||
startDiscoveryPolling(sessionId)
|
||||
await fallbackToUnfilteredDiscoveryIfNeeded(sessionId)
|
||||
} catch (error) {
|
||||
acceptingDeviceEvents = false
|
||||
|
|
@ -325,54 +611,214 @@ export function createBluetoothDiscovery(options = {}) {
|
|||
}
|
||||
}
|
||||
|
||||
function stopSearch(showStopToast = true) {
|
||||
return new Promise((resolve) => {
|
||||
async function stopSearch(showStopToast = true) {
|
||||
clearFallbackTimer()
|
||||
clearDiscoveryPollTimer()
|
||||
|
||||
searchSessionId += 1
|
||||
acceptingDeviceEvents = false
|
||||
|
||||
controller.ble.stopDiscovery()
|
||||
.catch(() => {})
|
||||
.finally(() => {
|
||||
await stopDiscoverySilently()
|
||||
|
||||
setSearching(false)
|
||||
if (showStopToast) {
|
||||
showToast('已停止搜索')
|
||||
}
|
||||
resolve()
|
||||
})
|
||||
}
|
||||
|
||||
function pickPreferredDeviceId(deviceId) {
|
||||
if (deviceId) {
|
||||
return deviceId
|
||||
}
|
||||
|
||||
const list = getSortedDeviceList()
|
||||
return list[0]?.deviceId || ''
|
||||
}
|
||||
|
||||
function applyProfileHintByDevice(deviceId) {
|
||||
if (!deviceId) {
|
||||
return
|
||||
}
|
||||
|
||||
const hint = deviceMap[deviceId]?.profileHint
|
||||
if (!hint || hint === DEVICE_PROFILE.UNKNOWN) {
|
||||
return
|
||||
}
|
||||
|
||||
const currentProfile = controller.ble.getProfile().id
|
||||
if (currentProfile === DEVICE_PROFILE.UNKNOWN) {
|
||||
controller.ble.setProfile(hint)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 仅连接与发现阶段:连接 -> 发现服务0x00F4 -> 发现特征 -> 订阅 F301/F302。
|
||||
*/
|
||||
async function connectDevice(deviceId) {
|
||||
await ensureInitialized()
|
||||
|
||||
await stopSearch(false)
|
||||
|
||||
const targetDeviceId = pickPreferredDeviceId(deviceId)
|
||||
if (!targetDeviceId) {
|
||||
throw new Error('未找到可连接设备')
|
||||
}
|
||||
|
||||
applyProfileHintByDevice(targetDeviceId)
|
||||
|
||||
const characteristics = await controller.connectAndDiscover(targetDeviceId)
|
||||
currentDeviceId = targetDeviceId
|
||||
setConnected(true)
|
||||
|
||||
return {
|
||||
deviceId: targetDeviceId,
|
||||
characteristics,
|
||||
profile: controller.ble.getProfile(),
|
||||
state: controller.ble.getState()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 鉴权流程:严格遵循协议绑定逻辑(读取 UUID_MSG 的 bindStatus 后写 KEY)。
|
||||
*/
|
||||
async function authorizeDevice(key, options = {}) {
|
||||
if (!currentDeviceId) {
|
||||
throw new Error('请先连接设备后再鉴权')
|
||||
}
|
||||
|
||||
const result = await controller.auth.ensureAuthorized(key, options)
|
||||
lastAuthResult = result
|
||||
onAuthChange(result)
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* 标准连接流程:连接 -> 发现 -> 订阅 -> 鉴权。
|
||||
*/
|
||||
async function standardConnectFlow(params = {}) {
|
||||
await ensureInitialized()
|
||||
|
||||
await stopSearch(false)
|
||||
|
||||
const targetDeviceId = pickPreferredDeviceId(params.deviceId)
|
||||
if (!targetDeviceId) {
|
||||
throw new Error('未找到可连接设备')
|
||||
}
|
||||
|
||||
applyProfileHintByDevice(targetDeviceId)
|
||||
|
||||
const result = await controller.standardConnectFlow({
|
||||
...params,
|
||||
deviceId: targetDeviceId
|
||||
})
|
||||
|
||||
currentDeviceId = result.deviceId
|
||||
setConnected(true)
|
||||
|
||||
lastAuthResult = result.authResult
|
||||
onAuthChange(result.authResult)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
async function readDeviceInfo() {
|
||||
const info = await controller.auth.readDeviceInfo()
|
||||
lastDeviceInfo = info
|
||||
onDeviceInfoChange(info)
|
||||
return info
|
||||
}
|
||||
|
||||
async function configureWifi(ssid, password, options = {}) {
|
||||
return controller.wifi.configureWifi(ssid, password, options)
|
||||
}
|
||||
|
||||
async function startRadar() {
|
||||
return controller.radar.startRadarStream()
|
||||
}
|
||||
|
||||
async function stopRadar() {
|
||||
return controller.radar.stopRadarStream()
|
||||
}
|
||||
|
||||
async function setNarrowMode(enabled) {
|
||||
return controller.radar.setNarrowMode(Boolean(enabled))
|
||||
}
|
||||
|
||||
async function writeFallParams(rawParams) {
|
||||
return controller.radar.writeFallParams(rawParams)
|
||||
}
|
||||
|
||||
async function sendRadarCommand(commandByte) {
|
||||
return controller.radar.sendCommand(commandByte)
|
||||
}
|
||||
|
||||
async function disconnectDevice() {
|
||||
await controller.ble.disconnect(currentDeviceId)
|
||||
currentDeviceId = ''
|
||||
setConnected(false)
|
||||
}
|
||||
|
||||
function getState() {
|
||||
const bleState = controller.ble.getState()
|
||||
|
||||
return {
|
||||
isSearching,
|
||||
isConnected,
|
||||
currentDeviceId,
|
||||
currentProfileId: controller.ble.getProfile().id,
|
||||
deviceMap: { ...deviceMap },
|
||||
deviceList: getSortedDeviceList(),
|
||||
lastDeviceInfo,
|
||||
lastAuthResult,
|
||||
bleState
|
||||
}
|
||||
}
|
||||
|
||||
function cleanupDiscovery() {
|
||||
clearFallbackTimer()
|
||||
clearDiscoveryPollTimer()
|
||||
|
||||
searchSessionId += 1
|
||||
acceptingDeviceEvents = false
|
||||
setSearching(false)
|
||||
setConnected(false)
|
||||
|
||||
if (offDeviceFound) {
|
||||
offDeviceFound()
|
||||
offDeviceFound = null
|
||||
}
|
||||
currentDeviceId = ''
|
||||
lastAuthResult = null
|
||||
lastDeviceInfo = null
|
||||
|
||||
if (offAdapterState) {
|
||||
offAdapterState()
|
||||
offAdapterState = null
|
||||
}
|
||||
unbindControllerEvents()
|
||||
|
||||
if (!initialized) {
|
||||
return
|
||||
}
|
||||
|
||||
initialized = false
|
||||
|
||||
controller.cleanup().catch(() => {})
|
||||
}
|
||||
|
||||
return {
|
||||
// 搜索能力(兼容原页面入口)。
|
||||
startSearch,
|
||||
stopSearch,
|
||||
cleanupDiscovery
|
||||
cleanupDiscovery,
|
||||
|
||||
// 连接与协议流程能力。
|
||||
connectDevice,
|
||||
authorizeDevice,
|
||||
standardConnectFlow,
|
||||
disconnectDevice,
|
||||
|
||||
// 协议业务能力。
|
||||
readDeviceInfo,
|
||||
configureWifi,
|
||||
startRadar,
|
||||
stopRadar,
|
||||
setNarrowMode,
|
||||
writeFallParams,
|
||||
sendRadarCommand,
|
||||
|
||||
// 调试/状态读取。
|
||||
getState
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue