蓝牙通讯初步调通
This commit is contained in:
535
pages/basics/BluetoothDebugging/BluetoothDebugging.js
Normal file
535
pages/basics/BluetoothDebugging/BluetoothDebugging.js
Normal file
@@ -0,0 +1,535 @@
|
||||
// pages/bluetooth-connect/bluetooth-connect.js
|
||||
const app = getApp()
|
||||
|
||||
const FIXED_CONNECT_CMD = new Uint8Array([0xCC, 0xC0, 0x0C, 0x00, 0x4F, 0xC0, 0x01, 0x00, 0x02, 0x00, 0x0C, 0x08])
|
||||
|
||||
Page({
|
||||
data: {
|
||||
ConnectedDevName:"",
|
||||
activeTab: 'W13', // 默认选中W13
|
||||
autho: null,
|
||||
Hotelinfo: {},
|
||||
deviceList: [],
|
||||
currentDeviceId: null,
|
||||
coid:0
|
||||
},
|
||||
|
||||
// 返回上一页
|
||||
goBack() {
|
||||
// 返回前主动断开当前BLE连接,避免连接遗留
|
||||
try {
|
||||
this.disconnectCurrentDevice()
|
||||
} catch (e) {}
|
||||
wx.navigateBack()
|
||||
},
|
||||
|
||||
// 切换导航选项卡
|
||||
switchTab(e) {
|
||||
const tab = e.currentTarget.dataset.tab
|
||||
const current = this.data.activeTab
|
||||
if (tab === current) {
|
||||
return
|
||||
}
|
||||
|
||||
const hasConnected = this.data.deviceList.some(d => d.connected)
|
||||
if (hasConnected) {
|
||||
wx.showModal({
|
||||
title: '提示',
|
||||
content: '当前有已连接的蓝牙设备,切换将断开并重新搜索,是否继续?',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
this.disconnectAllDevices()
|
||||
this.setData({ activeTab: tab })
|
||||
// 切换后立即搜索一次
|
||||
this.searchBluetooth()
|
||||
}
|
||||
}
|
||||
})
|
||||
} else {
|
||||
this.setData({ activeTab: tab })
|
||||
this.searchBluetooth()
|
||||
}
|
||||
},
|
||||
|
||||
disconnectAllDevices() {
|
||||
const list = this.data.deviceList.map(d => ({ ...d, connected: false }))
|
||||
this.setData({ deviceList: list })
|
||||
},
|
||||
|
||||
// 根据选项卡加载设备数据
|
||||
loadDevicesByTab(tab) {
|
||||
// 这里可以根据tab从服务器获取对应的设备列表
|
||||
console.log('加载设备列表,选项卡:', tab)
|
||||
|
||||
// 模拟不同选项卡的设备数据
|
||||
let deviceList = []
|
||||
if (tab === 'host') {
|
||||
deviceList = [
|
||||
{
|
||||
id: 1,
|
||||
name: '主机设备1',
|
||||
signal: 95,
|
||||
connected: true
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: '主机设备2',
|
||||
signal: 80,
|
||||
connected: false
|
||||
}
|
||||
]
|
||||
} else {
|
||||
deviceList = this.data.deviceList
|
||||
}
|
||||
|
||||
this.setData({ deviceList })
|
||||
},
|
||||
|
||||
// 搜索蓝牙设备
|
||||
searchBluetooth() {
|
||||
const filterPrefix = this.data.activeTab
|
||||
|
||||
wx.showLoading({
|
||||
title: '搜索中...',
|
||||
mask: true
|
||||
})
|
||||
|
||||
// 先断开当前连接设备(如果有)
|
||||
this.disconnectCurrentDevice()
|
||||
|
||||
// 清空旧列表并启动搜索
|
||||
this.setData({ deviceList: [] })
|
||||
|
||||
this.ensureBluetoothReady()
|
||||
.then(() => this.startBluetoothDevicesDiscovery(filterPrefix))
|
||||
.catch((err) => {
|
||||
console.error('蓝牙初始化失败', err)
|
||||
wx.hideLoading()
|
||||
wx.showToast({ title: '请开启蓝牙和定位权限', icon: 'none' })
|
||||
})
|
||||
},
|
||||
|
||||
ensureBluetoothReady() {
|
||||
return new Promise((resolve, reject) => {
|
||||
wx.openBluetoothAdapter({
|
||||
mode: 'central',
|
||||
success: () => {
|
||||
resolve()
|
||||
},
|
||||
fail: (err) => {
|
||||
// 10001 系统蓝牙未打开;10002 无权限
|
||||
if (err && err.errCode === 10001) {
|
||||
wx.showModal({
|
||||
title: '蓝牙未开启',
|
||||
content: '请先打开手机蓝牙后重试',
|
||||
showCancel: false
|
||||
})
|
||||
} else {
|
||||
wx.showModal({
|
||||
title: '权限提示',
|
||||
content: '请授权蓝牙与定位权限后重试',
|
||||
showCancel: false
|
||||
})
|
||||
}
|
||||
reject(err)
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
startBluetoothDevicesDiscovery(prefix) {
|
||||
// 先取消旧的发现监听,避免多次注册造成干扰
|
||||
this.teardownDeviceFoundListener()
|
||||
this._foundCount = 0
|
||||
console.info('[BLE] start scan, prefix:', prefix || 'ALL')
|
||||
// 先停止可能已有的搜索,待停止完成后再启动,避免竞态
|
||||
wx.stopBluetoothDevicesDiscovery({
|
||||
complete: () => {
|
||||
wx.startBluetoothDevicesDiscovery({
|
||||
allowDuplicatesKey: true, // 允许重复上报,提升二次搜索的发现率
|
||||
success: () => {
|
||||
this.setupDeviceFoundListener(prefix)
|
||||
// 定时停止,避免长时间占用
|
||||
setTimeout(() => {
|
||||
this.stopBluetoothDiscovery()
|
||||
}, 6000)
|
||||
},
|
||||
fail: (err) => {
|
||||
console.error('开始搜索蓝牙设备失败', err)
|
||||
wx.hideLoading()
|
||||
wx.showToast({ title: '搜索失败', icon: 'none' })
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
setupDeviceFoundListener(prefix) {
|
||||
this._deviceFoundHandler = (res) => {
|
||||
const devices = (res && res.devices) || []
|
||||
if (devices.length) this._foundCount = (this._foundCount || 0) + devices.length
|
||||
this.handleDeviceFound(devices, prefix)
|
||||
}
|
||||
if (typeof wx.onBluetoothDeviceFound === 'function') {
|
||||
wx.onBluetoothDeviceFound(this._deviceFoundHandler)
|
||||
}
|
||||
},
|
||||
|
||||
teardownDeviceFoundListener() {
|
||||
if (this._deviceFoundHandler && typeof wx.offBluetoothDeviceFound === 'function') {
|
||||
wx.offBluetoothDeviceFound(this._deviceFoundHandler)
|
||||
}
|
||||
this._deviceFoundHandler = null
|
||||
},
|
||||
|
||||
handleDeviceFound(devices, prefix) {
|
||||
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)
|
||||
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 mapped = {
|
||||
id: dev.deviceId,
|
||||
name,
|
||||
mac: dev.deviceId,
|
||||
signal,
|
||||
connected: false,
|
||||
RSSI: rssi,
|
||||
localName: dev.localName || '',
|
||||
serviceUUIDs: dev.serviceUUIDs || []
|
||||
}
|
||||
|
||||
if (existsIndex >= 0) {
|
||||
list[existsIndex] = { ...list[existsIndex], ...mapped }
|
||||
} else {
|
||||
list.push(mapped)
|
||||
}
|
||||
})
|
||||
|
||||
this.setData({ deviceList: list })
|
||||
},
|
||||
|
||||
// 停止蓝牙搜索
|
||||
stopBluetoothDiscovery() {
|
||||
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
|
||||
wx.showToast({ title: `发现${count}个设备`, icon: 'success', duration: 1500 })
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
onUnload() {
|
||||
// 页面卸载时清理蓝牙扫描与监听
|
||||
// this.teardownDeviceFoundListener()
|
||||
if (typeof wx.stopBluetoothDevicesDiscovery === 'function') {
|
||||
wx.stopBluetoothDevicesDiscovery({ complete: () => {} })
|
||||
}
|
||||
if (this._fixedLoopTimer) {
|
||||
clearInterval(this._fixedLoopTimer)
|
||||
this._fixedLoopTimer = null
|
||||
}
|
||||
},
|
||||
|
||||
// 连接设备
|
||||
onDeviceTap(e) {
|
||||
const index = e.currentTarget.dataset.index
|
||||
const device = this.data.deviceList[index]
|
||||
let coid= this.data.coid
|
||||
if (!device) return
|
||||
|
||||
const currentIndex = this.data.deviceList.findIndex(d => d.connected)
|
||||
|
||||
// 如果点击的就是已连接设备,直接进入对应页面并携带已保存的BLE参数
|
||||
if (currentIndex === index && currentIndex >= 0) {
|
||||
if (this.data.activeTab === 'W13') {
|
||||
const devName = device.name || 'W13设备'
|
||||
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 || ''
|
||||
// 至少携带 mac;若已持有 svc/tx/rx 则一并带上,避免重复发现
|
||||
const base = `/pages/basics/BluetoothDebugging/B13page/B13page?DevName=${encodeURIComponent(devName)}&mac=${encodeURIComponent(mac)}`
|
||||
const withParams = (svc && tx && rx)
|
||||
? `${base}&serviceId=${encodeURIComponent(svc)}&txCharId=${encodeURIComponent(tx)}&rxCharId=${encodeURIComponent(rx)}`
|
||||
: base
|
||||
console.log(url)
|
||||
console.log(withParams)
|
||||
wx.navigateTo({ url: withParams })
|
||||
|
||||
|
||||
|
||||
} else {
|
||||
wx.showToast({ title: '已连接当前设备', icon: 'none' })
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if (currentIndex >= 0 && currentIndex !== index) {
|
||||
wx.showModal({
|
||||
title: '切换设备',
|
||||
content: '已连接其他设备,是否切换到当前设备?',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
this.connectToDevice(index)
|
||||
}
|
||||
}
|
||||
})
|
||||
} else if (currentIndex < 0) {
|
||||
wx.showModal({
|
||||
title: '连接设备',
|
||||
content: '是否连接此蓝牙设备?',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
this.connectToDevice(index)
|
||||
}
|
||||
}
|
||||
})
|
||||
} else {
|
||||
this.connectToDevice(index)
|
||||
}
|
||||
},
|
||||
|
||||
connectToDevice(index) {
|
||||
const device = this.data.deviceList[index]
|
||||
if (!device || !device.id) {
|
||||
wx.showToast({ title: '设备信息缺失', icon: 'none' })
|
||||
return
|
||||
}
|
||||
|
||||
wx.showLoading({ title: '连接中...', mask: true })
|
||||
// 使用 BLE 直连,不再使用模拟延迟
|
||||
wx.createBLEConnection({
|
||||
deviceId: device.id,
|
||||
success: () => {
|
||||
const list = this.data.deviceList.map((d, i) => ({ ...d, connected: i === index }))
|
||||
this.setData({ deviceList: list, currentDeviceId: device.id })
|
||||
// 设置MTU为256,提升传输效率
|
||||
if (typeof wx.setBLEMTU === 'function') {
|
||||
wx.setBLEMTU({
|
||||
deviceId: device.id,
|
||||
mtu: 500,
|
||||
fail: () => console.warn('[BLE] set MTU 256 failed'),
|
||||
success: () => console.info('[BLE] set MTU 256 success')
|
||||
})
|
||||
}
|
||||
// 连接成功后发现服务与特征
|
||||
this.discoverBleChannels(device)
|
||||
},
|
||||
fail: (err) => {
|
||||
wx.hideLoading()
|
||||
console.error('BLE 连接失败', err)
|
||||
wx.showToast({ title: '连接失败', icon: 'none' })
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
// 发现包含 FFE1(写) / FFE2(订阅) 的服务与特征,并启用 FFE2 通知
|
||||
discoverBleChannels(device) {
|
||||
const deviceId = device.id
|
||||
wx.getBLEDeviceServices({
|
||||
deviceId,
|
||||
success: (srvRes) => {
|
||||
const services = srvRes.services || []
|
||||
if (!services.length) {
|
||||
wx.hideLoading()
|
||||
wx.showToast({ title: '未发现服务', icon: 'none' })
|
||||
return
|
||||
}
|
||||
let found = false
|
||||
let pending = services.length
|
||||
// 优先自定义/未知服务(UUID 含 FFE)其余按原顺序
|
||||
const score = (s) => {
|
||||
const u = (s.uuid || '').toUpperCase()
|
||||
return u.includes('FFE') ? 2 : (s.isPrimary === true ? 1 : 0)
|
||||
}
|
||||
const sorted = services.slice().sort((a, b) => score(b) - score(a))
|
||||
sorted.forEach(s => {
|
||||
const serviceId = s.uuid
|
||||
wx.getBLEDeviceCharacteristics({
|
||||
deviceId,
|
||||
serviceId,
|
||||
success: (chRes) => {
|
||||
const chars = chRes.characteristics || []
|
||||
const ffe1 = chars.find(c => this._matchUuid(c.uuid, 'FFE1'))
|
||||
const ffe2 = chars.find(c => this._matchUuid(c.uuid, 'FFE2'))
|
||||
if (!found && ffe1 && ffe2) {
|
||||
found = true
|
||||
// 启用FFE2通知
|
||||
wx.notifyBLECharacteristicValueChange({
|
||||
state: true,
|
||||
deviceId,
|
||||
serviceId,
|
||||
characteristicId: ffe2.uuid,
|
||||
complete: () => {
|
||||
wx.hideLoading()
|
||||
wx.showToast({ title: '连接成功', icon: 'success' })
|
||||
// 保存当前服务与特征,供已连接设备直接进入页面使用
|
||||
this.setData({
|
||||
currentServiceId: serviceId,
|
||||
currentTxCharId: ffe1.uuid,
|
||||
currentRxCharId: ffe2.uuid
|
||||
})
|
||||
// 连接成功后发送指定命令帧
|
||||
// this.sendFixedCommand(deviceId, serviceId, ffe1.uuid)
|
||||
|
||||
if (this.data.activeTab === 'W13') {
|
||||
const devName = device.name || 'W13设备'
|
||||
const url = `/pages/basics/BluetoothDebugging/B13page/B13page?DevName=${encodeURIComponent(devName)}&mac=${encodeURIComponent(deviceId)}&serviceId=${encodeURIComponent(serviceId)}&txCharId=${encodeURIComponent(ffe1.uuid)}&rxCharId=${encodeURIComponent(ffe2.uuid)}`
|
||||
console.log(url)
|
||||
wx.navigateTo({ url })
|
||||
// this.sendFixedCommand(deviceId, serviceId, ffe1.uuid)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
fail: () => {
|
||||
// 不中断流程,继续其他服务
|
||||
},
|
||||
complete: () => {
|
||||
pending -= 1
|
||||
if (!found && pending === 0) {
|
||||
wx.hideLoading()
|
||||
wx.showModal({ title: '提示', content: '未找到FFE1/FFE2特征', showCancel: false })
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
fail: (err) => {
|
||||
wx.hideLoading()
|
||||
console.error('获取服务失败', err)
|
||||
wx.showToast({ title: '获取服务失败', icon: 'none' })
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
_matchUuid(uuid, needle) {
|
||||
if (!uuid || !needle) return false
|
||||
const u = String(uuid).replace(/-/g, '').toUpperCase()
|
||||
const n = String(needle).toUpperCase().replace(/^0X/, '')
|
||||
// 模糊匹配:包含指定段即可(兼容16/128位 UUID)
|
||||
return u.includes(n)
|
||||
},
|
||||
|
||||
sendFixedCommand(deviceId, serviceId, txCharId) {
|
||||
if (!deviceId || !serviceId || !txCharId || typeof wx.writeBLECharacteristicValue !== 'function') return
|
||||
console.info(`[BLE] sendFixedCommand params device=${deviceId} svc=${serviceId} tx=${txCharId}`)
|
||||
try {
|
||||
wx.writeBLECharacteristicValue({
|
||||
deviceId,
|
||||
serviceId,
|
||||
characteristicId: txCharId,
|
||||
value: FIXED_CONNECT_CMD.buffer,
|
||||
complete: () => {
|
||||
console.info(`[BLE] sent fixed cmd device=${deviceId} svc=${serviceId} tx=${txCharId}`)
|
||||
}
|
||||
})
|
||||
} catch (e) {
|
||||
console.warn('[BLE] send fixed cmd failed', e)
|
||||
}
|
||||
},
|
||||
|
||||
// 测试函数:每5秒发送一次固定命令
|
||||
startFixedCmdLoop() {
|
||||
const deviceId = this.data.currentDeviceId
|
||||
const serviceId = this.data.currentServiceId
|
||||
const txCharId = this.data.currentTxCharId
|
||||
if (!deviceId || !serviceId || !txCharId) {
|
||||
wx.showToast({ title: '未连接BLE', icon: 'none' })
|
||||
return
|
||||
}
|
||||
if (this._fixedLoopTimer) {
|
||||
clearInterval(this._fixedLoopTimer)
|
||||
this._fixedLoopTimer = null
|
||||
}
|
||||
const sendOnce = () => {
|
||||
try {
|
||||
wx.writeBLECharacteristicValue({
|
||||
deviceId,
|
||||
serviceId,
|
||||
characteristicId: txCharId,
|
||||
value: FIXED_CONNECT_CMD.buffer,
|
||||
fail: (err) => {
|
||||
console.warn('[BLE] loop fixed cmd fail', err && (err.errMsg || err.message) || err)
|
||||
}
|
||||
})
|
||||
} catch (e) {
|
||||
console.warn('[BLE] loop fixed cmd exception', e && (e.errMsg || e.message) || e)
|
||||
}
|
||||
}
|
||||
sendOnce()
|
||||
this._fixedLoopTimer = setInterval(sendOnce, 5000)
|
||||
wx.showToast({ title: '已启动固定命令循环', icon: 'none' })
|
||||
},
|
||||
|
||||
// 可选:停止循环发送
|
||||
stopFixedCmdLoop() {
|
||||
if (this._fixedLoopTimer) {
|
||||
clearInterval(this._fixedLoopTimer)
|
||||
this._fixedLoopTimer = null
|
||||
wx.showToast({ title: '已停止循环', icon: 'none' })
|
||||
}
|
||||
},
|
||||
|
||||
// 断开当前连接设备(如果有真实连接)
|
||||
disconnectCurrentDevice() {
|
||||
const idx = this.data.deviceList.findIndex(d => d.connected)
|
||||
if (idx >= 0) {
|
||||
// 标记断开状态
|
||||
const list = this.data.deviceList.map((d, i) => ({ ...d, connected: false }))
|
||||
this.setData({ deviceList: list })
|
||||
}
|
||||
// 如果保留了设备ID,尝试调用系统断开
|
||||
const devId = this.data.currentDeviceId
|
||||
if (devId && typeof wx.closeBLEConnection === 'function') {
|
||||
try {
|
||||
wx.closeBLEConnection({ deviceId: devId, complete: () => {} })
|
||||
} catch (e) {
|
||||
// 忽略断开异常,继续搜索
|
||||
}
|
||||
this.setData({ currentDeviceId: null })
|
||||
}
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
const { autho } = app.globalData || {}
|
||||
let currentHotel = null
|
||||
|
||||
if (autho && Array.isArray(autho) && autho.length > 0) {
|
||||
// 优先取第一个分组里的第一个酒店;后续可按需要改为 options.HotelId 精确匹配
|
||||
const firstGroup = autho[0]
|
||||
if (firstGroup && Array.isArray(firstGroup.Hotels) && firstGroup.Hotels.length > 0) {
|
||||
currentHotel = firstGroup.Hotels[0]
|
||||
}
|
||||
}
|
||||
|
||||
if (currentHotel) {
|
||||
this.setData({
|
||||
autho,
|
||||
Hotelinfo: currentHotel
|
||||
})
|
||||
|
||||
const title = currentHotel.HotelName
|
||||
? `${currentHotel.HotelName}${currentHotel.Code ? ' (' + currentHotel.Code + ')' : ''}`
|
||||
: '蓝牙调试'
|
||||
wx.setNavigationBarTitle({ title })
|
||||
} else {
|
||||
wx.setNavigationBarTitle({ title: '蓝牙调试' })
|
||||
}
|
||||
|
||||
// 页面加载时,根据当前选中的选项卡加载设备
|
||||
this.loadDevicesByTab(this.data.activeTab)
|
||||
// 同步执行一次蓝牙搜索(按 W13 过滤规则)
|
||||
this.searchBluetooth()
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user