fix(ble): 恢复过滤扫描优先+4s兜底全扫,并加搜索排障日志
真机验证"直接全频段扫描"方案后设备反而搜不出(修改前过滤扫描可搜到), 说明该机型下无过滤扫描路径存在问题(可能为机型对无 services 参数的 startBluetoothDevicesDiscovery 兼容差异)。回退为迁移原版策略: - 恢复 DISCOVERY_FILTER_SERVICE_UUIDS 常量与 fallbackToUnfilteredDiscoveryIfNeeded 兜底机制:先按 0xFFF4/0x00F4/0xF400 过滤扫描,4s 无结果自动切全扫 - upsertDevice 改为返回布尔值,event/poll 两条通道均打印排障日志 ([BLE][SCAN] 原生采集数与命中数、[BLE][FOUND] 事件命中数、 启动/兜底切换日志),真机 vConsole 可直接观察各阶段状态
This commit is contained in:
parent
5e5cd2bf47
commit
2238e965b9
|
|
@ -1,6 +1,7 @@
|
||||||
import {
|
import {
|
||||||
BLUETOOTH_ERROR_MESSAGES,
|
BLUETOOTH_ERROR_MESSAGES,
|
||||||
DEFAULT_BLUETOOTH_ERROR_MESSAGE,
|
DEFAULT_BLUETOOTH_ERROR_MESSAGE,
|
||||||
|
DISCOVERY_FILTER_SERVICE_UUIDS,
|
||||||
TARGET_SERVICE_UUID_FULL,
|
TARGET_SERVICE_UUID_FULL,
|
||||||
TARGET_SERVICE_UUID_ALT_FULL,
|
TARGET_SERVICE_UUID_ALT_FULL,
|
||||||
TARGET_SERVICE_UUID_COMPAT_FULL,
|
TARGET_SERVICE_UUID_COMPAT_FULL,
|
||||||
|
|
@ -13,6 +14,8 @@ import Taro from '@tarojs/taro'
|
||||||
const RESTART_SCAN_DELAY_MS = 120
|
const RESTART_SCAN_DELAY_MS = 120
|
||||||
const DISCOVERY_POLL_INTERVAL_MS = 1200
|
const DISCOVERY_POLL_INTERVAL_MS = 1200
|
||||||
const SCAN_DURATION_MS = 30000
|
const SCAN_DURATION_MS = 30000
|
||||||
|
// 过滤扫描无结果多久后切到无过滤全扫
|
||||||
|
const FILTER_SCAN_TIMEOUT_MS = 4000
|
||||||
// 设备列表 UI 刷新最小间隔:搜索中 allowDuplicatesKey=true,目标设备每秒广播
|
// 设备列表 UI 刷新最小间隔:搜索中 allowDuplicatesKey=true,目标设备每秒广播
|
||||||
// 数次,不节流会以每秒数十次 setState 打满小程序渲染线程,点击被排队卡死
|
// 数次,不节流会以每秒数十次 setState 打满小程序渲染线程,点击被排队卡死
|
||||||
const DEVICE_LIST_EMIT_INTERVAL_MS = 300
|
const DEVICE_LIST_EMIT_INTERVAL_MS = 300
|
||||||
|
|
@ -81,6 +84,7 @@ export function createBluetoothDiscovery(options = {}) {
|
||||||
let discoveryPollTimer = null
|
let discoveryPollTimer = null
|
||||||
let autoStopTimer = null
|
let autoStopTimer = null
|
||||||
let deviceListEmitTimer = null
|
let deviceListEmitTimer = null
|
||||||
|
let filterFallbackTimer = null
|
||||||
|
|
||||||
let offDeviceFound = null
|
let offDeviceFound = null
|
||||||
let offAdapterState = null
|
let offAdapterState = null
|
||||||
|
|
@ -282,6 +286,19 @@ export function createBluetoothDiscovery(options = {}) {
|
||||||
].join('#')
|
].join('#')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function hasTargetDeviceInMap() {
|
||||||
|
return Object.values(deviceMap).some((item) => item.isTargetDevice)
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearFallbackTimer() {
|
||||||
|
if (!filterFallbackTimer) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
clearTimeout(filterFallbackTimer)
|
||||||
|
filterFallbackTimer = null
|
||||||
|
}
|
||||||
|
|
||||||
function clearDiscoveryPollTimer() {
|
function clearDiscoveryPollTimer() {
|
||||||
if (!discoveryPollTimer) {
|
if (!discoveryPollTimer) {
|
||||||
return
|
return
|
||||||
|
|
@ -350,12 +367,12 @@ export function createBluetoothDiscovery(options = {}) {
|
||||||
|
|
||||||
function upsertDevice(rawDevice) {
|
function upsertDevice(rawDevice) {
|
||||||
if (!rawDevice || !rawDevice.deviceId) {
|
if (!rawDevice || !rawDevice.deviceId) {
|
||||||
return
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
const matched = matchTargetDevice(rawDevice)
|
const matched = matchTargetDevice(rawDevice)
|
||||||
if (!matched.isTarget) {
|
if (!matched.isTarget) {
|
||||||
return
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
const existing = deviceMap[rawDevice.deviceId] || {}
|
const existing = deviceMap[rawDevice.deviceId] || {}
|
||||||
|
|
@ -374,13 +391,14 @@ export function createBluetoothDiscovery(options = {}) {
|
||||||
const nextSignature = getDeviceStableSignature(merged)
|
const nextSignature = getDeviceStableSignature(merged)
|
||||||
|
|
||||||
if (prevSignature === nextSignature) {
|
if (prevSignature === nextSignature) {
|
||||||
return
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
setDeviceMap({
|
setDeviceMap({
|
||||||
...deviceMap,
|
...deviceMap,
|
||||||
[rawDevice.deviceId]: merged
|
[rawDevice.deviceId]: merged
|
||||||
})
|
})
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
function getBluetoothErrorText(error) {
|
function getBluetoothErrorText(error) {
|
||||||
|
|
@ -415,13 +433,19 @@ export function createBluetoothDiscovery(options = {}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentSession = searchSessionId
|
const currentSession = searchSessionId
|
||||||
|
let hitCount = 0
|
||||||
devices.forEach((device) => {
|
devices.forEach((device) => {
|
||||||
if (!acceptingDeviceEvents || currentSession !== searchSessionId) {
|
if (!acceptingDeviceEvents || currentSession !== searchSessionId) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
upsertDevice(device)
|
if (upsertDevice(device)) {
|
||||||
|
hitCount += 1
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
if (hitCount) {
|
||||||
|
console.debug('[BLE][FOUND]', 'event devices=', devices.length, 'newHits=', hitCount)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
offAdapterState = controller.ble.on('adapter:state', (state) => {
|
offAdapterState = controller.ble.on('adapter:state', (state) => {
|
||||||
|
|
@ -562,13 +586,18 @@ export function createBluetoothDiscovery(options = {}) {
|
||||||
try {
|
try {
|
||||||
const result = await promisifyTaroApi('getBluetoothDevices')
|
const result = await promisifyTaroApi('getBluetoothDevices')
|
||||||
const list = Array.isArray(result?.devices) ? result.devices : []
|
const list = Array.isArray(result?.devices) ? result.devices : []
|
||||||
|
// 排障日志:原生层到底采到了多少设备、命中了多少目标(真机 vConsole 可见)
|
||||||
|
let hitCount = 0
|
||||||
list.forEach((device) => {
|
list.forEach((device) => {
|
||||||
if (!isSearching || !acceptingDeviceEvents || sessionId !== searchSessionId) {
|
if (!isSearching || !acceptingDeviceEvents || sessionId !== searchSessionId) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
upsertDevice(device)
|
if (upsertDevice(device)) {
|
||||||
|
hitCount += 1
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
console.debug('[BLE][SCAN]', 'native devices=', list.length, 'newHits=', hitCount)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -593,10 +622,36 @@ export function createBluetoothDiscovery(options = {}) {
|
||||||
discoveryPollTimer = setTimeout(run, 300)
|
discoveryPollTimer = setTimeout(run, 300)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 过滤扫描超过 4s 仍无目标设备:停扫 → 等待 → 切无过滤全频段扫描。
|
||||||
|
// 微信 services 过滤只按 128 位完整 UUID 匹配广播包,16 位短 UUID 设备
|
||||||
|
// 会被过滤掉,必须靠兜底全扫 + JS 层 matchTargetDevice 兜住
|
||||||
|
async function fallbackToUnfilteredDiscoveryIfNeeded(sessionId) {
|
||||||
|
clearFallbackTimer()
|
||||||
|
|
||||||
|
filterFallbackTimer = setTimeout(async () => {
|
||||||
|
if (!isSearching || !acceptingDeviceEvents || sessionId !== searchSessionId || hasTargetDeviceInMap()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
console.debug('[BLE][SCAN]', 'filtered scan found nothing, fallback to unfiltered')
|
||||||
|
await stopDiscoverySilently()
|
||||||
|
await delay(RESTART_SCAN_DELAY_MS)
|
||||||
|
|
||||||
|
if (!isSearching || !acceptingDeviceEvents || sessionId !== searchSessionId) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
await startDiscoveryWithServices([])
|
||||||
|
} catch (error) {
|
||||||
|
}
|
||||||
|
}, FILTER_SCAN_TIMEOUT_MS)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 设备搜索:先无过滤全频段扫描(onBluetoothDeviceFound 事件驱动 + getBluetoothDevices 轮询双通道),
|
* 设备搜索:
|
||||||
* 命中关键词/服务 UUID 即入列。微信的 services 过滤只匹配 128 位完整 UUID,
|
* 优先按 0xFFF4/0x00F4/0xF400 过滤扫描(快、列表干净);
|
||||||
* 对 16 位广播 UUID(0x00F4 等)的设备经常过滤过头导致搜不到,故不再走过滤扫描。
|
* 4s 无结果自动切到无过滤全扫(兜住 16 位短 UUID 设备)并轮询 getBluetoothDevices。
|
||||||
*/
|
*/
|
||||||
async function startSearch() {
|
async function startSearch() {
|
||||||
if (isSearching) {
|
if (isSearching) {
|
||||||
|
|
@ -606,6 +661,7 @@ export function createBluetoothDiscovery(options = {}) {
|
||||||
// 立即标记搜索中,防止 await 期间用户重复点击或 stopSearch 被覆盖
|
// 立即标记搜索中,防止 await 期间用户重复点击或 stopSearch 被覆盖
|
||||||
isSearching = true
|
isSearching = true
|
||||||
onSearchingChange(true)
|
onSearchingChange(true)
|
||||||
|
clearFallbackTimer()
|
||||||
clearDiscoveryPollTimer()
|
clearDiscoveryPollTimer()
|
||||||
clearAutoStopTimer()
|
clearAutoStopTimer()
|
||||||
|
|
||||||
|
|
@ -634,13 +690,21 @@ export function createBluetoothDiscovery(options = {}) {
|
||||||
|
|
||||||
acceptingDeviceEvents = true
|
acceptingDeviceEvents = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
await startDiscoveryWithServices(DISCOVERY_FILTER_SERVICE_UUIDS)
|
||||||
|
console.debug('[BLE][SCAN]', 'filtered discovery started, sessionId=', sessionId)
|
||||||
|
} catch (startError) {
|
||||||
|
console.debug('[BLE][SCAN]', 'filtered start failed, retry unfiltered', startError)
|
||||||
await startDiscoveryWithServices([])
|
await startDiscoveryWithServices([])
|
||||||
|
console.debug('[BLE][SCAN]', 'unfiltered discovery started (after filter fail), sessionId=', sessionId)
|
||||||
|
}
|
||||||
|
|
||||||
if (sessionId !== searchSessionId) {
|
if (sessionId !== searchSessionId) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
startDiscoveryPolling(sessionId)
|
startDiscoveryPolling(sessionId)
|
||||||
|
await fallbackToUnfilteredDiscoveryIfNeeded(sessionId)
|
||||||
|
|
||||||
// 30s 自动停止搜索
|
// 30s 自动停止搜索
|
||||||
autoStopTimer = setTimeout(() => {
|
autoStopTimer = setTimeout(() => {
|
||||||
|
|
@ -658,6 +722,7 @@ export function createBluetoothDiscovery(options = {}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function stopSearch(showStopToast = true) {
|
async function stopSearch(showStopToast = true) {
|
||||||
|
clearFallbackTimer()
|
||||||
clearDiscoveryPollTimer()
|
clearDiscoveryPollTimer()
|
||||||
clearAutoStopTimer()
|
clearAutoStopTimer()
|
||||||
|
|
||||||
|
|
@ -863,6 +928,7 @@ export function createBluetoothDiscovery(options = {}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function cleanupDiscovery() {
|
function cleanupDiscovery() {
|
||||||
|
clearFallbackTimer()
|
||||||
clearDiscoveryPollTimer()
|
clearDiscoveryPollTimer()
|
||||||
clearDeviceListEmitTimer()
|
clearDeviceListEmitTimer()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,16 @@ export const TARGET_SHORT_UUID_CANDIDATES = Object.freeze([
|
||||||
TARGET_SERVICE_UUID_COMPAT
|
TARGET_SERVICE_UUID_COMPAT
|
||||||
])
|
])
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 过滤扫描用的 128 位完整 UUID 列表(与 advertisServiceUUIDs 匹配主服务)。
|
||||||
|
* 微信 services 过滤只认完整 UUID;广播 16 位短 UUID 的机型由无过滤兜底全扫 + JS 层匹配覆盖。
|
||||||
|
*/
|
||||||
|
export const DISCOVERY_FILTER_SERVICE_UUIDS = Object.freeze([
|
||||||
|
TARGET_SERVICE_UUID_COMPAT_FULL,
|
||||||
|
TARGET_SERVICE_UUID_FULL,
|
||||||
|
TARGET_SERVICE_UUID_ALT_FULL
|
||||||
|
])
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 蓝牙常见错误提示映射。
|
* 蓝牙常见错误提示映射。
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue