Files
2026-02-07 23:14:57 +08:00

1492 lines
46 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.
// 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);
}
});
}
});