// 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 const now = Date.now() // 防护:避免短时间内频繁触发扫描(系统限制) if (this._lastScanAt && now - this._lastScanAt < 2000) { try { this.appendLog && this.appendLog('WARN', 'skip startBluetoothDevicesDiscovery: throttled') } catch (e) {} return } this._lastScanAt = now console.info('[BLE] start scan, prefix:', prefix || 'ALL') const doStart = () => { // 先停止可能已有的搜索,待停止完成后再启动,避免竞态 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() wx.showToast({ title: '搜索失败', icon: 'none' }) } }) } }) } // 优先查询适配器状态,若系统正在扫描则直接注册监听并返回 if (typeof wx.getBluetoothAdapterState === 'function') { wx.getBluetoothAdapterState({ success: (res) => { if (res && res.discovering) { try { this.appendLog && this.appendLog('CFG', 'adapter already discovering, attach listener') } catch (e) {} this.setupDeviceFoundListener(prefix) wx.hideLoading() wx.showToast({ title: '正在搜索中', icon: 'none' }) return } doStart() }, fail: () => { doStart() } }) } else { doStart() } }, 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('navigateTo:', withParams) try { this._navigatingToB13 = true } catch (e) { /* ignore */ } 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 }) // 在尝试 createBLEConnection 前确保适配器已打开(处理息屏后 closeBluetoothAdapter 场景) this.ensureBluetoothReady() .then(() => { // 使用 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) const errmsg = (err && (err.errMsg || err.message)) || '' const isAlreadyConnected = errmsg.indexOf('already connect') >= 0 || errmsg.indexOf('already connected') >= 0 || (err && (err.errCode === -1 || err.errno === 1509007)) if (isAlreadyConnected) { try { this.appendLog && this.appendLog('CFG', 'createBLEConnection: already connected, treating as connected') } catch (e) {} const list = this.data.deviceList.map((d, i) => ({ ...d, connected: i === index })) this.setData({ deviceList: list, currentDeviceId: device.id }) // 继续发现服务与特征以恢复页面状态 try { this.discoverBleChannels(device) } catch (e) {} return } wx.showToast({ title: '连接失败', icon: 'none' }) } }) }) .catch((err) => { wx.hideLoading() console.error('蓝牙未初始化,无法连接', 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) try { this._navigatingToB13 = true } catch (e) { /* ignore */ } 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() { // 如果正在导航到 B13 页面,避免主动断开连接以保持会话 if (this._navigatingToB13) { try { this.appendLog && this.appendLog('CFG', 'skip disconnectCurrentDevice during navigate to B13') } catch (e) {} return } 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() }, onShow() { // 返回到此页面时,清理导航标记 if (this._navigatingToB13) { this._navigatingToB13 = false return } try { this.appendLog && this.appendLog('CFG', 'onShow: resume, ensuring adapter and forcing discovery') } 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 } if (typeof wx.getConnectedBluetoothDevices === 'function' && svc) { wx.getConnectedBluetoothDevices({ services: [svc], success: (res) => { const devices = (res && res.devices) || [] if (devices.length) { // 将已连接设备合并到列表并按过滤规则筛选 const list = [...this.data.deviceList] devices.forEach(d => { const name = d.name || d.localName || '' if (!matchByTab(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 || [] } if (idx >= 0) list[idx] = { ...list[idx], ...mapped } else list.unshift(mapped) }) 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) || [] if (devices.length) { const list = [...this.data.deviceList] devices.forEach(d => { const name = d.name || d.localName || '' if (!matchByTab(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 || [] } if (idx >= 0) list[idx] = { ...list[idx], ...mapped } else list.push(mapped) }) this.setData({ deviceList: list }) } }}) } } catch (e) { /* ignore */ } }) .catch((err) => { try { this.appendLog && this.appendLog('WARN', 'onShow ensureBluetoothReady failed') } catch (e) {} }) }, onHide() { // 如果正在导航到 B13 页面,跳过 onHide 的断开/重置流程,保留连接 if (this._navigatingToB13) { try { this.appendLog && this.appendLog('CFG', 'onHide skipped due to navigation to B13') } catch (e) {} // 清理标记,后续返回时 onShow 会再次执行 this._navigatingToB13 = false return } try { // 停止发现,避免后台扫描 if (typeof wx.stopBluetoothDevicesDiscovery === 'function') { wx.stopBluetoothDevicesDiscovery({ complete: () => { try { this.appendLog && this.appendLog('CFG', 'stopBluetoothDevicesDiscovery onHide') } catch (e) {} } }) } } catch (e) { /* ignore */ } 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) {} } }) } } catch (e) { /* ignore */ } } })