feat: 添加蓝牙调试页面的初始实现
- 新增 BLVRQPage.json 配置文件,设置导航栏样式。 - 新增 BLVRQPage.wxml 文件,构建蓝牙设备信息展示和操作界面。 - 新增 BLVRQPage.wxss 文件,定义页面样式和布局。
This commit is contained in:
@@ -2,11 +2,18 @@
|
||||
const app = getApp()
|
||||
|
||||
const FIXED_CONNECT_CMD = new Uint8Array([0xCC, 0xC0, 0x0C, 0x00, 0x4F, 0xC0, 0x01, 0x00, 0x02, 0x00, 0x0C, 0x08])
|
||||
const DEVICE_EXPIRE_MS = 15000
|
||||
const SEARCH_DURATION_MS = 6000
|
||||
const SEARCH_RESTART_DELAY_MS = 350
|
||||
const RESUME_BACKGROUND_SEARCH_DELAY_MS = 2500
|
||||
|
||||
Page({
|
||||
data: {
|
||||
ConnectedDevName:"",
|
||||
activeTab: 'W13', // 默认选中W13
|
||||
deviceTypeLabels: ['请选择设备类型', '主机', 'W13', 'BLV_RQ'],
|
||||
deviceTypeValues: ['NONE', 'HOST', 'W13', 'BLV_RQ'],
|
||||
deviceTypeIndex: 0,
|
||||
activeTab: 'NONE',
|
||||
autho: null,
|
||||
Hotelinfo: {},
|
||||
deviceList: [],
|
||||
@@ -14,6 +21,150 @@ Page({
|
||||
coid:0
|
||||
},
|
||||
|
||||
getSignalByRSSI(rssi) {
|
||||
if (typeof rssi !== 'number' || Number.isNaN(rssi)) return 0
|
||||
return Math.max(0, Math.min(100, 100 + rssi))
|
||||
},
|
||||
|
||||
getConnectedDevice() {
|
||||
return (this.data.deviceList || []).find(item => item.connected)
|
||||
},
|
||||
|
||||
markDeviceDisconnected(deviceId, options = {}) {
|
||||
if (!deviceId) return
|
||||
const now = Date.now()
|
||||
const removeIfStale = !!options.removeIfStale
|
||||
const nextList = (this.data.deviceList || [])
|
||||
.map(item => item.id === deviceId
|
||||
? {
|
||||
...item,
|
||||
connected: false,
|
||||
RSSI: null,
|
||||
signal: 0,
|
||||
disconnectedAt: now
|
||||
}
|
||||
: item)
|
||||
.filter(item => {
|
||||
if (!removeIfStale) return true
|
||||
if (item.id !== deviceId) return true
|
||||
const lastSeenAt = item.lastSeenAt || 0
|
||||
return now - lastSeenAt <= DEVICE_EXPIRE_MS
|
||||
})
|
||||
const nextData = { deviceList: nextList }
|
||||
if (this.data.currentDeviceId === deviceId) {
|
||||
nextData.currentDeviceId = null
|
||||
}
|
||||
this.setData(nextData)
|
||||
},
|
||||
|
||||
reconcileConnectedDevices(connectedDevices = [], deviceType = this.data.activeTab) {
|
||||
const connectedIds = new Set((connectedDevices || []).map(item => item.deviceId))
|
||||
const now = Date.now()
|
||||
const nextList = (this.data.deviceList || []).map(item => {
|
||||
const name = item.name || item.localName || ''
|
||||
if (!this.matchDeviceType(name, deviceType)) return item
|
||||
if (connectedIds.has(item.id)) return item
|
||||
if (!item.connected) return item
|
||||
return {
|
||||
...item,
|
||||
connected: false,
|
||||
RSSI: null,
|
||||
signal: 0,
|
||||
disconnectedAt: now
|
||||
}
|
||||
})
|
||||
const hasCurrentConnected = this.data.currentDeviceId && connectedIds.has(this.data.currentDeviceId)
|
||||
this.setData({
|
||||
deviceList: nextList,
|
||||
currentDeviceId: hasCurrentConnected ? this.data.currentDeviceId : null
|
||||
})
|
||||
},
|
||||
|
||||
bindSearchPageBleStateListener() {
|
||||
if (this._bleStateChangeHandler || typeof wx.onBLEConnectionStateChange !== 'function') return
|
||||
this._bleStateChangeHandler = (res) => {
|
||||
if (!res || !res.deviceId) return
|
||||
if (res.connected) {
|
||||
this.refreshConnectedDeviceRSSI(res.deviceId)
|
||||
return
|
||||
}
|
||||
this.markDeviceDisconnected(res.deviceId, { removeIfStale: true })
|
||||
}
|
||||
wx.onBLEConnectionStateChange(this._bleStateChangeHandler)
|
||||
},
|
||||
|
||||
teardownSearchPageBleStateListener() {
|
||||
if (this._bleStateChangeHandler && typeof wx.offBLEConnectionStateChange === 'function') {
|
||||
wx.offBLEConnectionStateChange(this._bleStateChangeHandler)
|
||||
}
|
||||
this._bleStateChangeHandler = null
|
||||
},
|
||||
|
||||
startConnectedDeviceMonitor() {
|
||||
if (this._connectedDeviceMonitorTimer) return
|
||||
this._connectedDeviceMonitorTimer = setInterval(() => {
|
||||
const connectedDevice = this.getConnectedDevice()
|
||||
if (!connectedDevice) return
|
||||
this.refreshConnectedDeviceRSSI(connectedDevice.id)
|
||||
}, 3000)
|
||||
},
|
||||
|
||||
stopConnectedDeviceMonitor() {
|
||||
if (this._connectedDeviceMonitorTimer) {
|
||||
clearInterval(this._connectedDeviceMonitorTimer)
|
||||
this._connectedDeviceMonitorTimer = null
|
||||
}
|
||||
},
|
||||
|
||||
applyDeviceTypeChange(selectedType, index) {
|
||||
const nextList = (this.data.deviceList || []).filter((item) => {
|
||||
const name = item.name || item.localName || ''
|
||||
return this.matchDeviceType(name, selectedType)
|
||||
})
|
||||
|
||||
this.setData({
|
||||
deviceTypeIndex: index,
|
||||
activeTab: selectedType,
|
||||
deviceList: nextList
|
||||
})
|
||||
|
||||
if (this._discoveryStopTimer) {
|
||||
clearTimeout(this._discoveryStopTimer)
|
||||
this._discoveryStopTimer = null
|
||||
}
|
||||
if (this._resumeBackgroundSearchTimer) {
|
||||
clearTimeout(this._resumeBackgroundSearchTimer)
|
||||
this._resumeBackgroundSearchTimer = null
|
||||
}
|
||||
this._searchToken = (this._searchToken || 0) + 1
|
||||
this._pauseBackgroundSearch = true
|
||||
this.stopBluetoothDiscovery()
|
||||
this.syncScanTimerByType(selectedType)
|
||||
|
||||
if (this.isSearchableDeviceType(selectedType)) {
|
||||
this.searchBluetooth({ source: 'selector', restart: true, clearList: false, showLoading: true })
|
||||
}
|
||||
},
|
||||
|
||||
refreshConnectedDeviceRSSI(deviceId) {
|
||||
if (!deviceId || typeof wx.getBLEDeviceRSSI !== 'function') return
|
||||
wx.getBLEDeviceRSSI({
|
||||
deviceId,
|
||||
success: (res) => {
|
||||
const rssi = typeof res.RSSI === 'number' ? res.RSSI : null
|
||||
if (rssi == null) return
|
||||
const signal = this.getSignalByRSSI(rssi)
|
||||
const list = (this.data.deviceList || []).map(item => item.id === deviceId
|
||||
? { ...item, connected: true, RSSI: rssi, signal, lastSeenAt: Date.now() }
|
||||
: item)
|
||||
this.setData({ deviceList: list })
|
||||
},
|
||||
fail: () => {
|
||||
this.markDeviceDisconnected(deviceId, { removeIfStale: true })
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
// 返回上一页
|
||||
goBack() {
|
||||
// 返回前主动断开当前BLE连接,避免连接遗留
|
||||
@@ -23,32 +174,67 @@ Page({
|
||||
wx.navigateBack()
|
||||
},
|
||||
|
||||
// 切换导航选项卡
|
||||
switchTab(e) {
|
||||
const tab = e.currentTarget.dataset.tab
|
||||
const current = this.data.activeTab
|
||||
if (tab === current) {
|
||||
markPendingBleNavigation(target, deviceId, session = {}) {
|
||||
if (!app.globalData) return
|
||||
app.globalData.pendingBleNavigation = {
|
||||
target,
|
||||
deviceId: deviceId || '',
|
||||
session: { ...session },
|
||||
keepConnection: true,
|
||||
timestamp: Date.now()
|
||||
}
|
||||
},
|
||||
|
||||
scheduleResumeBackgroundSearch() {
|
||||
if (this._resumeBackgroundSearchTimer) {
|
||||
clearTimeout(this._resumeBackgroundSearchTimer)
|
||||
this._resumeBackgroundSearchTimer = null
|
||||
}
|
||||
this._resumeBackgroundSearchTimer = setTimeout(() => {
|
||||
this._resumeBackgroundSearchTimer = null
|
||||
this._pauseBackgroundSearch = false
|
||||
}, RESUME_BACKGROUND_SEARCH_DELAY_MS)
|
||||
},
|
||||
|
||||
syncSearchLoadingByList(searchToken = this._searchToken) {
|
||||
const hasDevices = Array.isArray(this.data.deviceList) && this.data.deviceList.length > 0
|
||||
if (hasDevices) {
|
||||
this._shouldShowSearching = false
|
||||
if (this._searchToken === searchToken) {
|
||||
wx.hideLoading()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
const hasConnected = this.data.deviceList.some(d => d.connected)
|
||||
if (hasConnected) {
|
||||
if (this._shouldShowSearching && this._searchToken === searchToken) {
|
||||
wx.showLoading({ title: '搜索中...', mask: true })
|
||||
}
|
||||
},
|
||||
|
||||
onDeviceTypeChange(e) {
|
||||
const index = Number(e.detail.value || 0)
|
||||
const selectedType = this.data.deviceTypeValues[index] || 'NONE'
|
||||
if (selectedType === this.data.activeTab) {
|
||||
return
|
||||
}
|
||||
const connectedDevice = this.getConnectedDevice()
|
||||
if (connectedDevice) {
|
||||
wx.showModal({
|
||||
title: '提示',
|
||||
content: '当前有已连接的蓝牙设备,切换将断开并重新搜索,是否继续?',
|
||||
title: '切换设备类型',
|
||||
content: '当前有蓝牙设备处于已连接状态,切换设备类型后将断开该连接,是否继续?',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
this.disconnectAllDevices()
|
||||
this.setData({ activeTab: tab })
|
||||
// 切换后立即搜索一次
|
||||
this.searchBluetooth()
|
||||
if (!res.confirm) {
|
||||
this.setData({ deviceTypeIndex: this.data.deviceTypeValues.indexOf(this.data.activeTab) })
|
||||
return
|
||||
}
|
||||
try { this.disconnectCurrentDevice() } catch (e) {}
|
||||
this.applyDeviceTypeChange(selectedType, index)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
this.setData({ activeTab: tab })
|
||||
this.searchBluetooth()
|
||||
return
|
||||
}
|
||||
|
||||
this.applyDeviceTypeChange(selectedType, index)
|
||||
},
|
||||
|
||||
disconnectAllDevices() {
|
||||
@@ -88,21 +274,52 @@ Page({
|
||||
},
|
||||
|
||||
// 搜索蓝牙设备
|
||||
searchBluetooth() {
|
||||
const filterPrefix = this.data.activeTab
|
||||
searchBluetooth(options = {}) {
|
||||
const deviceType = this.data.activeTab
|
||||
const source = options.source || 'manual'
|
||||
const restart = options.restart !== false
|
||||
const clearList = !!options.clearList
|
||||
const showLoading = !!options.showLoading
|
||||
if (!this.isSearchableDeviceType(deviceType)) {
|
||||
if (deviceType === 'HOST') {
|
||||
wx.showToast({ title: '该设备暂未发布', icon: 'none' })
|
||||
} else {
|
||||
wx.showToast({ title: '未选择设备类型', icon: 'none' })
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if (clearList) {
|
||||
this.setData({ deviceList: [] })
|
||||
}
|
||||
|
||||
// 先断开当前连接设备(如果有)
|
||||
// this.disconnectCurrentDevice()
|
||||
if (source !== 'timer') {
|
||||
this._manualSearchInProgress = true
|
||||
this._pauseBackgroundSearch = true
|
||||
if (this._resumeBackgroundSearchTimer) {
|
||||
clearTimeout(this._resumeBackgroundSearchTimer)
|
||||
this._resumeBackgroundSearchTimer = null
|
||||
}
|
||||
}
|
||||
|
||||
// // 清空旧列表并启动搜索
|
||||
// this.setData({ deviceList: [] })
|
||||
this._shouldShowSearching = showLoading
|
||||
this._hasFoundDeviceInCurrentSearch = false
|
||||
this._searchToken = (this._searchToken || 0) + 1
|
||||
const currentSearchToken = this._searchToken
|
||||
this.syncSearchLoadingByList(currentSearchToken)
|
||||
|
||||
this.ensureBluetoothReady()
|
||||
.then(() => this.startBluetoothDevicesDiscovery(filterPrefix))
|
||||
.then(() => this.startBluetoothDevicesDiscovery(deviceType, { restart, source, searchToken: currentSearchToken }))
|
||||
.catch((err) => {
|
||||
this._shouldShowSearching = false
|
||||
if (source !== 'timer') {
|
||||
this._manualSearchInProgress = false
|
||||
this.scheduleResumeBackgroundSearch()
|
||||
}
|
||||
console.error('蓝牙初始化失败', err)
|
||||
wx.hideLoading()
|
||||
if (this._searchToken === currentSearchToken) {
|
||||
wx.hideLoading()
|
||||
}
|
||||
wx.showToast({ title: '请开启蓝牙和定位权限', icon: 'none' })
|
||||
})
|
||||
},
|
||||
@@ -135,10 +352,18 @@ Page({
|
||||
})
|
||||
},
|
||||
|
||||
startBluetoothDevicesDiscovery(prefix) {
|
||||
startBluetoothDevicesDiscovery(deviceType, options = {}) {
|
||||
const restart = options.restart !== false
|
||||
const source = options.source || 'manual'
|
||||
const searchToken = options.searchToken || 0
|
||||
// 先取消旧的发现监听,避免多次注册造成干扰
|
||||
this.teardownDeviceFoundListener()
|
||||
if (this._discoveryStopTimer) {
|
||||
clearTimeout(this._discoveryStopTimer)
|
||||
this._discoveryStopTimer = null
|
||||
}
|
||||
this._foundCount = 0
|
||||
this._scanDeviceType = deviceType
|
||||
const now = Date.now()
|
||||
// 防护:避免短时间内频繁触发扫描(系统限制)
|
||||
if (this._lastScanAt && now - this._lastScanAt < 2000) {
|
||||
@@ -146,33 +371,58 @@ Page({
|
||||
return
|
||||
}
|
||||
this._lastScanAt = now
|
||||
console.info('[BLE] start scan, prefix:', prefix || 'ALL')
|
||||
console.info('[BLE] start scan, deviceType:', deviceType || 'NONE')
|
||||
|
||||
const doStart = () => {
|
||||
const beginDiscovery = () => {
|
||||
wx.startBluetoothDevicesDiscovery({
|
||||
allowDuplicatesKey: true, // 允许重复上报,提升二次搜索的发现率
|
||||
success: () => {
|
||||
this._isDiscoveringSearch = true
|
||||
this.setupDeviceFoundListener(deviceType)
|
||||
// 定时停止,避免长时间占用
|
||||
this._discoveryStopTimer = setTimeout(() => {
|
||||
this._discoveryStopTimer = null
|
||||
this.stopBluetoothDiscovery()
|
||||
}, SEARCH_DURATION_MS)
|
||||
},
|
||||
fail: (err) => {
|
||||
console.error('开始搜索蓝牙设备失败', err)
|
||||
// 若为系统提示搜索过于频繁,可稍后重试一次
|
||||
const code = err && (err.errCode || (err.errMsg && Number((err.errMsg.match(/\d+/)||[])[0])))
|
||||
const message = (err && (err.errMsg || err.message) || '').toLowerCase()
|
||||
if (code === 10008) {
|
||||
try { this.appendLog && this.appendLog('WARN', 'startBluetoothDevicesDiscovery failed: scanning too frequently, retrying shortly') } catch (e) {}
|
||||
setTimeout(() => { try { doStart() } catch (e) {} }, 1500)
|
||||
return
|
||||
}
|
||||
if ((code === 1509008 || message.indexOf('location permission is denied') >= 0) && restart) {
|
||||
setTimeout(() => { try { beginDiscovery() } catch (e) {} }, 800)
|
||||
return
|
||||
}
|
||||
if (source !== 'timer') {
|
||||
this._manualSearchInProgress = false
|
||||
this.scheduleResumeBackgroundSearch()
|
||||
}
|
||||
this._shouldShowSearching = false
|
||||
if (this._searchToken === searchToken) {
|
||||
wx.hideLoading()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (!restart) {
|
||||
beginDiscovery()
|
||||
return
|
||||
}
|
||||
|
||||
// 先停止可能已有的搜索,待停止完成后再启动,避免竞态
|
||||
wx.stopBluetoothDevicesDiscovery({
|
||||
complete: () => {
|
||||
wx.startBluetoothDevicesDiscovery({
|
||||
allowDuplicatesKey: true, // 允许重复上报,提升二次搜索的发现率
|
||||
success: () => {
|
||||
this.setupDeviceFoundListener(prefix)
|
||||
// 定时停止,避免长时间占用
|
||||
setTimeout(() => {
|
||||
this.stopBluetoothDiscovery()
|
||||
}, 6000)
|
||||
},
|
||||
fail: (err) => {
|
||||
console.error('开始搜索蓝牙设备失败', err)
|
||||
// 若为系统提示搜索过于频繁,可稍后重试一次
|
||||
const code = err && (err.errCode || (err.errMsg && Number((err.errMsg.match(/\d+/)||[])[0])))
|
||||
if (code === 10008) {
|
||||
try { this.appendLog && this.appendLog('WARN', 'startBluetoothDevicesDiscovery failed: scanning too frequently, retrying shortly') } catch (e) {}
|
||||
setTimeout(() => { try { doStart() } catch (e) {} }, 1500)
|
||||
return
|
||||
}
|
||||
wx.hideLoading()
|
||||
}
|
||||
})
|
||||
setTimeout(() => {
|
||||
beginDiscovery()
|
||||
}, SEARCH_RESTART_DELAY_MS)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -183,8 +433,7 @@ Page({
|
||||
success: (res) => {
|
||||
if (res && res.discovering) {
|
||||
try { this.appendLog && this.appendLog('CFG', 'adapter already discovering, attach listener') } catch (e) {}
|
||||
this.setupDeviceFoundListener(prefix)
|
||||
wx.hideLoading()
|
||||
this.setupDeviceFoundListener(deviceType)
|
||||
return
|
||||
}
|
||||
doStart()
|
||||
@@ -198,11 +447,15 @@ Page({
|
||||
}
|
||||
},
|
||||
|
||||
setupDeviceFoundListener(prefix) {
|
||||
setupDeviceFoundListener(deviceType) {
|
||||
this._deviceFoundHandler = (res) => {
|
||||
const devices = (res && res.devices) || []
|
||||
if (devices.length && this._shouldShowSearching && !this._hasFoundDeviceInCurrentSearch) {
|
||||
this._hasFoundDeviceInCurrentSearch = true
|
||||
this.scheduleResumeBackgroundSearch()
|
||||
}
|
||||
if (devices.length) this._foundCount = (this._foundCount || 0) + devices.length
|
||||
this.handleDeviceFound(devices, prefix)
|
||||
this.handleDeviceFound(devices, deviceType)
|
||||
}
|
||||
if (typeof wx.onBluetoothDeviceFound === 'function') {
|
||||
wx.onBluetoothDeviceFound(this._deviceFoundHandler)
|
||||
@@ -222,8 +475,9 @@ Page({
|
||||
try { this.appendLog && this.appendLog('CFG', 'startScanTimer') } catch (e) {}
|
||||
this._scanTimer = setInterval(() => {
|
||||
try {
|
||||
if (this._manualSearchInProgress || this._pauseBackgroundSearch) return
|
||||
// 触发一次搜索,内部有防抖保护
|
||||
this.searchBluetooth()
|
||||
this.searchBluetooth({ source: 'timer', restart: false, clearList: false, showLoading: false })
|
||||
} catch (e) { /* ignore */ }
|
||||
}, 3000)
|
||||
},
|
||||
@@ -236,51 +490,111 @@ Page({
|
||||
}
|
||||
},
|
||||
|
||||
handleDeviceFound(devices, prefix) {
|
||||
syncScanTimerByType(deviceType) {
|
||||
const type = deviceType || this.data.activeTab
|
||||
if (this.isSearchableDeviceType(type)) {
|
||||
this.startScanTimer()
|
||||
} else {
|
||||
this.stopScanTimer()
|
||||
}
|
||||
},
|
||||
|
||||
isSearchableDeviceType(deviceType) {
|
||||
return deviceType === 'W13' || deviceType === 'BLV_RQ'
|
||||
},
|
||||
|
||||
matchDeviceType(name, deviceType) {
|
||||
const value = (name || '').trim()
|
||||
if (!value) return false
|
||||
if (deviceType === 'W13') {
|
||||
return /^BLV_(W13|C13)/i.test(value)
|
||||
}
|
||||
if (deviceType === 'BLV_RQ') {
|
||||
return /^BLV_RQ/i.test(value)
|
||||
}
|
||||
return false
|
||||
},
|
||||
|
||||
handleDeviceFound(devices, deviceType) {
|
||||
const list = [...this.data.deviceList]
|
||||
devices.forEach((dev) => {
|
||||
const name = dev.name || dev.localName || ''
|
||||
if (!name) return
|
||||
const isW13 = this.data.activeTab === 'W13'
|
||||
const matched = isW13 ? /^BLV_(W13|C13)_.+$/i.test(name) : (prefix ? name.startsWith(prefix) : true)
|
||||
const matched = this.matchDeviceType(name, deviceType || this.data.activeTab)
|
||||
if (!matched) return
|
||||
|
||||
const existsIndex = list.findIndex((d) => d.id === dev.deviceId)
|
||||
const rssi = dev.RSSI || 0
|
||||
const signal = Math.max(0, Math.min(100, 100 + rssi))
|
||||
const oldItem = existsIndex >= 0 ? list[existsIndex] : null
|
||||
const hasFreshRSSI = typeof dev.RSSI === 'number' && dev.RSSI !== 0
|
||||
const rssi = hasFreshRSSI ? dev.RSSI : (oldItem && typeof oldItem.RSSI === 'number' ? oldItem.RSSI : (typeof dev.RSSI === 'number' ? dev.RSSI : null))
|
||||
const signal = this.getSignalByRSSI(rssi)
|
||||
const mapped = {
|
||||
id: dev.deviceId,
|
||||
name,
|
||||
mac: dev.deviceId,
|
||||
signal,
|
||||
connected: false,
|
||||
connected: oldItem ? !!oldItem.connected : false,
|
||||
RSSI: rssi,
|
||||
localName: dev.localName || '',
|
||||
serviceUUIDs: dev.serviceUUIDs || []
|
||||
serviceUUIDs: dev.serviceUUIDs || [],
|
||||
lastSeenAt: Date.now()
|
||||
}
|
||||
|
||||
if (existsIndex >= 0) {
|
||||
// 设备已存在时仅更新信号值与 RSSI,避免覆盖其它已保存字段
|
||||
list[existsIndex] = {
|
||||
...list[existsIndex],
|
||||
name,
|
||||
mac: dev.deviceId,
|
||||
signal,
|
||||
RSSI: rssi
|
||||
connected: typeof list[existsIndex].connected === 'boolean' ? list[existsIndex].connected : false,
|
||||
RSSI: rssi,
|
||||
localName: dev.localName || list[existsIndex].localName || '',
|
||||
serviceUUIDs: dev.serviceUUIDs || list[existsIndex].serviceUUIDs || [],
|
||||
lastSeenAt: Date.now()
|
||||
}
|
||||
} else {
|
||||
list.push(mapped)
|
||||
console.log('[BluetoothDebugging] 新增设备', {
|
||||
name: mapped.name,
|
||||
mac: mapped.mac,
|
||||
serviceUUIDs: mapped.serviceUUIDs
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
this.setData({ deviceList: list })
|
||||
this.syncSearchLoadingByList()
|
||||
},
|
||||
|
||||
// 停止蓝牙搜索
|
||||
stopBluetoothDiscovery() {
|
||||
const stoppingToken = this._searchToken || 0
|
||||
wx.stopBluetoothDevicesDiscovery({
|
||||
complete: () => {
|
||||
console.info('[BLE] stop scan, found events:', this._foundCount || 0, 'list size:', this.data.deviceList.length)
|
||||
wx.hideLoading()
|
||||
const count = this.data.deviceList.length
|
||||
this._isDiscoveringSearch = false
|
||||
this._manualSearchInProgress = false
|
||||
this.scheduleResumeBackgroundSearch()
|
||||
this._shouldShowSearching = false
|
||||
if (this._searchToken === stoppingToken) {
|
||||
wx.hideLoading()
|
||||
}
|
||||
const scanType = this._scanDeviceType || this.data.activeTab
|
||||
const now = Date.now()
|
||||
const nextList = (this.data.deviceList || []).filter((item) => {
|
||||
const name = item.name || item.localName || ''
|
||||
const isCurrentType = this.matchDeviceType(name, scanType)
|
||||
if (!isCurrentType) return true
|
||||
if (item.connected) {
|
||||
return item.id === this.data.currentDeviceId && now - (item.lastSeenAt || 0) <= DEVICE_EXPIRE_MS
|
||||
}
|
||||
const lastSeenAt = item.lastSeenAt || 0
|
||||
return now - lastSeenAt <= DEVICE_EXPIRE_MS
|
||||
})
|
||||
this.setData({ deviceList: nextList })
|
||||
this.syncSearchLoadingByList(stoppingToken)
|
||||
this._scanDeviceType = null
|
||||
// 取消自动显示搜索完成提示,避免打扰
|
||||
}
|
||||
})
|
||||
@@ -291,6 +605,16 @@ Page({
|
||||
// this.teardownDeviceFoundListener()
|
||||
// 页面卸载时停止定时扫描
|
||||
try { this.stopScanTimer && this.stopScanTimer() } catch (e) {}
|
||||
if (this._discoveryStopTimer) {
|
||||
clearTimeout(this._discoveryStopTimer)
|
||||
this._discoveryStopTimer = null
|
||||
}
|
||||
if (this._resumeBackgroundSearchTimer) {
|
||||
clearTimeout(this._resumeBackgroundSearchTimer)
|
||||
this._resumeBackgroundSearchTimer = null
|
||||
}
|
||||
this._pauseBackgroundSearch = false
|
||||
this._manualSearchInProgress = false
|
||||
if (typeof wx.stopBluetoothDevicesDiscovery === 'function') {
|
||||
wx.stopBluetoothDevicesDiscovery({ complete: () => {} })
|
||||
}
|
||||
@@ -298,6 +622,8 @@ Page({
|
||||
clearInterval(this._fixedLoopTimer)
|
||||
this._fixedLoopTimer = null
|
||||
}
|
||||
this.stopConnectedDeviceMonitor()
|
||||
this.teardownSearchPageBleStateListener()
|
||||
},
|
||||
|
||||
// 连接设备
|
||||
@@ -324,10 +650,21 @@ Page({
|
||||
: base
|
||||
console.log('navigateTo:', withParams)
|
||||
try { this._navigatingToB13 = true } catch (e) { /* ignore */ }
|
||||
this.markPendingBleNavigation('B13', mac, { devName, serviceId: svc, txCharId: tx, rxCharId: rx })
|
||||
wx.navigateTo({ url: withParams })
|
||||
} else if (this.data.activeTab === 'BLV_RQ') {
|
||||
const devName = device.name || 'BLV_RQ设备'
|
||||
const mac = this.data.currentDeviceId || device.id || ''
|
||||
const svc = this.data.currentServiceId || device.serviceId || ''
|
||||
const tx = this.data.currentTxCharId || device.txCharId || ''
|
||||
const rx = this.data.currentRxCharId || device.rxCharId || ''
|
||||
const base = `/pages/basics/BluetoothDebugging/BLVRQPage/BLVRQPage?DevName=${encodeURIComponent(devName)}&mac=${encodeURIComponent(mac)}&connected=1`
|
||||
const withParams = (svc && tx && rx)
|
||||
? `${base}&serviceId=${encodeURIComponent(svc)}&txCharId=${encodeURIComponent(tx)}&rxCharId=${encodeURIComponent(rx)}`
|
||||
: base
|
||||
try { this._navigatingToBLVRQ = true } catch (e) { /* ignore */ }
|
||||
this.markPendingBleNavigation('BLV_RQ', mac, { devName, serviceId: svc, txCharId: tx, rxCharId: rx })
|
||||
wx.navigateTo({ url: withParams })
|
||||
|
||||
|
||||
|
||||
} else {
|
||||
wx.showToast({ title: '已连接当前设备', icon: 'none' })
|
||||
}
|
||||
@@ -376,6 +713,7 @@ Page({
|
||||
success: () => {
|
||||
const list = this.data.deviceList.map((d) => ({ ...d, connected: d.id === device.id }))
|
||||
this.setData({ deviceList: list, currentDeviceId: device.id })
|
||||
this.startConnectedDeviceMonitor()
|
||||
// 设置MTU为256,提升传输效率(若支持)
|
||||
if (typeof wx.setBLEMTU === 'function') {
|
||||
wx.setBLEMTU({
|
||||
@@ -386,7 +724,17 @@ Page({
|
||||
})
|
||||
}
|
||||
// 连接成功后发现服务与特征
|
||||
this.discoverBleChannels(device)
|
||||
if (this.data.activeTab === 'BLV_RQ') {
|
||||
this.markPendingBleNavigation('BLV_RQ', device.id, {
|
||||
devName: device.name || 'BLV_RQ设备',
|
||||
serviceId: this.data.currentServiceId,
|
||||
txCharId: this.data.currentTxCharId,
|
||||
rxCharId: this.data.currentRxCharId
|
||||
})
|
||||
this.discoverBLVRQChannels(device)
|
||||
} else {
|
||||
this.discoverBleChannels(device)
|
||||
}
|
||||
},
|
||||
fail: (err) => {
|
||||
wx.hideLoading()
|
||||
@@ -397,6 +745,7 @@ Page({
|
||||
try { this.appendLog && this.appendLog('CFG', 'createBLEConnection: already connected, treating as connected') } catch (e) {}
|
||||
const list = this.data.deviceList.map((d) => ({ ...d, connected: d.id === device.id }))
|
||||
this.setData({ deviceList: list, currentDeviceId: device.id })
|
||||
this.startConnectedDeviceMonitor()
|
||||
// 继续发现服务与特征以恢复页面状态
|
||||
try { this.discoverBleChannels(device) } catch (e) {}
|
||||
return
|
||||
@@ -468,6 +817,11 @@ Page({
|
||||
try { this._navigatingToB13 = true } catch (e) { /* ignore */ }
|
||||
wx.navigateTo({ url })
|
||||
// this.sendFixedCommand(deviceId, serviceId, ffe1.uuid)
|
||||
} else if (this.data.activeTab === 'BLV_RQ') {
|
||||
const devName = device.name || 'BLV_RQ设备'
|
||||
const url = `/pages/basics/BluetoothDebugging/BLVRQPage/BLVRQPage?DevName=${encodeURIComponent(devName)}&mac=${encodeURIComponent(deviceId)}&connected=1&serviceId=${encodeURIComponent(serviceId)}&txCharId=${encodeURIComponent(ffe1.uuid)}&rxCharId=${encodeURIComponent(ffe2.uuid)}`
|
||||
try { this._navigatingToBLVRQ = true } catch (e) { /* ignore */ }
|
||||
wx.navigateTo({ url })
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -494,6 +848,91 @@ Page({
|
||||
})
|
||||
},
|
||||
|
||||
discoverBLVRQChannels(device) {
|
||||
const deviceId = device.id
|
||||
wx.getBLEDeviceServices({
|
||||
deviceId,
|
||||
success: (srvRes) => {
|
||||
const services = srvRes.services || []
|
||||
const targetService = services.find((s) => {
|
||||
const uuid = String(s.uuid || '').toLowerCase()
|
||||
return uuid.startsWith('0000fff0')
|
||||
})
|
||||
|
||||
if (!targetService) {
|
||||
wx.hideLoading()
|
||||
wx.showModal({ title: '提示', content: '未找到FFF0目标服务', showCancel: false })
|
||||
return
|
||||
}
|
||||
|
||||
wx.getBLEDeviceCharacteristics({
|
||||
deviceId,
|
||||
serviceId: targetService.uuid,
|
||||
success: (chRes) => {
|
||||
const chars = chRes.characteristics || []
|
||||
const fff1 = chars.find(c => this._matchUuid(c.uuid, 'FFF1'))
|
||||
const fff2 = chars.find(c => this._matchUuid(c.uuid, 'FFF2'))
|
||||
const fff3 = chars.find(c => this._matchUuid(c.uuid, 'FFF3'))
|
||||
|
||||
if (!fff1 || !fff2 || !fff3) {
|
||||
wx.hideLoading()
|
||||
wx.showModal({ title: '提示', content: '未找到FFF1/FFF2/FFF3完整特征', showCancel: false })
|
||||
return
|
||||
}
|
||||
|
||||
const notifyTargets = [fff1, fff2].filter(c => c.properties && c.properties.notify)
|
||||
let pending = notifyTargets.length
|
||||
const finalize = () => {
|
||||
wx.hideLoading()
|
||||
wx.showToast({ title: '连接成功', icon: 'success' })
|
||||
const devName = device.name || 'BLV_RQ设备'
|
||||
this.setData({
|
||||
currentServiceId: targetService.uuid,
|
||||
currentTxCharId: fff1.uuid,
|
||||
currentRxCharId: fff2.uuid,
|
||||
currentReadCharId: fff2.uuid,
|
||||
currentWriteCharId: fff1.uuid
|
||||
})
|
||||
const url = `/pages/basics/BluetoothDebugging/BLVRQPage/BLVRQPage?DevName=${encodeURIComponent(devName)}&mac=${encodeURIComponent(deviceId)}&connected=1&serviceId=${encodeURIComponent(targetService.uuid)}&txCharId=${encodeURIComponent(fff1.uuid)}&rxCharId=${encodeURIComponent(fff2.uuid)}&readCharId=${encodeURIComponent(fff2.uuid)}&writeCharId=${encodeURIComponent(fff1.uuid)}`
|
||||
this.markPendingBleNavigation('BLV_RQ', deviceId, { devName, serviceId: targetService.uuid, txCharId: fff1.uuid, rxCharId: fff2.uuid })
|
||||
wx.navigateTo({ url })
|
||||
}
|
||||
|
||||
if (!pending) {
|
||||
finalize()
|
||||
return
|
||||
}
|
||||
|
||||
notifyTargets.forEach((charItem) => {
|
||||
wx.notifyBLECharacteristicValueChange({
|
||||
state: true,
|
||||
deviceId,
|
||||
serviceId: targetService.uuid,
|
||||
characteristicId: charItem.uuid,
|
||||
complete: () => {
|
||||
pending -= 1
|
||||
if (pending <= 0) {
|
||||
finalize()
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
fail: (err) => {
|
||||
wx.hideLoading()
|
||||
console.error('获取BLV_RQ特征失败', err)
|
||||
wx.showToast({ title: '获取特征失败', icon: 'none' })
|
||||
}
|
||||
})
|
||||
},
|
||||
fail: (err) => {
|
||||
wx.hideLoading()
|
||||
console.error('获取BLV_RQ服务失败', err)
|
||||
wx.showToast({ title: '获取服务失败', icon: 'none' })
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
_matchUuid(uuid, needle) {
|
||||
if (!uuid || !needle) return false
|
||||
const u = String(uuid).replace(/-/g, '').toUpperCase()
|
||||
@@ -572,7 +1011,7 @@ Page({
|
||||
const idx = this.data.deviceList.findIndex(d => d.connected)
|
||||
if (idx >= 0) {
|
||||
// 标记断开状态
|
||||
const list = this.data.deviceList.map((d, i) => ({ ...d, connected: false }))
|
||||
const list = this.data.deviceList.map((d, i) => ({ ...d, connected: false, RSSI: null, signal: 0 }))
|
||||
this.setData({ deviceList: list })
|
||||
}
|
||||
// 如果保留了设备ID,尝试调用系统断开
|
||||
@@ -585,6 +1024,7 @@ Page({
|
||||
}
|
||||
this.setData({ currentDeviceId: null })
|
||||
}
|
||||
this.stopConnectedDeviceMonitor()
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
@@ -613,10 +1053,12 @@ Page({
|
||||
wx.setNavigationBarTitle({ title: '蓝牙调试' })
|
||||
}
|
||||
|
||||
// 页面加载时,根据当前选中的选项卡加载设备
|
||||
this.loadDevicesByTab(this.data.activeTab)
|
||||
// 同步执行一次蓝牙搜索(按 W13 过滤规则)
|
||||
this.searchBluetooth()
|
||||
this.setData({
|
||||
deviceTypeIndex: 0,
|
||||
activeTab: 'NONE',
|
||||
deviceList: []
|
||||
})
|
||||
this.syncScanTimerByType('NONE')
|
||||
},
|
||||
|
||||
onShow() {
|
||||
@@ -625,60 +1067,85 @@ Page({
|
||||
// 已从 B13 返回:清理导航标记但仍继续执行恢复流程,确保已连接设备状态可被恢复并展示
|
||||
this._navigatingToB13 = false
|
||||
}
|
||||
if (this._navigatingToBLVRQ) {
|
||||
this._navigatingToBLVRQ = false
|
||||
}
|
||||
|
||||
try { this.appendLog && this.appendLog('CFG', 'onShow: resume, ensuring adapter and forcing discovery') } catch (e) {}
|
||||
// 息屏唤醒后可能适配器被关闭,先确保打开后短延迟再搜索一次
|
||||
const deviceType = this.data.activeTab
|
||||
this.bindSearchPageBleStateListener()
|
||||
this.syncScanTimerByType(deviceType)
|
||||
this.startConnectedDeviceMonitor()
|
||||
|
||||
if (!this.isSearchableDeviceType(deviceType)) {
|
||||
try { this.appendLog && this.appendLog('CFG', 'onShow: no searchable device type, skip discovery') } catch (e) {}
|
||||
return
|
||||
}
|
||||
|
||||
try { this.appendLog && this.appendLog('CFG', 'onShow: resume adapter and restore devices without auto scan') } catch (e) {}
|
||||
this.ensureBluetoothReady()
|
||||
.then(() => {
|
||||
setTimeout(() => {
|
||||
try {
|
||||
this.startBluetoothDevicesDiscovery(this.data.activeTab)
|
||||
} catch (e) { /* ignore */ }
|
||||
}, 300)
|
||||
|
||||
// 尝试获取系统已连接设备,优先恢复之前连接的设备显示(防止已连接但不广播的设备无法被扫描到)
|
||||
try {
|
||||
const svc = this.data.currentServiceId
|
||||
const isW13 = this.data.activeTab === 'W13'
|
||||
const prefix = this.data.activeTab
|
||||
const matchByTab = (name) => {
|
||||
const n = name || ''
|
||||
if (isW13) return /^BLV_(W13|C13)_.+$/i.test(n)
|
||||
return prefix ? n.startsWith(prefix) : true
|
||||
}
|
||||
const matchByType = (name) => this.matchDeviceType(name, deviceType)
|
||||
|
||||
if (typeof wx.getConnectedBluetoothDevices === 'function' && svc) {
|
||||
wx.getConnectedBluetoothDevices({ services: [svc], success: (res) => {
|
||||
const devices = (res && res.devices) || []
|
||||
this.reconcileConnectedDevices(devices, deviceType)
|
||||
if (devices.length) {
|
||||
// 将已连接设备合并到列表并按过滤规则筛选
|
||||
const list = [...this.data.deviceList]
|
||||
devices.forEach(d => {
|
||||
|
||||
const name = d.name || d.localName || ''
|
||||
if (!matchByTab(name)) return
|
||||
if (!matchByType(name)) return
|
||||
const idx = list.findIndex(x => x.id === d.deviceId)
|
||||
const mapped = { id: d.deviceId, name, mac: d.deviceId, connected: true, RSSI: d.RSSI || 0, serviceUUIDs: d.serviceUUIDs || [] }
|
||||
const prev = idx >= 0 ? list[idx] : null
|
||||
const hasSystemRSSI = typeof d.RSSI === 'number' && d.RSSI !== 0
|
||||
const rssi = hasSystemRSSI ? d.RSSI : (prev && typeof prev.RSSI === 'number' ? prev.RSSI : null)
|
||||
const mapped = {
|
||||
id: d.deviceId,
|
||||
name,
|
||||
mac: d.deviceId,
|
||||
connected: true,
|
||||
RSSI: rssi,
|
||||
signal: this.getSignalByRSSI(rssi),
|
||||
serviceUUIDs: d.serviceUUIDs || []
|
||||
}
|
||||
mapped.lastSeenAt = Date.now()
|
||||
if (idx >= 0) list[idx] = { ...list[idx], ...mapped }
|
||||
else list.unshift(mapped)
|
||||
this.refreshConnectedDeviceRSSI(d.deviceId)
|
||||
})
|
||||
this.setData({ deviceList: list })
|
||||
try { this.appendLog && this.appendLog('CFG', 'restored connected devices from system') } catch (e) {}
|
||||
}
|
||||
}})
|
||||
} else if (typeof wx.getBluetoothDevices === 'function') {
|
||||
// 作为兜底,查询最近缓存的设备并按过滤规则合并
|
||||
wx.getBluetoothDevices({ success: (res) => {
|
||||
const devices = (res && res.devices) || []
|
||||
const connectedDevices = devices.filter(d => !!d.connected)
|
||||
this.reconcileConnectedDevices(connectedDevices, deviceType)
|
||||
if (devices.length) {
|
||||
const list = [...this.data.deviceList]
|
||||
devices.forEach(d => {
|
||||
const name = d.name || d.localName || ''
|
||||
if (!matchByTab(name)) return
|
||||
if (!matchByType(name)) return
|
||||
const idx = list.findIndex(x => x.id === d.deviceId)
|
||||
const mapped = { id: d.deviceId, name, mac: d.deviceId, connected: !!d.connected, RSSI: d.RSSI || 0, serviceUUIDs: d.serviceUUIDs || [] }
|
||||
const prev = idx >= 0 ? list[idx] : null
|
||||
const hasSystemRSSI = typeof d.RSSI === 'number' && d.RSSI !== 0
|
||||
const rssi = hasSystemRSSI ? d.RSSI : (prev && typeof prev.RSSI === 'number' ? prev.RSSI : null)
|
||||
const mapped = {
|
||||
id: d.deviceId,
|
||||
name,
|
||||
mac: d.deviceId,
|
||||
connected: !!d.connected,
|
||||
RSSI: rssi,
|
||||
signal: this.getSignalByRSSI(rssi),
|
||||
serviceUUIDs: d.serviceUUIDs || []
|
||||
}
|
||||
mapped.lastSeenAt = Date.now()
|
||||
if (idx >= 0) list[idx] = { ...list[idx], ...mapped }
|
||||
else list.push(mapped)
|
||||
if (mapped.connected) this.refreshConnectedDeviceRSSI(d.deviceId)
|
||||
})
|
||||
this.setData({ deviceList: list })
|
||||
}
|
||||
@@ -689,42 +1156,43 @@ Page({
|
||||
.catch((err) => {
|
||||
try { this.appendLog && this.appendLog('WARN', 'onShow ensureBluetoothReady failed') } catch (e) {}
|
||||
})
|
||||
// 进入页面时启动定时扫描(每3秒一次)
|
||||
try { this.startScanTimer && this.startScanTimer() } catch (e) {}
|
||||
},
|
||||
|
||||
onHide() {
|
||||
// 如果正在导航到 B13 页面,保留连接会话,但应停止扫描/发现以节省资源
|
||||
if (this._navigatingToB13) {
|
||||
try { this.appendLog && this.appendLog('CFG', 'onHide during navigation to B13: stop scan but keep connection') } catch (e) {}
|
||||
// 停止定时扫描
|
||||
try { this.stopScanTimer && this.stopScanTimer() } catch (e) {}
|
||||
// 停止发现(但不断开已连接设备),随后直接返回以保留连接状态
|
||||
try {
|
||||
if (typeof wx.stopBluetoothDevicesDiscovery === 'function') {
|
||||
wx.stopBluetoothDevicesDiscovery({ complete: () => { try { this.appendLog && this.appendLog('CFG', 'stopBluetoothDevicesDiscovery onHide (navigating to B13)') } catch (e) {} } })
|
||||
}
|
||||
} catch (e) { /* ignore */ }
|
||||
return
|
||||
}
|
||||
|
||||
// 非导航到 B13 的离开:停止定时扫描并断开连接、关闭适配器以重置状态
|
||||
this.stopConnectedDeviceMonitor()
|
||||
try { this.stopScanTimer && this.stopScanTimer() } catch (e) {}
|
||||
|
||||
if (this._discoveryStopTimer) {
|
||||
clearTimeout(this._discoveryStopTimer)
|
||||
this._discoveryStopTimer = null
|
||||
}
|
||||
if (this._resumeBackgroundSearchTimer) {
|
||||
clearTimeout(this._resumeBackgroundSearchTimer)
|
||||
this._resumeBackgroundSearchTimer = null
|
||||
}
|
||||
this._pauseBackgroundSearch = false
|
||||
this._manualSearchInProgress = false
|
||||
try {
|
||||
// 停止发现,避免后台扫描
|
||||
if (typeof wx.stopBluetoothDevicesDiscovery === 'function') {
|
||||
wx.stopBluetoothDevicesDiscovery({ complete: () => { try { this.appendLog && this.appendLog('CFG', 'stopBluetoothDevicesDiscovery onHide') } catch (e) {} } })
|
||||
}
|
||||
} catch (e) { /* ignore */ }
|
||||
|
||||
const pendingBleNavigation = app.globalData && app.globalData.pendingBleNavigation
|
||||
const keepBleSession = this._navigatingToB13 || this._navigatingToBLVRQ || (
|
||||
pendingBleNavigation &&
|
||||
pendingBleNavigation.keepConnection &&
|
||||
Date.now() - (pendingBleNavigation.timestamp || 0) < 5000
|
||||
)
|
||||
if (keepBleSession) {
|
||||
try { this.appendLog && this.appendLog('CFG', 'onHide during navigation: keep connection and skip disconnect') } catch (e) {}
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
// 断开当前连接(如果有)
|
||||
this.disconnectCurrentDevice()
|
||||
} catch (e) { /* ignore */ }
|
||||
|
||||
try {
|
||||
// 关闭蓝牙适配器以重置底层状态(部分 Android 机型息屏后需要此步骤)
|
||||
if (typeof wx.closeBluetoothAdapter === 'function') {
|
||||
wx.closeBluetoothAdapter({ complete: () => { try { this.appendLog && this.appendLog('CFG', 'closeBluetoothAdapter called onHide') } catch (e) {} } })
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user