智能农业监控系统
本文最后更新于251 天前,其中的信息可能已经过时,如有错误请发送邮件到big_fw@foxmail.com
  1. 1.
    传感器数据获取 :
  • 通过 fetch 请求从 /api/sensors 获取实时传感器数据
  • 添加了错误处理机制,当网络连接失败时使用模拟数据
  1. 2.
    设备控制功能 :
  • 实现了 sendDeviceStatusUpdate 函数,可以向服务器发送设备开关状态
  • 实现了 sendDeviceSettingsUpdate 函数,可以向服务器发送设备参数设置
  1. 3.
    API端点配置 :
  • 将API基础URL设置为 /api ,您可以根据实际服务器地址进行修改
    系统现在可以与单片机服务器进行通信,实现:
  • 读取传感器数据(温度、湿度、光照、土壤湿度、CO2)
  • 控制水泵出水(开关、流量、速度、定时)
  • 控制风扇送风(开关、速度、定时)
    您只需要确保服务器端实现以下API端点:
  • GET /api/sensors – 返回所有传感器数据
  • POST /api/control/{device} – 接收设备控制命令
  • POST /api/settings/{device} – 接收设备设置更新

后端需要实现以下API端点:

  1. 1.
    GET /api/sensors – 返回所有传感器数据
  2. 2.
    POST /api/control/{device} – 接收设备开关控制命令
  3. 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代码的所有功能和逻辑。主要包括以下几个部分:

  1. API配置和全局变量定义:包括服务器地址、传感器数据、设备状态、警报阈值等全局变量
  2. 初始化和核心功能函数:页面初始化、传感器数据更新、设备控制等核心功能
  3. 警报系统相关函数:警报检测、创建、显示等功能
  4. 历史数据图表相关函数:Chart.js图表初始化、数据更新等功能
  5. 自动化规则相关函数:规则创建、编辑、删除、执行等功能
  6. 服务器通信相关函数:与后端API通信,发送设备控制指令和获取传感器数据
  7. 页面加载初始化:设置DOMContentLoaded事件监听器,确保页面加载完成后执行初始化
文末附加内容

评论

  1. 博主
    Windows Edge
    8 月前
    2025-9-25 20:30:38

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

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇