STM32_KQZLJC/minicode-1/index/index.js

820 lines
23 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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=5CO2=0C 00=12PM1=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
}
})