// pages/homeControl/homeContril.js Page({ /** * 页面的初始数据 */ data: { active: 0, // 当前激活的tab deviceTimeout: 10000, // 10秒超时 switch1: false, // 开关1的状态(风扇) switch2: false, // 开关2的状态(窗帘) switch3: false, // 开关3的状态(警报) switch4: false, // 开关4的状态(灯光) //单独存放传感器单独数据 temperature: 0, // 温度 humidity: 0, // 湿度 light_intensity: 0, // 光照强度 air_quality: 0, // 空气质量 curtain_state: 0, // 窗帘状态 buzzer_state: 0, // 警报状态 fan_state: 0, // 风扇状态 led_state: 0, // 灯光状态 led_power: 0, // 灯光功率 led_power_value: 50, // 灯光功率滑块当前值 // 闹钟设置数据 alarm1: { time: '07:00', enabled: false }, alarm2: { time: '08:00', enabled: false }, alarm3: { time: '09:00', enabled: false }, // 时间选择器显示状态 showTimePicker: false, currentEditingAlarm: null, // 当前正在编辑的闹钟编号 currentEditingTime: '', // 当前编辑的闹钟原时间 // 时间选择器数据 hours: Array.from({length: 24}, (_, i) => i.toString().padStart(2, '0')), minutes: Array.from({length: 60}, (_, i) => i.toString().padStart(2, '0')), timePickerValue: [7, 0], // 时间选择器当前值索引 // 时间段设置数据 dayPeriod: { start: '06:00', end: '18:00' }, nightPeriod: { start: '18:00', end: '06:00' }, // 温度阈值设置(用于自动开启降温模式) temperatureThreshold: 28, // 默认28度 // 当前编辑的时间段类型和时间点类型 currentEditingPeriod: null, // 'day' 或 'night' currentEditingPeriodType: null, // 'start' 或 'end' deviceOnline: false, // 设备在线状态 lastUpdateTime: null, // 最后更新时间 esp32Device: null, // 存储ESP32设备数据 pendingRequests: {}, // 存储待处理的控制请求 {request_id: {timestamp, controls, config, callback}} heartbeatInterval: null, // 心跳定时器 heartbeatIntervalTime: 30000, // 心跳间隔时间(毫秒),默认30秒 }, // 存储事件监听器函数引用,用于后续移除 mqttMessageHandler: null, // 存储定时器引用,用于页面卸载时清理 presenceTimer: null, /** * 生命周期函数--监听页面加载 */ onLoad(options) { this.initMQTT(); this.setupMQTTEvents(); this.startHeartbeat(); // 启动心跳 }, /** * 生命周期函数--监听页面显示 */ onShow() { // 清理之前的 presence 定时器 if (this.presenceTimer) { clearTimeout(this.presenceTimer); this.presenceTimer = null; } // 页面显示时重新启动心跳 this.startHeartbeat(); // 延迟发送在家消息,确保消息能够发送出去 this.presenceTimer = setTimeout(() => { // 检查页面是否还存在(防止页面已销毁后访问) if (this && typeof this.sendPresenceHome === 'function') { this.sendPresenceHome(); } this.presenceTimer = null; }, 100); }, /** * 生命周期函数--监听页面隐藏 */ onHide() { // 清理之前的 presence 定时器 if (this.presenceTimer) { clearTimeout(this.presenceTimer); this.presenceTimer = null; } // 页面隐藏时停止心跳 this.stopHeartbeat(); // 延迟发送离开消息,确保消息能够发送出去 this.presenceTimer = setTimeout(() => { // 检查页面是否还存在(防止页面已销毁后访问) if (this && typeof this.sendPresenceAway === 'function') { this.sendPresenceAway(); } this.presenceTimer = null; }, 100); }, initMQTT() { const app = getApp(); const mqttClient = app.globalData.mqttClient; if (!mqttClient) { console.log("[MQTT] 全局MQTT实例未初始化"); wx.showToast({ title: "MQTT未连接", icon: "none", }); return; } // 检查连接状态 const isConnected = app.globalData.mqttConnected; if (isConnected) { // 如果已连接,可以进行订阅等操作 console.log("[MQTT] HomeControl页面连接已建立"); } else { wx.showToast({ title: "MQTT未连接", icon: "none", }); } }, setupMQTTEvents() { const app = getApp(); const mqttClient = app.globalData.mqttClient; if (mqttClient) { // 定义消息处理函数 this.mqttMessageHandler = (topic, payload) => { console.log("[MQTT] HomeControl收到消息,主题:", topic); // 检查是否是ESP32设备发送的传感器数据或设备消息 let messageContent; try { // 尝试解析JSON messageContent = JSON.parse(payload.toString()); console.log("[MQTT] HomeControl收到JSON消息:", messageContent); } catch (e) { // 如果不是JSON,直接使用原始内容 messageContent = payload.toString(); console.log("[MQTT] HomeControl收到非JSON消息:", messageContent); return; // 非JSON消息不处理 } // 检查是否是设备消息 if ( messageContent.type === "device_message" && (messageContent.device_id.startsWith("esp32") || messageContent.device_type === "bedroom_controller") ) { console.log("[MQTT] 识别为ESP32设备消息:", messageContent); // 处理设备消息(仅保留核心逻辑) this.parseESP32Data(messageContent); // 示例:显示空气质量数据 // const airQuality = this.extractESP32Info("telemetry.air_quality"); // if (airQuality !== null) { // console.log(`[ESP32] 当前空气质量: ${airQuality}`); // } // this.sendControlCommand({ // light: "on", // }); } else if ( messageContent.type === "control_response" && messageContent.message_type === "control_result" ) { // 处理控制响应 console.log("[MQTT] 收到控制响应:", messageContent); this.handleControlResponse(messageContent); } else { console.log( "[MQTT] 消息不符合ESP32设备消息格式,忽略:", messageContent ); } }; // 监听消息事件 - 为HomeControl页面单独处理特定设备消息 mqttClient.on("message", this.mqttMessageHandler); console.log("[MQTT] HomeControl页面消息监听器已添加"); } else { console.log("[MQTT] MQTT客户端未初始化"); } }, /** * 发送控制指令到ESP32设备 * @param {Object} controls - 控制参数对象 * @param {Object} config - 配置参数对象(可选) * @param {Function} callback - 响应回调函数(可选) * @returns {string|null} 返回request_id,失败返回null */ sendControlCommand(controls = {}, config = {}, callback = null) { try { const app = getApp(); const mqttClient = app.globalData.mqttClient; // 检查MQTT连接状态 if (!mqttClient || !app.globalData.mqttConnected) { console.error("[MQTT] MQTT未连接,无法发送控制指令"); wx.showToast({ title: "MQTT未连接", icon: "none", }); return false; } // 从本地存储获取MQTT配置 const mqttConfig = wx.getStorageSync("mqttConfig"); if (!mqttConfig || !mqttConfig.pubTopic) { console.error("[MQTT] 未找到发布主题配置"); wx.showToast({ title: "未配置发布主题", icon: "none", }); return false; } // 获取当前设备信息 const deviceData = this.data.esp32Device; if (!deviceData) { console.error("[ESP32] 没有可用的设备数据"); wx.showToast({ title: "设备数据不可用", icon: "none", }); return false; } // 构建控制命令消息 const requestId = `req_${Date.now()}_${Math.random() .toString(36) .substr(2, 9)}`; const controlMessage = { type: "control_command", device_id: deviceData.deviceId, device_type: deviceData.deviceType, timestamp: Date.now(), message_type: "control_request", request_id: requestId, data: {}, }; // 添加控制参数 if (Object.keys(controls).length > 0) { controlMessage.data.controls = controls; } // 添加配置参数 if (Object.keys(config).length > 0) { controlMessage.data.config = config; } // 保存请求信息 const pendingRequests = this.data.pendingRequests || {}; pendingRequests[requestId] = { timestamp: Date.now(), controls: controls, config: config, callback: callback, status: "pending", }; this.setData({ pendingRequests: pendingRequests, }); // 将消息转换为JSON字符串 const messageStr = JSON.stringify(controlMessage); // 发布消息 mqttClient.publish(mqttConfig.pubTopic, messageStr, (err) => { if (!err) { console.log("[MQTT] 控制指令发送成功:", controlMessage); // wx.showToast({ // title: "指令发送成功", // icon: "success", // }); } else { console.error("[MQTT] 控制指令发送失败:", err.message); // wx.showToast({ // title: "指令发送失败", // icon: "none", // }); // 发送失败,移除请求记录 const updatedRequests = this.data.pendingRequests; delete updatedRequests[requestId]; this.setData({ pendingRequests: updatedRequests, }); } }); return requestId; } catch (error) { console.error("[ESP32] 发送控制指令时发生错误:", error); wx.showToast({ title: "发送控制指令失败", icon: "none", }); return null; } }, /** * 发布MQTT消息 * @param {string} message - 要发布的消息内容 */ publishMessage(message) { const app = getApp(); const mqttClient = app.globalData.mqttClient; // 从本地存储获取MQTT配置 const mqttConfig = wx.getStorageSync("mqttConfig"); if (!mqttConfig || !mqttConfig.pubTopic) { console.error("[MQTT] 未找到发布主题配置"); wx.showToast({ title: "未配置发布主题", icon: "none", }); return; } const topic = mqttConfig.pubTopic.trim(); if (mqttClient && app.globalData.mqttConnected) { mqttClient.publish(topic, message, (err) => { if (!err) { console.log(`[MQTT] 消息发布成功: ${topic}`); console.log(`[MQTT] 发布内容: ${message}`); wx.showToast({ title: "发布成功", icon: "success", }); } else { console.error(`[MQTT] 消息发布失败:`, err.message); wx.showToast({ title: "消息发布失败", icon: "none", }); } }); } else { wx.showToast({ title: "MQTT未连接", icon: "none", }); } }, /** * 处理ESP32设备的控制响应 * @param {Object} response - 响应消息对象 */ handleControlResponse(response) { try { // 检查响应格式 if (!response || !response.request_id) { console.error("[ESP32] 无效的控制响应:", response); return; } const requestId = response.request_id; const pendingRequests = this.data.pendingRequests; // 查找对应的请求 const request = pendingRequests[requestId]; if (!request) { console.warn(`[ESP32] 未找到对应的请求: ${requestId}`); return; } // 检查响应状态 const result = response.data?.result; const status = result?.status || "unknown"; const message = result?.message || ""; console.log(`[ESP32] 控制响应 [${requestId}]:`, { status: status, message: message, controls: request.controls, config: request.config, }); // 更新请求状态 const updatedRequests = { ...pendingRequests }; updatedRequests[requestId] = { ...request, status: status, response: response, responseTime: Date.now(), }; this.setData({ pendingRequests: updatedRequests, }); // 根据状态显示提示 if (status === "success") { wx.showToast({ title: message || "控制成功", icon: "success", duration: 2000, }); } else if (status === "error" || status === "failed") { wx.showToast({ title: message || "控制失败", icon: "none", duration: 3000, }); } // 调用回调函数(如果有) if (request.callback && typeof request.callback === "function") { try { request.callback({ success: status === "success", status: status, message: message, response: response, request: request, }); } catch (callbackError) { console.error("[ESP32] 回调函数执行错误:", callbackError); } } // 延迟移除已完成的请求(可选) setTimeout(() => { const finalRequests = this.data.pendingRequests; if (finalRequests[requestId]) { delete finalRequests[requestId]; this.setData({ pendingRequests: finalRequests, }); } }, 5000); // 5秒后移除 } catch (error) { console.error("[ESP32] 处理控制响应时发生错误:", error); } }, /** * 生命周期函数--监听页面卸载 */ onUnload() { // 清理 presence 定时器 if (this.presenceTimer) { clearTimeout(this.presenceTimer); this.presenceTimer = null; } // 停止心跳 this.stopHeartbeat(); // 延迟发送离开消息,确保消息能够发送出去 this.presenceTimer = setTimeout(() => { // 检查页面是否还存在(防止页面已销毁后访问) if (this && typeof this.sendPresenceAway === 'function') { this.sendPresenceAway(); } this.presenceTimer = null; }, 100); // 移除事件监听,避免内存泄漏 const app = getApp(); const mqttClient = app.globalData.mqttClient; if (mqttClient && this.mqttMessageHandler) { // 移除当前页面绑定的特定回调函数,不影响其他页面 mqttClient.removeListener("message", this.mqttMessageHandler); console.log("HomeControl页面MQTT消息监听器已移除"); } }, /** * 格式化数值,保留指定小数位 * @param {number|string} value - 原始数值 * @param {number} digits - 保留的小数位数 * @returns {string|null} 格式化后的字符串,无效值返回null */ formatFixed(value, digits) { if (value === null || value === undefined) return null; const num = parseFloat(value); return isNaN(num) ? null : num.toFixed(digits); }, /** * 解析ESP32设备上传的数据 * @param {Object|string} message - 接收到的消息内容(JSON对象或JSON字符串) * @returns {Object|null} 返回解析后的数据对象,解析失败返回null */ parseESP32Data(message) { try { let parsedMessage = message; // 如果是字符串,尝试解析为JSON if (typeof message === "string") { parsedMessage = JSON.parse(message); } // 验证消息格式 if (!parsedMessage || typeof parsedMessage !== "object") { console.error("[ESP32] 无效的消息格式"); return null; } // 验证必要字段 const requiredFields = [ "type", "device_id", "device_type", "timestamp", "message_type", ]; for (const field of requiredFields) { if (!(field in parsedMessage)) { console.error(`[ESP32] 缺少必要字段: ${field}`); return null; } } // 验证设备类型 if ( !parsedMessage.device_id.startsWith("esp32") && parsedMessage.device_type !== "bedroom_controller" ) { console.error("[ESP32] 非ESP32设备消息"); return null; } // 构造解析后的数据对象 const result = { // 基本信息 type: parsedMessage.type, deviceId: parsedMessage.device_id, deviceType: parsedMessage.device_type, timestamp: parsedMessage.timestamp, messageType: parsedMessage.message_type, requestId: parsedMessage.request_id || null, statusCode: parsedMessage.status_code || null, statusMessage: parsedMessage.status_message || null, // 状态信息 state: null, // 遥测数据 telemetry: null, // 控制结果 controlResult: null, // 原始数据 rawData: parsedMessage, }; // 解析data字段 if (parsedMessage.data && typeof parsedMessage.data === "object") { // 解析状态信息(动态提取,不预设字段) if (parsedMessage.data.state) { result.state = parsedMessage.data.state; } // 解析遥测数据(动态提取,不预设字段) if (parsedMessage.data.telemetry) { result.telemetry = parsedMessage.data.telemetry; } // 解析控制结果(动态提取,不预设字段) if (parsedMessage.data.result) { result.controlResult = parsedMessage.data.result; } } function formatDateTime(ts) { if (!ts) return '--'; const date = new Date( typeof ts === 'number' ? ts : ts.replace(/-/g, '/') ); if (isNaN(date.getTime())) return '--'; const pad = n => n < 10 ? '0' + n : n; return ( date.getFullYear() + '-' + pad(date.getMonth() + 1) + '-' + pad(date.getDate()) + ' ' + pad(date.getHours()) + ':' + pad(date.getMinutes()) + ':' + pad(date.getSeconds()) ); } // ===== 这里是重点:更新设备状态(添加了超时判断)===== const currentTime = Date.now(); const resultTimestamp = result.timestamp ? new Date(result.timestamp).getTime() : currentTime; const isDataFresh = (currentTime - resultTimestamp) < this.data.deviceTimeout; const backendOnline = result.state ? result.state.online : false; const isOnline = backendOnline && isDataFresh; // 更新页面数据 this.setData({ esp32Device: result, deviceOnline: isOnline, lastUpdateTime: formatDateTime(result.timestamp), }); // 添加日志便于调试 console.log("设备状态判断:", { 后端状态: backendOnline, 数据新鲜: isDataFresh, 最终状态: isOnline, 数据时间: new Date(result.timestamp).toLocaleString(), 当前时间: new Date(currentTime).toLocaleString(), 时间差: currentTime - resultTimestamp, 超时阈值: this.data.deviceTimeout }); // 更新状态 if (result.telemetry) { console.log("接收到telemetry数据:", result.telemetry); const updates = {}; const telemetry = result.telemetry; // 统一的状态转换函数 const parseState = (value) => { if (value === undefined || value === null) return undefined; return value === 'open'; }; // 风扇开关 if (telemetry.fan_state !== undefined) { const parsedValue = parseState(telemetry.fan_state); if (parsedValue !== undefined) { updates.switch1 = parsedValue; } } // 窗帘开关 if (telemetry.curtain_state !== undefined) { const parsedValue = parseState(telemetry.curtain_state); if (parsedValue !== undefined) { updates.switch2 = parsedValue; } } // 警报开关 if (telemetry.buzzer_state !== undefined) { const parsedValue = parseState(telemetry.buzzer_state); if (parsedValue !== undefined) { updates.switch3 = parsedValue; } } // 灯光开关 if (telemetry.led_state !== undefined) { const parsedValue = parseState(telemetry.led_state); if (parsedValue !== undefined) { updates.switch4 = parsedValue; } } // 如果有更新才执行setData if (Object.keys(updates).length > 0) { this.setData(updates); console.log("开关状态更新成功"); console.log("更新内容:", updates); } // 更新其他传感器数据 (应用格式化) // 温度保留1位小数 const tempVal = this.formatFixed(telemetry.temperature, 1); // 湿度保留1位小数 const humVal = this.formatFixed(telemetry.humidity, 1); // 光照强度取整 const lightVal = this.formatFixed(telemetry.light_intensity, 0); // 空气质量取整 const airVal = this.formatFixed(telemetry.air_quality, 0); // 灯光功率取整 const powerVal = this.formatFixed(telemetry.led_power, 0); this.setData({ // 使用三元运算符:如果新值不为null,则用新值;否则用旧值 (this.data.xxx) temperature: tempVal !== null ? tempVal : this.data.temperature, humidity: humVal !== null ? humVal : this.data.humidity, light_intensity: lightVal !== null ? lightVal : this.data.light_intensity, air_quality: airVal !== null ? airVal : this.data.air_quality, // 状态类保持原值 (如果上报包里没这个字段,也沿用旧值) curtain_state: telemetry.curtain_state !== undefined ? telemetry.curtain_state : this.data.curtain_state, buzzer_state: telemetry.buzzer_state !== undefined ? telemetry.buzzer_state : this.data.buzzer_state, fan_state: telemetry.fan_state !== undefined ? telemetry.fan_state : this.data.fan_state, led_state: telemetry.led_state !== undefined ? telemetry.led_state : this.data.led_state, led_power: powerVal !== null ? powerVal : this.data.led_power, // 只有当新值有效时才更新滑块,防止用户拖动时被覆盖 led_power_value: powerVal !== null ? powerVal : this.data.led_power_value, // 解析闹钟数据(仅当设备明确上报时才更新) ...(telemetry.alarm1_time !== undefined || telemetry.alarm1_enable !== undefined ? { alarm1: { time: telemetry.alarm1_time || this.data.alarm1.time, enabled: telemetry.alarm1_enable === 'on' || telemetry.alarm1_enable === true } } : {}), ...(telemetry.alarm2_time !== undefined || telemetry.alarm2_enable !== undefined ? { alarm2: { time: telemetry.alarm2_time || this.data.alarm2.time, enabled: telemetry.alarm2_enable === 'on' || telemetry.alarm2_enable === true } } : {}), ...(telemetry.alarm3_time !== undefined || telemetry.alarm3_enable !== undefined ? { alarm3: { time: telemetry.alarm3_time || this.data.alarm3.time, enabled: telemetry.alarm3_enable === 'on' || telemetry.alarm3_enable === true } } : {}), // 解析时间段数据(仅当设备明确上报时才更新) ...(telemetry.day_period_start !== undefined || telemetry.day_period_end !== undefined ? { dayPeriod: { start: telemetry.day_period_start || this.data.dayPeriod.start, end: telemetry.day_period_end || this.data.dayPeriod.end } } : {}), ...(telemetry.night_period_start !== undefined || telemetry.night_period_end !== undefined ? { nightPeriod: { start: telemetry.night_period_start || this.data.nightPeriod.start, end: telemetry.night_period_end || this.data.nightPeriod.end } } : {}), // 解析温度阈值数据(仅当设备明确上报时才更新) ...(telemetry.temperature_threshold !== undefined ? { temperatureThreshold: telemetry.temperature_threshold } : {}), }); console.log("温度:", this.data.temperature); console.log("湿度:", this.data.humidity); console.log("光照强度:", this.data.light_intensity); console.log("空气质量:", this.data.air_quality); console.log("窗帘状态:", this.data.curtain_state); console.log("警报状态:", this.data.buzzer_state); console.log("风扇状态:", this.data.fan_state); console.log("灯光状态:", this.data.led_state); console.log("灯光功率:", this.data.led_power); // 仅在设备上报闹钟数据时才打印 if (telemetry.alarm1_time !== undefined || telemetry.alarm1_enable !== undefined) { console.log("闹钟1:", this.data.alarm1); } if (telemetry.alarm2_time !== undefined || telemetry.alarm2_enable !== undefined) { console.log("闹钟2:", this.data.alarm2); } if (telemetry.alarm3_time !== undefined || telemetry.alarm3_enable !== undefined) { console.log("闹钟3:", this.data.alarm3); } // 仅在设备上报时间段数据时才打印 if (telemetry.day_period_start !== undefined || telemetry.day_period_end !== undefined) { console.log("白天时间段:", this.data.dayPeriod); } if (telemetry.night_period_start !== undefined || telemetry.night_period_end !== undefined) { console.log("晚上时间段:", this.data.nightPeriod); } // 仅在设备上报温度阈值数据时才打印 if (telemetry.temperature_threshold !== undefined) { console.log("温度阈值:", this.data.temperatureThreshold + "°C"); } // 处理警报消息 if (telemetry.alert !== undefined && telemetry.alert !== '') { this.showTemperatureAlert(telemetry.alert); console.log("收到警报消息:", telemetry.alert); } // 处理空气质量警报 if (telemetry.air_quality_alert !== undefined && telemetry.air_quality_alert !== '') { this.showAirQualityAlert(telemetry.air_quality_alert); console.log("收到空气质量警报:", telemetry.air_quality_alert); } } return result; } catch (error) { console.error("[ESP32] 数据解析错误:", error); return null; } }, /** * 从解析后的ESP32数据中提取特定信息 * @param {string} path - 数据路径,使用点号分隔,例如 "data.temperature" * @returns {any} 返回提取的值,未找到返回null */ extractESP32Info(path = "") { const esp32Data = this.data.esp32Device; if (!esp32Data) { console.warn("[ESP32] 没有可用的设备数据"); return null; } // 如果没有指定路径,返回所有数据 if (!path) { return esp32Data; } // 分割路径并遍历获取值 const keys = path.split("."); let value = esp32Data; for (const key of keys) { if (value && typeof value === "object" && key in value) { value = value[key]; } else { console.warn(`[ESP32] 路径 ${path} 未找到`); return null; } } return value; }, onChange(event) { wx.showToast({ title: `切换到${event.detail.title}`, icon: 'none', }); }, // 开关1变化事件处理 onSwitch1Change(event) { const targetState = event.detail; // 先更新页面状态,再发送指令 this.setData({ switch1: targetState }); wx.showToast({ title: `风扇状态:${targetState ? '开启' : '关闭'}`, icon: 'none', }); console.log('开关1状态:', targetState); // 发送控制命令 this.sendControlCommand({ fan_state: targetState ? 'open' : 'close' }); }, // 开关2变化事件处理 onSwitch2Change(event) { const targetState = event.detail; // 先更新页面状态,再发送指令 this.setData({ switch2: targetState }); wx.showToast({ title: `窗帘状态:${targetState ? '开启' : '关闭'}`, icon: 'none', }); console.log('开关2状态:', targetState); // 发送控制命令 this.sendControlCommand({ curtain_state: targetState ? 'open' : 'close' }); }, // 开关3变化事件处理 onSwitch3Change(event) { const targetState = event.detail; // 先更新页面状态,再发送指令 this.setData({ switch3: targetState }); wx.showToast({ title: `警报状态:${targetState ? '开启' : '关闭'}`, icon: 'none', }); console.log('开关3状态:', targetState); // 发送控制命令 this.sendControlCommand({ buzzer_state: targetState ? 'open' : 'close' }); }, // 开关4变化事件处理 onSwitch4Change(event) { const targetState = event.detail; // 先更新页面状态,再发送指令 this.setData({ switch4: targetState }); wx.showToast({ title: `灯光状态:${targetState ? '开启' : '关闭'}`, icon: 'none', }); console.log('开关4状态:', targetState); // 发送控制命令 this.sendControlCommand({ led_state: targetState ? 'open' : 'close' }); }, led_powerChange(event) { this.setData({ led_power: event.detail, }); this.sendControlCommand({ led_power: event.detail, }); }, // ==================== 闹钟设置相关方法 ==================== /** * 初始化时间选择器列数据 */ initTimePickerColumns() { const hours = []; const minutes = []; for (let i = 0; i < 24; i++) { hours.push(i.toString().padStart(2, '0')); } for (let i = 0; i < 60; i++) { minutes.push(i.toString().padStart(2, '0')); } this.setData({ timePickerColumns: [ { values: hours, defaultIndex: 7 }, { values: [':'], defaultIndex: 0 }, { values: minutes, defaultIndex: 0 } ] }); }, /** * 点击闹钟时间 - 打开时间选择器 */ onAlarmTimeClick(event) { const alarmNum = event.currentTarget.dataset.alarm; const alarmKey = `alarm${alarmNum}`; const currentTime = this.data[alarmKey].time; const [hour, minute] = currentTime.split(':'); this.setData({ showTimePicker: true, currentEditingAlarm: alarmNum, currentEditingPeriod: null, currentEditingPeriodType: null, currentEditingTime: currentTime, timePickerValue: [parseInt(hour), parseInt(minute)] }); }, /** * 点击时间段时间 - 打开时间选择器 */ onPeriodTimeClick(event) { const period = event.currentTarget.dataset.period; const type = event.currentTarget.dataset.type; const periodKey = `${period}Period`; const currentTime = this.data[periodKey][type]; const [hour, minute] = currentTime.split(':'); this.setData({ showTimePicker: true, currentEditingAlarm: null, currentEditingPeriod: period, currentEditingPeriodType: type, currentEditingTime: currentTime, timePickerValue: [parseInt(hour), parseInt(minute)] }); }, /** * 时间选择器滚动变化 */ onTimePickerChange(event) { const value = event.detail.value; this.setData({ timePickerValue: value }); }, /** * 关闭时间选择器 */ onTimePickerClose() { this.setData({ showTimePicker: false, currentEditingAlarm: null }); }, /** * 时间选择器确认 */ onTimePickerConfirm() { const [hourIndex, minuteIndex] = this.data.timePickerValue; const newTime = `${this.data.hours[hourIndex]}:${this.data.minutes[minuteIndex]}`; // 检查是闹钟还是时间段 if (this.data.currentEditingAlarm) { // 处理闹钟时间设置 const alarmNum = this.data.currentEditingAlarm; const alarmKey = `alarm${alarmNum}`; this.setData({ [`${alarmKey}.time`]: newTime, showTimePicker: false, currentEditingAlarm: null }); wx.showToast({ title: `闹钟${alarmNum}时间:${newTime}`, icon: 'none' }); console.log(`闹钟${alarmNum}时间设置:`, newTime); // 发送控制命令 this.sendAlarmControlCommand(alarmNum); } else if (this.data.currentEditingPeriod) { // 处理时间段设置 const period = this.data.currentEditingPeriod; const type = this.data.currentEditingPeriodType; const periodKey = `${period}Period`; // 检查时间段是否重合 if (!this.validatePeriodTime(period, type, newTime)) { wx.showToast({ title: '设置失败:时间段不能重合', icon: 'none', duration: 2000 }); this.setData({ showTimePicker: false, currentEditingPeriod: null, currentEditingPeriodType: null }); return; } this.setData({ [`${periodKey}.${type}`]: newTime, showTimePicker: false, currentEditingPeriod: null, currentEditingPeriodType: null }); const periodName = period === 'day' ? '白天' : '晚上'; const typeName = type === 'start' ? '开始' : '结束'; wx.showToast({ title: `${periodName}时间${typeName}:${newTime}`, icon: 'none' }); console.log(`${periodName}时间段${typeName}时间设置:`, newTime); // 提示用户需要手动下发设置 wx.showToast({ title: '请点击下方按钮下发设置', icon: 'none', duration: 1500 }); } }, /** * 验证时间段设置是否合法(不能重合) * @param {string} editingPeriod - 当前编辑的时间段 'day' 或 'night' * @param {string} editingType - 当前编辑的时间点类型 'start' 或 'end' * @param {string} newTime - 新的时间值 * @returns {boolean} 是否合法 */ validatePeriodTime(editingPeriod, editingType, newTime) { const { dayPeriod, nightPeriod } = this.data; // 构建新的时间段数据 const newDayPeriod = { ...dayPeriod }; const newNightPeriod = { ...nightPeriod }; if (editingPeriod === 'day') { newDayPeriod[editingType] = newTime; } else { newNightPeriod[editingType] = newTime; } // 将时间转换为分钟数(0-1440) const timeToMinutes = (time) => { const [h, m] = time.split(':').map(Number); return h * 60 + m; }; const dayStart = timeToMinutes(newDayPeriod.start); const dayEnd = timeToMinutes(newDayPeriod.end); const nightStart = timeToMinutes(newNightPeriod.start); const nightEnd = timeToMinutes(newNightPeriod.end); // 判断两个时间段是否重叠 // 情况1: 白天时间段不跨天(start < end) // 情况2: 白天时间段跨天(start > end) // 情况3: 夜晚时间段同理 const isInPeriod = (time, start, end) => { if (start < end) { return time >= start && time < end; } else { return time >= start || time < end; } }; // 检查白天的开始是否在夜晚时间段内 if (isInPeriod(dayStart, nightStart, nightEnd)) { return false; } // 检查白天的结束是否在夜晚时间段内 if (isInPeriod(dayEnd, nightStart, nightEnd)) { return false; } // 检查夜晚的开始是否在白天时间段内 if (isInPeriod(nightStart, dayStart, dayEnd)) { return false; } // 检查夜晚的结束是否在白天时间段内 if (isInPeriod(nightEnd, dayStart, dayEnd)) { return false; } return true; }, /** * 发送时间段控制命令 */ sendPeriodControlCommand() { const { dayPeriod, nightPeriod } = this.data; this.sendControlCommand({ day_period_start: dayPeriod.start, day_period_end: dayPeriod.end, night_period_start: nightPeriod.start, night_period_end: nightPeriod.end }); console.log('发送时间段控制命令:', { day_period_start: dayPeriod.start, day_period_end: dayPeriod.end, night_period_start: nightPeriod.start, night_period_end: nightPeriod.end }); }, /** * 温度阈值变化事件 */ onTemperatureThresholdChange(event) { const value = event.detail; this.setData({ temperatureThreshold: value }); console.log('温度阈值设置:', value + '°C'); }, /** * 发送温度阈值控制命令 */ sendTemperatureThresholdCommand() { const { temperatureThreshold } = this.data; this.sendControlCommand({ temperature_threshold: temperatureThreshold }); wx.showToast({ title: `温度阈值设置:${temperatureThreshold}°C`, icon: 'none' }); console.log('发送温度阈值控制命令:', { temperature_threshold: temperatureThreshold }); }, /** * 显示温度警报弹窗 * @param {string} message - 警报消息 */ showTemperatureAlert(message) { wx.showModal({ title: '⚠️ 温度警报', content: message, showCancel: false, confirmText: '我知道了', confirmColor: '#ee0a24', success: (res) => { if (res.confirm) { console.log('用户已确认温度警报'); } } }); }, /** * 显示空气质量警报弹窗 * @param {string} message - 警报消息 */ showAirQualityAlert(message) { wx.showModal({ title: '💨 空气质量警报', content: message, showCancel: false, confirmText: '我知道了', confirmColor: '#07c160', success: (res) => { if (res.confirm) { console.log('用户已确认空气质量警报'); } } }); }, /** * 闹钟启用状态变化 */ onAlarmEnableChange(event) { const targetState = event.detail; const alarmNum = event.currentTarget.dataset.alarm; const alarmKey = `alarm${alarmNum}`; // 更新闹钟启用状态 this.setData({ [`${alarmKey}.enabled`]: targetState }); wx.showToast({ title: `闹钟${alarmNum}:${targetState ? '启用' : '禁用'}`, icon: 'none' }); console.log(`闹钟${alarmNum}状态:`, targetState); // 发送控制命令 this.sendAlarmControlCommand(alarmNum); }, /** * 发送闹钟控制命令 * @param {number} alarmNum - 闹钟编号(1/2/3) */ sendAlarmControlCommand(alarmNum) { const alarmKey = `alarm${alarmNum}`; const alarm = this.data[alarmKey]; const controls = {}; controls[`alarm${alarmNum}_time`] = alarm.time; controls[`alarm${alarmNum}_enable`] = alarm.enabled ? 'on' : 'off'; this.sendControlCommand(controls); }, /** * 启动心跳包 */ startHeartbeat() { // 先停止已存在的心跳定时器 this.stopHeartbeat(); // 立即发送一次心跳 this.sendHeartbeat(); // 设置定时器,定期发送心跳 this.setData({ heartbeatInterval: setInterval(() => { this.sendHeartbeat(); }, this.data.heartbeatIntervalTime) }); console.log(`[心跳] 心跳已启动,间隔: ${this.data.heartbeatIntervalTime}ms`); }, /** * 停止心跳包 */ stopHeartbeat() { if (this.data.heartbeatInterval) { clearInterval(this.data.heartbeatInterval); this.setData({ heartbeatInterval: null }); console.log("[心跳] 心跳已停止"); } }, /** * 发送心跳包 */ sendHeartbeat() { const app = getApp(); const mqttClient = app.globalData.mqttClient; // 检查MQTT连接状态 if (!mqttClient || !app.globalData.mqttConnected) { console.error("[心跳] MQTT未连接,无法发送心跳"); return; } // 从本地存储获取MQTT配置 const mqttConfig = wx.getStorageSync("mqttConfig"); if (!mqttConfig || !mqttConfig.pubTopic) { console.error("[心跳] 未找到发布主题配置"); return; } // 获取当前设备信息 const deviceData = this.data.esp32Device; if (!deviceData) { console.error("[心跳] 没有可用的设备数据"); return; } // 构建心跳消息(沿用控制指令格式) const heartbeatMessage = { type: "control_command", device_id: deviceData.deviceId, device_type: deviceData.deviceType, timestamp: Date.now(), message_type: "presence_request", request_id: `presence_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`, data: { presence: "home" } }; // 将消息转换为JSON字符串 const messageStr = JSON.stringify(heartbeatMessage); // 发布心跳消息 mqttClient.publish(mqttConfig.pubTopic, messageStr, (err) => { if (!err) { console.log("[心跳] 心跳发送成功:", heartbeatMessage); } else { console.error("[心跳] 心跳发送失败:", err.message); } }); }, /** * 发送离开消息 */ sendPresenceAway() { const app = getApp(); const mqttClient = app.globalData.mqttClient; // 检查MQTT连接状态 if (!mqttClient || !app.globalData.mqttConnected) { console.error("[离开] MQTT未连接,无法发送离开消息"); return; } // 从本地存储获取MQTT配置 const mqttConfig = wx.getStorageSync("mqttConfig"); if (!mqttConfig || !mqttConfig.pubTopic) { console.error("[离开] 未找到发布主题配置"); return; } // 获取当前设备信息 const deviceData = this.data.esp32Device; if (!deviceData) { console.error("[离开] 没有可用的设备数据"); return; } // 构建离开消息(沿用控制指令格式) const awayMessage = { type: "control_command", device_id: deviceData.deviceId, device_type: deviceData.deviceType, timestamp: Date.now(), message_type: "presence_request", request_id: `presence_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`, data: { presence: "away" } }; // 将消息转换为JSON字符串 const messageStr = JSON.stringify(awayMessage); // 发布离开消息 mqttClient.publish(mqttConfig.pubTopic, messageStr, (err) => { if (!err) { console.log("[离开] 离开消息发送成功:", awayMessage); } else { console.error("[离开] 离开消息发送失败:", err.message); } }); }, /** * 发送在家消息 */ sendPresenceHome() { const app = getApp(); const mqttClient = app.globalData.mqttClient; // 检查MQTT连接状态 if (!mqttClient || !app.globalData.mqttConnected) { console.error("[在家] MQTT未连接,无法发送在家消息"); return; } // 从本地存储获取MQTT配置 const mqttConfig = wx.getStorageSync("mqttConfig"); if (!mqttConfig || !mqttConfig.pubTopic) { console.error("[在家] 未找到发布主题配置"); return; } // 获取当前设备信息 const deviceData = this.data.esp32Device; if (!deviceData) { console.error("[在家] 没有可用的设备数据"); return; } // 构建在家消息(沿用控制指令格式) const homeMessage = { type: "control_command", device_id: deviceData.deviceId, device_type: deviceData.deviceType, timestamp: Date.now(), message_type: "presence_request", request_id: `presence_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`, data: { presence: "home" } }; // 将消息转换为JSON字符串 const messageStr = JSON.stringify(homeMessage); // 发布在家消息 mqttClient.publish(mqttConfig.pubTopic, messageStr, (err) => { if (!err) { console.log("[在家] 在家消息发送成功:", homeMessage); } else { console.error("[在家] 在家消息发送失败:", err.message); } }); } });