Page({ data: { // 传感器数据 temperature: '--', humidity: '--', tvoc: '--', co2: '--', pm1: '--', pm25: '--', pm10: '--', // 数据状态 temperatureStatus: 'normal', humidityStatus: 'normal', tvocStatus: 'normal', co2Status: 'normal', pmStatus: 'normal', // 蓝牙连接状态 deviceId: '', deviceName: '', serviceId: '', notifyCharId: '', writeCharId: '', connected: false, refreshing: false, lastConnectedTime: '', // WiFi配置 showWifiModalFlag: false, wifiName: '', wifiPassword: '', // 历史记录 showHistoryFlag: false, historyData: [] }, /** * 页面加载 */ onLoad() { console.log('页面加载完成') // 初始化蓝牙监听器 this.initBLEListener() // 启动自动刷新 this.startAutoRefresh() // 加载历史数据 this.loadHistoryData() // 记录系统日志 this.addLog('system', '页面加载完成,初始化蓝牙监听器') }, /** * 添加日志 * @param {string} type 日志类型:data或system * @param {string} content 日志内容 * @param {string} details 日志详情 */ addLog(type, content, details = '') { const app = getApp() app.globalData = app.globalData || {} app.globalData.logs = app.globalData.logs || [] const log = { time: this.getFormattedTime(), type, data: type === 'data' ? content : '', message: type === 'system' ? content : '', details } app.globalData.logs.unshift(log) // 限制日志数量 if (app.globalData.logs.length > 100) { app.globalData.logs = app.globalData.logs.slice(0, 100) } }, /** * 页面卸载 */ onUnload() { console.log('页面卸载') // 移除蓝牙监听器 wx.offBLECharacteristicValueChange() // 停止自动刷新 this.stopAutoRefresh() // 断开蓝牙连接 this.disconnectBluetooth() }, /** * 初始化蓝牙数据接收监听器 */ initBLEListener() { console.log('========== 初始化蓝牙数据接收监听器 ==========') // 移除之前的监听器,避免重复 console.log('移除之前的监听器') wx.offBLECharacteristicValueChange() // 设置新的监听器 console.log('设置新的蓝牙数据接收监听器') const listener = (res) => { console.log('========== 接收到蓝牙数据 ==========') console.log('接收到蓝牙数据事件:', res) // 检查数据是否存在 if (!res || !res.value) { console.error('接收到的数据为空:', res) return } // 处理接收到的数据 const data = new Uint8Array(res.value) console.log('接收到的原始数据:', Array.from(data)) // 转换为十六进制字符串,便于调试 const hexString = Array.from(data).map(byte => byte.toString(16).padStart(2, '0')).join('') console.log('接收到的十六进制数据:', hexString) console.log('数据长度:', data.length) // 立即记录所有蓝牙数据,无论格式是否正确 console.log('记录蓝牙数据到日志') this.addLog('data', hexString, `长度: ${data.length}字节`) // 解析传感器数据 console.log('解析传感器数据') this.parseSensorData(data) } wx.onBLECharacteristicValueChange(listener) console.log('蓝牙数据接收监听器初始化完成,等待数据...') }, /** * 解析传感器数据 * @param {Uint8Array} data 接收到的原始数据 */ parseSensorData(data) { console.log('解析传感器数据') // 隐藏加载提示 wx.hideLoading() this.setData({ refreshing: false }) // 转换为十六进制字符串,便于调试 const hexString = Array.from(data).map(byte => byte.toString(16).padStart(2, '0')).join('') console.log('接收到的完整数据:', hexString) console.log('数据长度:', data.length) console.log('原始数据数组:', Array.from(data)) // 检查数据长度 if (data.length < 18) { console.error('数据长度错误:', data.length, ',期望至少18字节') wx.showToast({ title: `数据长度错误: ${data.length}字节`, icon: 'none' }) return } // 检查帧头 if (data[0] !== 0xAA || data[1] !== 0x55) { console.error('数据格式错误,帧头不匹配:', `0x${data[0].toString(16).padStart(2, '0')} 0x${data[1].toString(16).padStart(2, '0')}`, ',期望 AA 55' ) wx.showToast({ title: '数据格式错误', icon: 'none' }) return } try { // 解析数据 - 根据用户提供的数据格式AA550000FF025B007801C20005000C000F00调整索引(小端模式) // 数据格式:AA 55 00 00 FF 02 5B 00 78 01 C2 00 05 00 0C 00 0F 00 // 解析:温度=78 01 → 0178=376→37.6℃,湿度=C2 00→00C2=194→19.4%,TVOC=05 00=5,CO2=0C 00=12,PM1=0F 00=15 console.log('开始解析各传感器数据:') // 温度数据解析 const tempRaw = (data[9] << 8) | data[8] const temperature = tempRaw / 10 console.log(`温度原始值: 0x${tempRaw.toString(16).padStart(4, '0')} = ${tempRaw} → ${temperature}℃`) // 湿度数据解析 const humRaw = (data[11] << 8) | data[10] const humidity = humRaw / 10 console.log(`湿度原始值: 0x${humRaw.toString(16).padStart(4, '0')} = ${humRaw} → ${humidity}%`) // TVOC数据解析 const tvoc = (data[13] << 8) | data[12] console.log(`TVOC原始值: 0x${tvoc.toString(16).padStart(4, '0')} = ${tvoc}`) // CO2数据解析 const co2 = (data[15] << 8) | data[14] console.log(`CO2原始值: 0x${co2.toString(16).padStart(4, '0')} = ${co2}`) // PM1数据解析 const pm1 = (data[17] << 8) | data[16] console.log(`PM1原始值: 0x${pm1.toString(16).padStart(4, '0')} = ${pm1}`) // 固定值 const pm25 = 0 const pm10 = 0 console.log('解析结果:', { temperature, humidity, tvoc, co2, pm1, pm25, pm10 }) // 计算数据状态 const temperatureStatus = this.calculateStatus(temperature, 18, 26) const humidityStatus = this.calculateStatus(humidity, 40, 60) const tvocStatus = this.calculateStatus(tvoc, 0, 50) const co2Status = this.calculateStatus(co2, 0, 1000) const pmStatus = this.calculateStatus(pm1, 0, 35) console.log('数据状态:', { temperatureStatus, humidityStatus, tvocStatus, co2Status, pmStatus }) // 更新页面数据 const newData = { temperature: temperature.toFixed(1), humidity: humidity.toFixed(1), tvoc, co2, pm1, pm25, pm10, temperatureStatus, humidityStatus, tvocStatus, co2Status, pmStatus } this.setData(newData) console.log('页面数据更新成功') // 保存历史数据 this.saveHistoryData(newData) console.log('历史数据保存成功') // 不显示解析成功提示,避免频繁弹窗干扰用户体验 // 只在控制台记录解析成功 console.log('数据解析成功,已更新到页面') console.log('数据解析完成') } catch (error) { console.error('数据解析错误:', error) console.error('错误堆栈:', error.stack) wx.showToast({ title: `解析错误: ${error.message}`, icon: 'none' }) } }, /** * 计算数据状态 * @param {number} value 数据值 * @param {number} min 正常最小值 * @param {number} max 正常最大值 * @returns {string} 状态 */ calculateStatus(value, min, max) { if (value < min || value > max) { return 'warning' } return 'normal' }, /** * 手动刷新数据 */ onRefresh() { console.log('手动刷新数据') // 检查蓝牙连接状态 if (!this.data.connected) { console.warn('蓝牙未连接,无法刷新数据') wx.showToast({ title: '请先连接蓝牙', icon: 'none' }) return } // 检查蓝牙初始化状态 if (!this.data.writeCharId) { console.warn('蓝牙初始化未完成,无法刷新数据') wx.showToast({ title: '蓝牙初始化未完成', icon: 'none' }) return } console.log('发送数据读取指令') this.setData({ refreshing: true }) wx.showLoading({ title: '读取数据中...' }) this.sendReadCmd() }, /** * 发送读取数据指令 */ sendReadCmd() { console.log('开始发送读取数据指令') // 构建读取指令 const cmd = new Uint8Array([0xA5, 0x01, 0x00, 0x5A]) console.log('发送的指令数据:', Array.from(cmd)) console.log('指令十六进制:', Array.from(cmd).map(byte => byte.toString(16).padStart(2, '0')).join('')) // 发送指令 wx.writeBLECharacteristicValue({ deviceId: this.data.deviceId, serviceId: this.data.serviceId, characteristicId: this.data.writeCharId, value: cmd.buffer, success: (res) => { console.log('发送指令成功:', res) // 取消指令发送成功的提示,因为蓝牙模块会自动发送数据 }, fail: (err) => { console.error('发送指令失败:', err) wx.hideLoading() this.setData({ refreshing: false }) wx.showToast({ title: '发送指令失败', icon: 'none' }) } }) }, /** * 启动自动刷新(已移除,因为传感器主动上报数据) */ startAutoRefresh() { console.log('传感器数据由设备主动上报,无需自动刷新') }, /** * 停止自动刷新 */ stopAutoRefresh() { console.log('停止自动刷新') }, /** * 跳转到蓝牙搜索页面 */ connectBluetooth() { console.log('准备跳转到蓝牙搜索页面') wx.navigateTo({ url: '/pages/bluetooth/bluetooth', success: () => { console.log('跳转到蓝牙搜索页面成功') }, fail: (err) => { console.error('跳转到蓝牙搜索页面失败:', err) wx.showToast({ title: '打开蓝牙搜索页面失败', icon: 'none' }) } }) }, /** * 断开蓝牙连接 */ disconnectBluetooth() { if (!this.data.connected || !this.data.deviceId) { return } console.log('断开蓝牙连接') wx.closeBLEConnection({ deviceId: this.data.deviceId, success: () => { console.log('断开连接成功') this.resetConnectionState() wx.showToast({ title: '已断开连接', icon: 'success' }) }, fail: (err) => { console.error('断开连接失败:', err) this.resetConnectionState() } }) }, /** * 重置连接状态 */ resetConnectionState() { const app = getApp() app.globalData.connectedDevice = null this.setData({ deviceId: '', deviceName: '', serviceId: '', notifyCharId: '', writeCharId: '', connected: false, lastConnectedTime: '', temperature: '--', humidity: '--', tvoc: '--', co2: '--', pm1: '--', pm25: '--', pm10: '--' }) }, /** * 页面显示时检查连接状态 */ onShow() { console.log('========== 页面显示,检查连接状态 ==========') // 获取全局数据 const app = getApp() const connectedDevice = app.globalData.connectedDevice // 检查是否有连接的设备 if (connectedDevice) { console.log('检测到已连接的设备:', connectedDevice) // 更新页面状态 this.setData({ deviceId: connectedDevice.deviceId, deviceName: connectedDevice.name, connected: true, lastConnectedTime: this.getFormattedTime() }) // 记录系统日志 this.addLog('system', `蓝牙设备已连接: ${connectedDevice.name}`, `设备ID: ${connectedDevice.deviceId}`) // 先初始化监听器,确保能接收到数据 console.log('先初始化蓝牙监听器') this.initBLEListener() // 然后获取设备服务和特征值 console.log('然后获取设备服务和特征值') this.getBLEServices() } else { console.log('未检测到已连接的设备') } }, /** * 获取格式化时间 * @returns {string} 格式化后的时间 */ getFormattedTime() { const now = new Date() return now.toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit', second: '2-digit' }) }, /** * 获取蓝牙设备服务 */ getBLEServices() { console.log('开始获取蓝牙设备服务') console.log('设备ID:', this.data.deviceId) wx.getBLEDeviceServices({ deviceId: this.data.deviceId, success: (res) => { console.log('获取设备服务成功,共找到', res.services.length, '个服务') console.log('服务列表:', JSON.stringify(res.services)) // 检查服务数量 if (res.services.length === 0) { console.error('未找到设备服务') wx.showToast({ title: '未找到设备服务', icon: 'none' }) return } // 遍历所有服务,打印详细信息 for (let i = 0; i < res.services.length; i++) { console.log(`服务 ${i}:`, res.services[i].uuid, '是否为主服务:', res.services[i].isPrimary) } // 尝试每个服务,直到找到具有通知和写入特征的服务 // 优先尝试第二个服务,因为第一个服务可能不是数据服务 let serviceIndex = 1 let triedIndexes = new Set() const tryNextService = () => { if (triedIndexes.size >= res.services.length) { console.error('所有服务都没有找到完整的特征值') wx.showToast({ title: '未找到合适的服务', icon: 'none' }) return } // 确保不重复尝试同一个服务 while (triedIndexes.has(serviceIndex)) { serviceIndex = (serviceIndex + 1) % res.services.length } triedIndexes.add(serviceIndex) const serviceId = res.services[serviceIndex].uuid console.log(`尝试服务 ${serviceIndex}:`, serviceId) // 获取当前服务的特征值 wx.getBLEDeviceCharacteristics({ deviceId: this.data.deviceId, serviceId: serviceId, success: (charRes) => { console.log(`服务 ${serviceId} 的特征值:`, JSON.stringify(charRes.characteristics)) let hasNotify = false let hasWrite = false let notifyCharId = '' let writeCharId = '' for (const char of charRes.characteristics) { if (char.properties.notify) { hasNotify = true notifyCharId = char.uuid } if (char.properties.write) { hasWrite = true writeCharId = char.uuid } } if (hasNotify && hasWrite) { console.log('========== 找到合适的服务 ==========') console.log('找到合适的服务,同时具有通知和写入特征') this.setData({ serviceId, notifyCharId, writeCharId }) console.log('使用服务:', serviceId) console.log('通知特征值:', notifyCharId) console.log('写入特征值:', writeCharId) // 记录系统日志 this.addLog('system', `使用蓝牙服务: ${serviceId}`, `通知特征: ${notifyCharId}, 写入特征: ${writeCharId}`) // 再次确认监听器已设置 console.log('再次确认监听器已设置') this.initBLEListener() // 启用通知 console.log('准备启用通知') this.enableNotification(notifyCharId) } else { console.log(`服务 ${serviceId} 缺少必要的特征值,通知:${hasNotify}, 写入:${hasWrite}`) // 尝试下一个服务 tryNextService() } }, fail: (err) => { console.error(`获取服务 ${serviceId} 的特征值失败:`, err) // 尝试下一个服务 tryNextService() } }) } // 开始尝试服务 tryNextService() }, fail: (err) => { console.error('获取服务失败:', err) wx.showToast({ title: '获取服务失败', icon: 'none' }) // 记录系统日志 this.addLog('system', '获取蓝牙服务失败', err.errMsg) } }) }, /** * 启用蓝牙通知 * @param {string} notifyCharId 通知特征值ID */ enableNotification(notifyCharId) { console.log('========== 开始启用蓝牙通知 ==========') console.log('设备ID:', this.data.deviceId) console.log('服务ID:', this.data.serviceId) console.log('通知特征值ID:', notifyCharId) wx.notifyBLECharacteristicValueChange({ deviceId: this.data.deviceId, serviceId: this.data.serviceId, characteristicId: notifyCharId, state: true, success: (res) => { console.log('========== 启用通知成功 ==========') console.log('启用通知成功:', res) wx.showToast({ title: '通知已启用,开始接收数据', icon: 'success' }) // 记录系统日志 this.addLog('system', '蓝牙通知已启用', `特征值: ${notifyCharId}`) // 发送触发命令,让传感器开始发送数据 console.log('发送触发命令,让传感器开始发送数据') wx.writeBLECharacteristicValue({ deviceId: this.data.deviceId, serviceId: this.data.serviceId, characteristicId: this.data.writeCharId, value: new Uint8Array([0xA5, 0x01, 0x00, 0x5A]).buffer, success: (res) => { console.log('发送触发命令成功:', res) wx.showToast({ title: '已触发数据发送', icon: 'success' }) }, fail: (err) => { console.error('发送触发命令失败:', err) wx.showToast({ title: '触发数据发送失败', icon: 'none' }) } }) }, fail: (err) => { console.error('========== 启用通知失败 ==========') console.error('启用通知失败:', err) wx.showToast({ title: '启用通知失败', icon: 'none' }) // 记录系统日志 this.addLog('system', '蓝牙通知启用失败', err.errMsg) } }) }, /** * 显示WiFi配置模态框 */ showWifiModal() { console.log('显示WiFi配置模态框') this.setData({ showWifiModalFlag: true }) }, /** * 处理WiFi名称输入 * @param {Event} e 输入事件 */ onWifiNameInput(e) { this.setData({ wifiName: e.detail.value }) }, /** * 处理WiFi密码输入 * @param {Event} e 输入事件 */ onWifiPasswordInput(e) { this.setData({ wifiPassword: e.detail.value }) }, /** * 取消WiFi配置 */ onWifiCancel() { console.log('取消WiFi配置') this.setData({ showWifiModalFlag: false, wifiName: '', wifiPassword: '' }) }, /** * 确认WiFi配置 */ onWifiConfirm() { console.log('确认WiFi配置') // 检查输入 if (!this.data.wifiName || !this.data.wifiPassword) { console.warn('WiFi名称或密码为空') wx.showToast({ title: 'WiFi名称和密码不能为空', icon: 'none' }) return } console.log('发送WiFi配置信息') wx.showLoading({ title: '发送WiFi信息...' }) // 构建WiFi配置指令 const wifiNameBytes = this.stringToUint8Array(this.data.wifiName) const wifiPasswordBytes = this.stringToUint8Array(this.data.wifiPassword) // 指令格式:0xAA 0x55 0x02 [name_length] [name_bytes] [password_length] [password_bytes] const cmd = new Uint8Array([ 0xAA, 0x55, 0x02, wifiNameBytes.length, ...wifiNameBytes, wifiPasswordBytes.length, ...wifiPasswordBytes ]) // 发送指令 wx.writeBLECharacteristicValue({ deviceId: this.data.deviceId, serviceId: this.data.serviceId, characteristicId: this.data.writeCharId, value: cmd.buffer, success: () => { console.log('WiFi配置发送成功') wx.hideLoading() wx.showToast({ title: 'WiFi信息已发送', icon: 'success' }) this.setData({ showWifiModalFlag: false, wifiName: '', wifiPassword: '' }) }, fail: (err) => { console.error('WiFi配置发送失败:', err) wx.hideLoading() wx.showToast({ title: '发送失败', icon: 'none' }) } }) }, /** * 显示历史记录 */ showHistory() { console.log('显示历史记录') this.setData({ showHistoryFlag: true }) }, /** * 隐藏历史记录 */ hideHistory() { console.log('隐藏历史记录') this.setData({ showHistoryFlag: false }) }, /** * 导航到日志页面 */ navigateToLogs() { console.log('导航到日志页面') wx.navigateTo({ url: '/pages/logs/logs', success: () => { console.log('跳转到日志页面成功') this.addLog('system', '导航到日志页面') }, fail: (err) => { console.error('跳转到日志页面失败:', err) this.addLog('system', '导航到日志页面失败', err.errMsg) } }) }, /** * 保存历史数据 * @param {Object} data 传感器数据 */ saveHistoryData(data) { try { // 获取现有历史数据 let historyData = wx.getStorageSync('historyData') || [] // 添加新数据 const newRecord = { time: this.getFormattedTime(), temperature: data.temperature, humidity: data.humidity, tvoc: data.tvoc, co2: data.co2, pm1: data.pm1 } historyData.unshift(newRecord) // 限制历史记录数量 if (historyData.length > 50) { historyData = historyData.slice(0, 50) } // 保存到本地存储 wx.setStorageSync('historyData', historyData) // 更新页面数据 this.setData({ historyData }) } catch (error) { console.error('保存历史数据失败:', error) } }, /** * 加载历史数据 */ loadHistoryData() { try { const historyData = wx.getStorageSync('historyData') || [] this.setData({ historyData }) } catch (error) { console.error('加载历史数据失败:', error) this.setData({ historyData: [] }) } }, /** * 将字符串转换为Uint8Array * @param {string} str 要转换的字符串 * @returns {Array} 转换后的Uint8Array */ stringToUint8Array(str) { // 在小程序环境中,TextEncoder不可用,使用兼容的字符串转换方法 const result = [] for (let i = 0; i < str.length; i++) { result.push(str.charCodeAt(i)) } return result } })