本文最后更新于251 天前,其中的信息可能已经过时,如有错误请发送邮件到big_fw@foxmail.com
- 1.
传感器数据获取 :
- 通过 fetch 请求从 /api/sensors 获取实时传感器数据
- 添加了错误处理机制,当网络连接失败时使用模拟数据
- 2.
设备控制功能 :
- 实现了 sendDeviceStatusUpdate 函数,可以向服务器发送设备开关状态
- 实现了 sendDeviceSettingsUpdate 函数,可以向服务器发送设备参数设置
- 3.
API端点配置 :
- 将API基础URL设置为 /api ,您可以根据实际服务器地址进行修改
系统现在可以与单片机服务器进行通信,实现: - 读取传感器数据(温度、湿度、光照、土壤湿度、CO2)
- 控制水泵出水(开关、流量、速度、定时)
- 控制风扇送风(开关、速度、定时)
您只需要确保服务器端实现以下API端点: - GET /api/sensors – 返回所有传感器数据
- POST /api/control/{device} – 接收设备控制命令
- POST /api/settings/{device} – 接收设备设置更新
后端需要实现以下API端点:
- 1.
GET /api/sensors – 返回所有传感器数据 - 2.
POST /api/control/{device} – 接收设备开关控制命令 - 3.
POST /api/settings/{device} – 接收设备参数设置
请按照这个格式实现API
// 1. 定时获取单片机数据并显示
function fetchSensorData() {
fetch('/api/sensors')
.then(res => res.json())
.then(data => {
// 假设返回数据结构如下
// { temperature, humidity, light, soilMoisture, cloudStatus, wifiStatus }
document.getElementById('center-temp-value').textContent = data.temperature + ' °C';
document.getElementById('center-hum-value').textContent = data.humidity + ' %';
document.getElementById('center-light-value').textContent = data.light + ' lux';
document.getElementById('center-soil-value').textContent = data.soilMoisture + ' %';
document.getElementById('cloud-status').textContent = data.cloudStatus;
document.getElementById('wifi-status').textContent = data.wifiStatus;
})
.catch(() => {
document.getElementById('cloud-status').textContent = '未连接';
document.getElementById('wifi-status').textContent = '异常';
});
}
setInterval(fetchSensorData, 5000);
fetchSensorData();
// 2. 控制水泵
function controlPump(action, value) {
fetch('/api/control/pump', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ action, value }) // action: 'on'/'off'/'timer', value: 定时时间(分钟)
});
}
// 3. 控制风扇
function controlFan(action, value) {
fetch('/api/control/fan', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ action, value }) // action: 'on'/'off'/'timer', value: 定时时间(分钟)
});
}
// 4. 绑定按钮事件(示例)
document.getElementById('pump-toggle').addEventListener('change', function() {
controlPump(this.checked ? 'on' : 'off');
});
document.getElementById('pump-timer-set').addEventListener('click', function() {
const min = parseInt(document.getElementById('pump-timer').value);
controlPump('timer', min);
});
document.getElementById('fan-toggle').addEventListener('change', function() {
controlFan(this.checked ? 'on' : 'off');
});
document.getElementById('fan-timer-set').addEventListener('click', function() {
const min = parseInt(document.getElementById('fan-timer').value);
controlFan('timer', min);
});
// ===============================================================================
// 农业监控系统 - 前端JavaScript控制代码
// 主要功能:传感器数据监控、设备控制、警报系统、历史数据展示和自动化规则管理
// ===============================================================================
// ===============================================================================
// API配置和全局变量定义
// ===============================================================================
// API端点 - 根据实际服务器地址修改
const API_BASE_URL = '/api';
// 全局变量 - 存储当前传感器数据
let sensorData = {
temperature: 25, // 温度(°C)
humidity: 60, // 湿度(%)
light: 800, // 光照(lux)
soilMoisture: 45, // 土壤湿度(%)
co2Level: 450 // CO₂浓度(ppm)
};
// 全局变量 - 存储设备状态
let deviceStatus = {
pump: { // 水泵状态
isOn: false, // 是否开启
flow: 5, // 流量
speed: 5, // 速度
timer: 0, // 定时器时间(分钟)
timerInterval: null // 定时器实例
},
fan: { // 风扇状态
isOn: false, // 是否开启
speed: 5, // 速度
timer: 0, // 定时器时间(分钟)
timerInterval: null // 定时器实例
}
};
// 警报阈值 - 定义各传感器正常范围
const alertThresholds = {
temperature: { min: 15, max: 35 }, // 温度范围(°C)
humidity: { min: 40, max: 80 }, // 湿度范围(%)
light: { min: 200, max: 2000 }, // 光照范围(lux)
soilMoisture: { min: 30, max: 70 }, // 土壤湿度范围(%)
co2Level: { min: 300, max: 800 } // CO₂浓度范围(ppm)
};
// 警报历史记录数组
let alertHistory = [];
// 历史数据存储 - 用于图表展示
let historyData = {
temperature: [], // 温度历史数据
humidity: [], // 湿度历史数据
light: [], // 光照历史数据
soilMoisture: [], // 土壤湿度历史数据
co2Level: [] // CO₂浓度历史数据
};
// 自动化规则数组
let automationRules = [];
// DOM元素引用 - 将在初始化函数中赋值
let temperatureElement, humidityElement, lightElement, soilMoistureElement, co2LevelElement;
let pumpToggle, pumpStatus, pumpFlow, pumpFlowValue, pumpSpeed, pumpSpeedValue;
let pumpTimer, pumpTimerSet, pumpTimerCancel, pumpTimerStatus;
let fanToggle, fanStatus, fanSpeed, fanSpeedValue;
let fanTimer, fanTimerSet, fanTimerCancel, fanTimerStatus;
let alertContainer, alertContent;
let historyChart;
let ruleModal, addRuleBtn, closeModal, ruleForm, rulesContainer, emptyRulesMessage;
// ===============================================================================
// 初始化和核心功能函数
// ===============================================================================
/**
* 初始化函数 - 页面加载时执行
* 功能:获取DOM元素引用、添加事件监听器、初始化图表、加载规则、启动数据轮询
*/
function init() {
// 获取传感器显示元素引用
temperatureElement = document.getElementById('temperature');
humidityElement = document.getElementById('humidity');
lightElement = document.getElementById('light');
soilMoistureElement = document.getElementById('soil-moisture');
co2LevelElement = document.getElementById('co2-level');
// 获取水泵控制元素引用
pumpToggle = document.getElementById('pump-toggle');
pumpStatus = document.getElementById('pump-status');
pumpFlow = document.getElementById('pump-flow');
pumpFlowValue = document.getElementById('pump-flow-value');
pumpSpeed = document.getElementById('pump-speed');
pumpSpeedValue = document.getElementById('pump-speed-value');
pumpTimer = document.getElementById('pump-timer');
pumpTimerSet = document.getElementById('pump-timer-set');
pumpTimerCancel = document.getElementById('pump-timer-cancel');
pumpTimerStatus = document.getElementById('pump-timer-status');
// 获取风扇控制元素引用
fanToggle = document.getElementById('fan-toggle');
fanStatus = document.getElementById('fan-status');
fanSpeed = document.getElementById('fan-speed');
fanSpeedValue = document.getElementById('fan-speed-value');
fanTimer = document.getElementById('fan-timer');
fanTimerSet = document.getElementById('fan-timer-set');
fanTimerCancel = document.getElementById('fan-timer-cancel');
fanTimerStatus = document.getElementById('fan-timer-status');
// 获取警报元素引用
alertContainer = document.getElementById('alert-container');
alertContent = document.getElementById('alert-content');
// 获取自动化规则元素引用
ruleModal = document.getElementById('rule-modal');
addRuleBtn = document.getElementById('add-rule-btn');
closeModal = document.querySelector('.close-modal');
ruleForm = document.getElementById('rule-form');
rulesContainer = document.getElementById('rules-container');
emptyRulesMessage = document.getElementById('empty-rules-message');
// 添加事件监听器 - 水泵控制
pumpToggle.addEventListener('change', () => toggleDevice('pump'));
pumpFlow.addEventListener('input', () => updateDeviceSettings('pump', 'flow'));
pumpSpeed.addEventListener('input', () => updateDeviceSettings('pump', 'speed'));
pumpTimerSet.addEventListener('click', () => setDeviceTimer('pump'));
pumpTimerCancel.addEventListener('click', () => cancelDeviceTimer('pump'));
// 添加事件监听器 - 风扇控制
fanToggle.addEventListener('change', () => toggleDevice('fan'));
fanSpeed.addEventListener('input', () => updateDeviceSettings('fan', 'speed'));
fanTimerSet.addEventListener('click', () => setDeviceTimer('fan'));
fanTimerCancel.addEventListener('click', () => cancelDeviceTimer('fan'));
// 自动化规则事件监听
addRuleBtn.addEventListener('click', openRuleModal);
closeModal.addEventListener('click', closeRuleModal);
ruleForm.addEventListener('submit', saveRule);
document.getElementById('cancel-rule').addEventListener('click', closeRuleModal);
// 触发条件和执行动作联动
document.getElementById('action-device').addEventListener('change', updateActionOperations);
document.getElementById('action-operation').addEventListener('change', toggleActionValue);
// 初始化图表
initHistoryChart();
// 加载本地存储的规则
loadRules();
// 开始数据轮询
startDataPolling();
}
/**
* 更新传感器数据显示
* 功能:更新页面上各传感器数值显示、更新样式、检查警报、检查自动化规则、更新历史数据
*/
function updateSensorDisplay() {
// 更新传感器数值显示
temperatureElement.textContent = `${sensorData.temperature} °C`;
humidityElement.textContent = `${sensorData.humidity} %`;
lightElement.textContent = `${sensorData.light} lux`;
soilMoistureElement.textContent = `${sensorData.soilMoisture} %`;
co2LevelElement.textContent = `${sensorData.co2Level} ppm`;
// 更新传感器样式 - 超出阈值时添加警报样式
updateSensorStyle('temperature', sensorData.temperature, temperatureElement);
updateSensorStyle('humidity', sensorData.humidity, humidityElement);
updateSensorStyle('light', sensorData.light, lightElement);
updateSensorStyle('soilMoisture', sensorData.soilMoisture, soilMoistureElement);
updateSensorStyle('co2Level', sensorData.co2Level, co2LevelElement);
// 检查警报
checkAlerts();
// 检查自动化规则
checkAutomationRules();
// 更新历史数据
updateHistoryData();
}
/**
* 更新传感器样式
* @param {string} sensorType - 传感器类型
* @param {number} value - 传感器当前值
* @param {HTMLElement} element - 对应的DOM元素
* 功能:根据传感器数值是否在阈值范围内更新元素样式
*/
function updateSensorStyle(sensorType, value, element) {
const thresholds = alertThresholds[sensorType];
if (value < thresholds.min || value > thresholds.max) {
element.classList.add('alert');
} else {
element.classList.remove('alert');
}
}
/**
* 切换设备开关状态
* @param {string} device - 设备类型('pump'或'fan')
* 功能:切换设备开关状态并更新显示,同时发送更新到服务器
*/
function toggleDevice(device) {
// 获取设备开关状态
const isOn = device === 'pump' ? pumpToggle.checked : fanToggle.checked;
deviceStatus[device].isOn = isOn;
// 更新设备状态显示
if (device === 'pump') {
pumpStatus.textContent = isOn ? '开启' : '关闭';
} else {
fanStatus.textContent = isOn ? '开启' : '关闭';
}
// 发送设备状态更新到服务器
sendDeviceStatusUpdate(device);
}
/**
* 更新设备设置
* @param {string} device - 设备类型('pump'或'fan')
* @param {string} setting - 设置类型('flow'或'speed')
* 功能:更新设备的流量或速度设置,并发送更新到服务器
*/
function updateDeviceSettings(device, setting) {
if (device === 'pump') {
if (setting === 'flow') {
deviceStatus.pump.flow = pumpFlow.value;
pumpFlowValue.textContent = pumpFlow.value;
} else if (setting === 'speed') {
deviceStatus.pump.speed = pumpSpeed.value;
pumpSpeedValue.textContent = pumpSpeed.value;
}
} else if (device === 'fan') {
deviceStatus.fan.speed = fanSpeed.value;
fanSpeedValue.textContent = fanSpeed.value;
}
// 发送设备设置更新到服务器
sendDeviceSettingsUpdate(device);
}
/**
* 设置设备定时器
* @param {string} device - 设备类型('pump'或'fan')
* 功能:设置设备在指定时间后自动关闭
*/
function setDeviceTimer(device) {
// 获取定时时间值
const timerValue = device === 'pump' ? parseInt(pumpTimer.value) : parseInt(fanTimer.value);
if (timerValue <= 0) {
alert('请输入有效的定时时间');
return;
}
// 清除现有定时器
cancelDeviceTimer(device);
// 设置新定时器
deviceStatus[device].timer = timerValue;
const timerStatus = device === 'pump' ? pumpTimerStatus : fanTimerStatus;
timerStatus.textContent = `定时: ${timerValue} 分钟后关闭`;
// 启动定时器
deviceStatus[device].timerInterval = setTimeout(() => {
// 时间到后关闭设备
if (device === 'pump') {
pumpToggle.checked = false;
} else {
fanToggle.checked = false;
}
toggleDevice(device);
cancelDeviceTimer(device);
}, timerValue * 60 * 1000); // 转换为毫秒
}
/**
* 取消设备定时器
* @param {string} device - 设备类型('pump'或'fan')
* 功能:取消设备的定时设置
*/
function cancelDeviceTimer(device) {
if (deviceStatus[device].timerInterval) {
clearTimeout(deviceStatus[device].timerInterval);
deviceStatus[device].timerInterval = null;
deviceStatus[device].timer = 0;
// 更新定时器状态显示
const timerStatus = device === 'pump' ? pumpTimerStatus : fanTimerStatus;
timerStatus.textContent = '未设置定时';
// 重置定时器输入值
if (device === 'pump') {
pumpTimer.value = 0;
} else {
fanTimer.value = 0;
}
}
}
// ===============================================================================
// 警报系统相关函数
// ===============================================================================
/**
* 检查所有传感器警报
* 功能:检查各传感器数值是否超出阈值并创建相应警报
*/
function checkAlerts() {
checkSensorAlert('temperature', sensorData.temperature, '温度');
checkSensorAlert('humidity', sensorData.humidity, '湿度');
checkSensorAlert('light', sensorData.light, '光照');
checkSensorAlert('soilMoisture', sensorData.soilMoisture, '土壤湿度');
checkSensorAlert('co2Level', sensorData.co2Level, 'CO₂浓度');
// 更新警报显示
updateAlertDisplay();
}
/**
* 检查单个传感器警报
* @param {string} sensorType - 传感器类型
* @param {number} value - 传感器当前值
* @param {string} sensorName - 传感器中文名称
* 功能:检查传感器数值是否超出阈值并创建相应警报
*/
function checkSensorAlert(sensorType, value, sensorName) {
const thresholds = alertThresholds[sensorType];
if (value < thresholds.min) {
createAlert('warning', `${sensorName}过低: ${value}`);
} else if (value > thresholds.max) {
createAlert('warning', `${sensorName}过高: ${value}`);
}
}
/**
* 创建警报
* @param {string} type - 警报类型('warning'、'info'或'success')
* @param {string} message - 警报消息内容
* 功能:创建并存储警报信息,避免重复警报
*/
function createAlert(type, message) {
// 检查是否已存在相同警报
const existingAlert = alertHistory.find(alert => alert.message === message);
if (!existingAlert) {
const alert = {
type,
message,
timestamp: new Date()
};
// 添加到警报历史(添加到开头)
alertHistory.unshift(alert);
// 限制警报历史数量,最多保留10条
if (alertHistory.length > 10) {
alertHistory.pop();
}
}
}
/**
* 更新警报显示
* 功能:将警报历史显示在页面上
*/
function updateAlertDisplay() {
if (alertHistory.length === 0) {
alertContent.innerHTML = '<p>当前无异常警报</p>';
return;
}
let alertHTML = '';
alertHistory.forEach(alert => {
// 根据警报类型选择图标
const icon = alert.type === 'warning' ? 'exclamation-triangle' : 'info-circle';
const time = formatAlertTime(alert.timestamp);
// 构建警报HTML
alertHTML += `
<div class="alert-item ${alert.type}">
<i class="fas fa-${icon}"></i>
<span>${alert.message}</span>
<span class="alert-timestamp">${time}</span>
</div>
`;
});
alertContent.innerHTML = alertHTML;
}
/**
* 格式化警报时间
* @param {Date} timestamp - 警报发生时间
* @returns {string} 格式化后的时间字符串
* 功能:根据时间差返回友好的时间表示
*/
function formatAlertTime(timestamp) {
const now = new Date();
const diff = Math.floor((now - timestamp) / 1000); // 秒数差
if (diff < 60) {
return '刚刚';
} else if (diff < 3600) {
return `${Math.floor(diff / 60)}分钟前`;
} else if (diff < 86400) {
return `${Math.floor(diff / 3600)}小时前`;
} else {
return `${Math.floor(diff / 86400)}天前`;
}
}
// ===============================================================================
// 历史数据图表相关函数
// ===============================================================================
/**
* 初始化历史图表
* 功能:创建Chart.js图表实例并添加事件监听
*/
function initHistoryChart() {
const ctx = document.getElementById('history-chart').getContext('2d');
historyChart = new Chart(ctx, {
type: 'line',
data: {
labels: [],
datasets: [{
label: '温度',
data: [],
borderColor: '#ff7675',
backgroundColor: 'rgba(255, 118, 117, 0.1)',
borderWidth: 2,
tension: 0.3,
fill: true
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
y: {
beginAtZero: false
}
}
}
});
// 添加传感器选择事件监听
document.getElementById('chart-sensor-select').addEventListener('change', updateChartData);
document.getElementById('chart-time-select').addEventListener('change', updateChartData);
// 初始更新图表数据
updateChartData();
}
/**
* 更新图表数据
* 功能:根据选择的传感器和时间范围更新图表数据
*/
function updateChartData() {
const sensorType = document.getElementById('chart-sensor-select').value;
const timeRange = parseInt(document.getElementById('chart-time-select').value);
// 获取传感器名称
const sensorName = getSensorName(sensorType);
// 准备数据
const now = new Date();
const labels = [];
const data = [];
// 如果历史数据不足,生成模拟数据
if (historyData[sensorType].length < timeRange * 12) { // 假设每5分钟一个数据点
// 生成过去timeRange小时的模拟数据
for (let i = timeRange * 12; i >= 0; i--) {
const time = new Date(now - i * 5 * 60 * 1000);
labels.push(time.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }));
// 根据传感器类型生成合理范围内的随机数据
let value;
switch (sensorType) {
case 'temperature':
value = 20 + Math.random() * 10;
break;
case 'humidity':
value = 50 + Math.random() * 30;
break;
case 'light':
value = 500 + Math.random() * 1000;
break;
case 'soilMoisture':
value = 40 + Math.random() * 30;
break;
case 'co2Level':
value = 400 + Math.random() * 300;
break;
}
data.push(value);
}
} else {
// 使用实际历史数据
const relevantData = historyData[sensorType].slice(0, timeRange * 12);
relevantData.forEach(item => {
labels.push(new Date(item.timestamp).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }));
data.push(item.value);
});
}
// 更新图表数据
historyChart.data.labels = labels;
historyChart.data.datasets[0].label = sensorName;
historyChart.data.datasets[0].data = data;
// 根据传感器类型设置颜色
switch (sensorType) {
case 'temperature':
historyChart.data.datasets[0].borderColor = '#ff7675';
historyChart.data.datasets[0].backgroundColor = 'rgba(255, 118, 117, 0.1)';
break;
case 'humidity':
historyChart.data.datasets[0].borderColor = '#74b9ff';
historyChart.data.datasets[0].backgroundColor = 'rgba(116, 185, 255, 0.1)';
break;
case 'light':
historyChart.data.datasets[0].borderColor = '#ffeaa7';
historyChart.data.datasets[0].backgroundColor = 'rgba(255, 234, 167, 0.1)';
break;
case 'soilMoisture':
historyChart.data.datasets[0].borderColor = '#55efc4';
historyChart.data.datasets[0].backgroundColor = 'rgba(85, 239, 196, 0.1)';
break;
case 'co2Level':
historyChart.data.datasets[0].borderColor = '#a29bfe';
historyChart.data.datasets[0].backgroundColor = 'rgba(162, 155, 254, 0.1)';
break;
}
// 更新图表显示
historyChart.update();
}
/**
* 获取传感器名称
* @param {string} sensorType - 传感器类型
* @returns {string} 格式化的传感器名称(包含单位)
* 功能:根据传感器类型返回友好的中文名称和单位
*/
function getSensorName(sensorType) {
switch (sensorType) {
case 'temperature': return '温度 (°C)';
case 'humidity': return '湿度 (%)';
case 'light': return '光照 (lux)';
case 'soilMoisture': return '土壤湿度 (%)';
case 'co2Level': return 'CO₂浓度 (ppm)';
default: return '';
}
}
/**
* 更新历史数据
* 功能:将当前传感器数据添加到历史数据中,并限制历史数据量
*/
function updateHistoryData() {
const now = new Date();
// 添加当前数据到历史记录
Object.keys(sensorData).forEach(key => {
historyData[key].unshift({
value: sensorData[key],
timestamp: now.getTime()
});
// 限制历史数据量(保留24小时的数据,假设每5分钟一个数据点)
if (historyData[key].length > 24 * 12) {
historyData[key].pop();
}
});
}
// ===============================================================================
// 自动化规则相关函数
// ===============================================================================
/**
* 打开规则模态框
* 功能:显示添加自动化规则的模态框
*/
function openRuleModal() {
ruleModal.style.display = 'flex';
document.getElementById('modal-title').textContent = '添加自动化规则';
ruleForm.reset();
// 重置表单ID
ruleForm.dataset.ruleId = '';
}
/**
* 关闭规则模态框
* 功能:隐藏规则模态框
*/
function closeRuleModal() {
ruleModal.style.display = 'none';
}
/**
* 保存规则
* @param {Event} e - 表单提交事件
* 功能:保存新规则或更新现有规则
*/
function saveRule(e) {
e.preventDefault();
// 获取表单数据
const ruleName = document.getElementById('rule-name').value;
const triggerSensor = document.getElementById('trigger-sensor').value;
const triggerCondition = document.getElementById('trigger-condition').value;
const triggerValue = parseFloat(document.getElementById('trigger-value').value);
const actionDevice = document.getElementById('action-device').value;
const actionOperation = document.getElementById('action-operation').value;
const actionValue = document.getElementById('action-value').value ? parseInt(document.getElementById('action-value').value) : null;
const ruleId = ruleForm.dataset.ruleId || Date.now().toString();
// 创建规则对象
const rule = {
id: ruleId,
name: ruleName,
trigger: {
sensor: triggerSensor,
condition: triggerCondition,
value: triggerValue
},
action: {
device: actionDevice,
operation: actionOperation,
value: actionValue
}
};
// 检查是否是编辑现有规则
const existingRuleIndex = automationRules.findIndex(r => r.id === ruleId);
if (existingRuleIndex !== -1) {
// 更新现有规则
automationRules[existingRuleIndex] = rule;
} else {
// 添加新规则
automationRules.push(rule);
}
// 保存到本地存储
saveRulesToLocalStorage();
// 更新规则显示
renderRules();
// 关闭模态框
closeRuleModal();
}
/**
* 渲染规则列表
* 功能:将所有自动化规则显示在页面上
*/
function renderRules() {
if (automationRules.length === 0) {
emptyRulesMessage.style.display = 'block';
rulesContainer.innerHTML = '';
return;
}
emptyRulesMessage.style.display = 'none';
let rulesHTML = '';
automationRules.forEach(rule => {
const triggerText = getTriggerText(rule.trigger);
const actionText = getActionText(rule.action);
// 构建规则卡片HTML
rulesHTML += `
<div class="rule-card" data-rule-id="${rule.id}">
<div class="rule-info">
<div class="rule-name">${rule.name}</div>
<div class="rule-condition">当 ${triggerText}</div>
<div class="rule-action">执行 ${actionText}</div>
</div>
<div class="rule-controls">
<button class="edit-rule" onclick="editRule('${rule.id}')"><i class="fas fa-edit"></i></button>
<button class="delete-rule" onclick="deleteRule('${rule.id}')"><i class="fas fa-trash"></i></button>
</div>
</div>
`;
});
rulesContainer.innerHTML = rulesHTML;
}
/**
* 获取触发条件文本
* @param {Object} trigger - 触发条件对象
* @returns {string} 格式化的触发条件文本
* 功能:将触发条件对象转换为友好的中文描述
*/
function getTriggerText(trigger) {
const sensorNames = {
temperature: '温度',
humidity: '湿度',
light: '光照',
soilMoisture: '土壤湿度',
co2Level: 'CO₂浓度'
};
const conditionSymbols = {
gt: '大于',
lt: '小于',
eq: '等于'
};
const units = {
temperature: '°C',
humidity: '%',
light: 'lux',
soilMoisture: '%',
co2Level: 'ppm'
};
return `${sensorNames[trigger.sensor]} ${conditionSymbols[trigger.condition]} ${trigger.value}${units[trigger.sensor]}`;
}
/**
* 获取执行动作文本
* @param {Object} action - 动作对象
* @returns {string} 格式化的动作文本
* 功能:将动作对象转换为友好的中文描述
*/
function getActionText(action) {
const deviceNames = {
pump: '水泵',
fan: '风扇'
};
const operationNames = {
turnOn: '打开',
turnOff: '关闭',
setSpeed: '设置速度为'
};
let text = `${deviceNames[action.device]} ${operationNames[action.operation]}`;
if (action.operation === 'setSpeed' && action.value !== null) {
text += ` ${action.value}`;
}
return text;
}
/**
* 编辑规则
* @param {string} ruleId - 规则ID
* 功能:打开编辑规则模态框并填充现有规则数据
*/
function editRule(ruleId) {
const rule = automationRules.find(r => r.id === ruleId);
if (!rule) return;
// 填充表单数据
document.getElementById('modal-title').textContent = '编辑自动化规则';
document.getElementById('rule-name').value = rule.name;
document.getElementById('trigger-sensor').value = rule.trigger.sensor;
document.getElementById('trigger-condition').value = rule.trigger.condition;
document.getElementById('trigger-value').value = rule.trigger.value;
document.getElementById('action-device').value = rule.action.device;
document.getElementById('action-operation').value = rule.action.operation;
// 设置速度值输入框
if (rule.action.value !== null) {
document.getElementById('action-value').value = rule.action.value;
document.getElementById('action-value').style.display = 'block';
} else {
document.getElementById('action-value').style.display = 'none';
}
// 设置表单ID
ruleForm.dataset.ruleId = ruleId;
// 打开模态框
openRuleModal();
}
/**
* 删除规则
* @param {string} ruleId - 规则ID
* 功能:从列表中删除指定规则
*/
function deleteRule(ruleId) {
if (confirm('确定要删除此规则吗?')) {
automationRules = automationRules.filter(rule => rule.id !== ruleId);
saveRulesToLocalStorage();
renderRules();
}
}
/**
* 更新执行动作操作选项
* 功能:根据选择的设备更新可用的操作选项
*/
function updateActionOperations() {
const actionDevice = document.getElementById('action-device').value;
const actionOperation = document.getElementById('action-operation');
// 清空现有选项
actionOperation.innerHTML = '';
// 添加通用选项
actionOperation.appendChild(new Option('打开', 'turnOn'));
actionOperation.appendChild(new Option('关闭', 'turnOff'));
// 添加设备特定选项
if (actionDevice === 'pump' || actionDevice === 'fan') {
actionOperation.appendChild(new Option('设置速度', 'setSpeed'));
}
// 触发变更事件
toggleActionValue();
}
/**
* 切换动作值输入框显示
* 功能:根据选择的操作显示或隐藏速度值输入框
*/
function toggleActionValue() {
const actionOperation = document.getElementById('action-operation').value;
const actionValue = document.getElementById('action-value');
if (actionOperation === 'setSpeed') {
actionValue.style.display = 'block';
actionValue.required = true;
} else {
actionValue.style.display = 'none';
actionValue.required = false;
}
}
/**
* 检查自动化规则
* 功能:检查所有自动化规则是否满足触发条件,如果满足则执行相应动作
*/
function checkAutomationRules() {
automationRules.forEach(rule => {
const sensorValue = sensorData[rule.trigger.sensor];
let conditionMet = false;
// 检查触发条件是否满足
switch (rule.trigger.condition) {
case 'gt':
conditionMet = sensorValue > rule.trigger.value;
break;
case 'lt':
conditionMet = sensorValue < rule.trigger.value;
break;
case 'eq':
conditionMet = Math.abs(sensorValue - rule.trigger.value) < 0.1; // 近似相等
break;
}
if (conditionMet) {
// 条件满足,执行规则动作
executeRuleAction(rule);
}
});
}
/**
* 执行规则动作
* @param {Object} rule - 规则对象
* 功能:根据规则执行相应的设备操作
*/
function executeRuleAction(rule) {
const { device, operation, value } = rule.action;
switch (operation) {
case 'turnOn':
// 打开设备
if (!deviceStatus[device].isOn) {
if (device === 'pump') {
pumpToggle.checked = true;
} else {
fanToggle.checked = true;
}
toggleDevice(device);
createAlert('info', `自动化规则 "${rule.name}" 已打开${device === 'pump' ? '水泵' : '风扇'}`);
}
break;
case 'turnOff':
// 关闭设备
if (deviceStatus[device].isOn) {
if (device === 'pump') {
pumpToggle.checked = false;
} else {
fanToggle.checked = false;
}
toggleDevice(device);
createAlert('info', `自动化规则 "${rule.name}" 已关闭${device === 'pump' ? '水泵' : '风扇'}`);
}
break;
case 'setSpeed':
// 设置设备速度
if (device === 'pump') {
pumpSpeed.value = value;
updateDeviceSettings('pump', 'speed');
createAlert('info', `自动化规则 "${rule.name}" 已设置水泵速度为 ${value}`);
} else {
fanSpeed.value = value;
updateDeviceSettings('fan', 'speed');
createAlert('info', `自动化规则 "${rule.name}" 已设置风扇速度为 ${value}`);
}
break;
}
}
/**
* 保存规则到本地存储
* 功能:将自动化规则保存到浏览器本地存储中
*/
function saveRulesToLocalStorage() {
localStorage.setItem('automationRules', JSON.stringify(automationRules));
}
/**
* 从本地存储加载规则
* 功能:从浏览器本地存储中加载自动化规则
*/
function loadRules() {
const savedRules = localStorage.getItem('automationRules');
if (savedRules) {
automationRules = JSON.parse(savedRules);
renderRules();
}
}
// ===============================================================================
// 服务器通信相关函数
// ===============================================================================
/**
* 发送设备状态更新到服务器
* @param {string} device - 设备类型
* 功能:将设备开关状态更新发送到服务器
*/
function sendDeviceStatusUpdate(device) {
const deviceData = {
device: device,
status: deviceStatus[device].isOn,
settings: {
speed: deviceStatus[device].speed,
flow: device === 'pump' ? deviceStatus[device].flow : undefined
}
};
fetch(`${API_BASE_URL}/control/${device}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(deviceData)
})
.then(response => {
if (!response.ok) {
throw new Error('网络响应异常');
}
return response.json();
})
.then(data => {
console.log(`${device}状态更新成功:`, data);
createAlert('success', `${device === 'pump' ? '水泵' : '风扇'}状态已更新`);
})
.catch(error => {
console.error(`${device}状态更新失败:`, error);
createAlert('error', `${device === 'pump' ? '水泵' : '风扇'}状态更新失败,请检查网络连接`);
});
}
/**
* 发送设备设置更新到服务器
* @param {string} device - 设备类型
* 功能:将设备设置(如速度、流量等)更新发送到服务器
*/
function sendDeviceSettingsUpdate(device) {
const settingsData = {
device: device,
settings: {
speed: deviceStatus[device].speed,
flow: device === 'pump' ? deviceStatus[device].flow : undefined,
timer: deviceStatus[device].timer
}
};
fetch(`${API_BASE_URL}/settings/${device}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(settingsData)
})
.then(response => {
if (!response.ok) {
throw new Error('网络响应异常');
}
return response.json();
})
.then(data => {
console.log(`${device}设置更新成功:`, data);
createAlert('success', `${device === 'pump' ? '水泵' : '风扇'}设置已更新`);
})
.catch(error => {
console.error(`${device}设置更新失败:`, error);
createAlert('error', `${device === 'pump' ? '水泵' : '风扇'}设置更新失败,请检查网络连接`);
});
}
/**
* 获取传感器数据
* 功能:从服务器获取最新的传感器数据
*/
function fetchSensorData() {
fetch(`${API_BASE_URL}/sensors`)
.then(response => {
if (!response.ok) {
throw new Error('网络响应异常');
}
return response.json();
})
.then(data => {
// 更新传感器数据
sensorData = data;
// 更新显示
updateSensorDisplay();
// 更新历史数据
updateHistoryData();
// 检查警报
checkAlerts();
// 检查自动化规则
checkAutomationRules();
})
.catch(error => {
console.error('获取传感器数据失败:', error);
createAlert('error', '获取传感器数据失败,请检查网络连接');
// 如果无法连接服务器,使用模拟数据
simulateSensorData();
});
}
/**
* 模拟传感器数据
* 功能:当无法连接服务器时,生成模拟的传感器数据波动
*/
function simulateSensorData() {
// 模拟数据波动
sensorData.temperature = Math.round((sensorData.temperature + (Math.random() * 2 - 1)) * 10) / 10;
sensorData.humidity = Math.round((sensorData.humidity + (Math.random() * 4 - 2)) * 10) / 10;
sensorData.light = Math.round(sensorData.light + (Math.random() * 100 - 50));
sensorData.soilMoisture = Math.round((sensorData.soilMoisture + (Math.random() * 3 - 1.5)) * 10) / 10;
sensorData.co2Level = Math.round(sensorData.co2Level + (Math.random() * 20 - 10));
// 确保数据在合理范围内
sensorData.temperature = Math.max(10, Math.min(40, sensorData.temperature));
sensorData.humidity = Math.max(20, Math.min(90, sensorData.humidity));
sensorData.light = Math.max(100, Math.min(2500, sensorData.light));
sensorData.soilMoisture = Math.max(20, Math.min(80, sensorData.soilMoisture));
sensorData.co2Level = Math.max(300, Math.min(1000, sensorData.co2Level));
// 更新显示
updateSensorDisplay();
}
/**
* 开始数据轮询
* 功能:启动定期从服务器获取数据的轮询机制
*/
function startDataPolling() {
// 初始更新
fetchSensorData();
// 设置定时更新(每5秒更新一次)
setInterval(fetchSensorData, 5000);
}
// ===============================================================================
// 页面加载完成后初始化
// ===============================================================================
// 页面加载完成后执行初始化函数
window.addEventListener('DOMContentLoaded', init);
// 添加全局函数以便HTML中的onclick调用
window.editRule = editRule;
window.deleteRule = deleteRule;
这份详细注释涵盖了整个农业监控系统JavaScript代码的所有功能和逻辑。主要包括以下几个部分:
- API配置和全局变量定义:包括服务器地址、传感器数据、设备状态、警报阈值等全局变量
- 初始化和核心功能函数:页面初始化、传感器数据更新、设备控制等核心功能
- 警报系统相关函数:警报检测、创建、显示等功能
- 历史数据图表相关函数:Chart.js图表初始化、数据更新等功能
- 自动化规则相关函数:规则创建、编辑、删除、执行等功能
- 服务器通信相关函数:与后端API通信,发送设备控制指令和获取传感器数据
- 页面加载初始化:设置DOMContentLoaded事件监听器,确保页面加载完成后执行初始化








参赛团队应围绕智慧农业的实际需求,设计并实现基于单片机的智慧农业系统。如开发智能温室控制系统,采集温室内温湿度、光照强度等数据,结合无线通信技术将数据上传至云平台,供远程监控温室环境。