mirror of
https://git.beihong.wang/wangbeihong/iot-bedroom-environment-controller.git
synced 2026-04-23 11:53:03 +08:00
1492 lines
46 KiB
JavaScript
1492 lines
46 KiB
JavaScript
// 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);
|
||
}
|
||
});
|
||
}
|
||
|
||
}); |