[空调控制]NodeRed自动判断当前季节

19

根据过去7天温度变化数据主动识别判断当前季节.

创建一个辅助元素 input_select.season

可选参数: summer winter transition

NodeRed流程:

[
    {
        "id": "75c4c5c446cd5b15",
        "type": "function",
        "z": "63f66746ece01b5f",
        "name": "检查是否需要更新",
        "func": "// 检查检测到的季节是否与当前季节不同\nconst detectedSeason = msg.detected_season || 'transition';\nconst currentSeason = msg.current_season || 'transition';\n\nif (detectedSeason !== currentSeason) {\n    // 季节不同,需要更新\n    return msg;\n} else {\n    // 季节相同,不需要更新\n    node.warn(`季节未变化,跳过更新: ${currentSeason}`);\n    return null;\n}",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 700,
        "y": 200,
        "wires": [
            [
                "e41988d70742a5b9"
            ]
        ]
    },
    {
        "id": "59d25f3c136ea6e4",
        "type": "function",
        "z": "63f66746ece01b5f",
        "name": "分析温度数据判断季节",
        "func": "// 解析历史数据\nconst historyData = msg.payload;\nif (!historyData || !Array.isArray(historyData) || historyData.length === 0) {\n    node.warn('无法获取历史温度数据');\n    return null;\n}\n\n// 提取所有温度值\nconst temps = [];\nfor (const dayData of historyData) {\n    if (Array.isArray(dayData)) {\n        for (const record of dayData) {\n            const temp = parseFloat(record.state);\n            if (!isNaN(temp)) {\n                temps.push(temp);\n            }\n        }\n    }\n}\n\nif (temps.length === 0) {\n    node.warn('历史数据中没有有效的温度值');\n    return null;\n}\n\n// 计算统计值\nconst avgTemp = temps.reduce((a, b) => a + b, 0) / temps.length;\nconst maxTemp = Math.max(...temps);\nconst minTemp = Math.min(...temps);\n\n// 计算温度趋势:最近3天 vs 前4天\nconst midPoint = Math.floor(temps.length / 2);\nconst recentTemps = temps.slice(midPoint);\nconst earlierTemps = temps.slice(0, midPoint);\nconst recentAvg = recentTemps.reduce((a, b) => a + b, 0) / recentTemps.length;\nconst earlierAvg = earlierTemps.length > 0 ? earlierTemps.reduce((a, b) => a + b, 0) / earlierTemps.length : recentAvg;\nconst tempTrend = recentAvg - earlierAvg;\n\n// 季节判断逻辑\nlet detectedSeason = 'transition';\n\n// 夏季判断:平均温度高或最高温度高\nif (avgTemp >= 25 || maxTemp >= 30) {\n    detectedSeason = 'summer';\n}\n// 冬季判断:平均温度低或最低温度低\nelse if (avgTemp <= 15 || minTemp <= 5) {\n    detectedSeason = 'winter';\n}\n// 过渡季节判断:结合趋势\nelse if (avgTemp > 20 && tempTrend > 2) {\n    // 温度较高且上升趋势明显 -> 可能进入夏季\n    detectedSeason = 'summer';\n}\nelse if (avgTemp < 20 && tempTrend < -2) {\n    // 温度较低且下降趋势明显 -> 可能进入冬季\n    detectedSeason = 'winter';\n}\nelse {\n    // 其他情况保持过渡季\n    detectedSeason = 'transition';\n}\n\n// 保存分析结果\nmsg.detected_season = detectedSeason;\nmsg.current_season = msg.current_season || 'transition';\nmsg.stats = {\n    avgTemp: avgTemp.toFixed(1),\n    maxTemp: maxTemp.toFixed(1),\n    minTemp: minTemp.toFixed(1),\n    tempTrend: tempTrend.toFixed(1),\n    dataPoints: temps.length\n};\n\nnode.warn(`季节判断: 当前=${msg.current_season}, 检测=${detectedSeason}, 平均=${avgTemp.toFixed(1)}°C, 趋势=${tempTrend > 0 ? '+' : ''}${tempTrend.toFixed(1)}°C`);\n\nreturn msg;",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 470,
        "y": 200,
        "wires": [
            [
                "75c4c5c446cd5b15"
            ]
        ]
    },
    {
        "id": "e41988d70742a5b9",
        "type": "function",
        "z": "63f66746ece01b5f",
        "name": "构建服务调用数据",
        "func": "// 构建服务调用数据\nconst entity_id = msg.season_entity || 'input_select.season';\nconst option = msg.detected_season || 'transition';\n\nmsg.payload = {\n  action: \"input_select.select_option\",\n  data: {\n    entity_id,\n    option\n  }\n};\n\nreturn msg;",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 220,
        "y": 320,
        "wires": [
            [
                "a8f72b6f4884b3e4"
            ]
        ]
    },
    {
        "id": "3739259c563e17cf",
        "type": "http request",
        "z": "63f66746ece01b5f",
        "name": "获取历史温度数据",
        "method": "use",
        "ret": "obj",
        "url": "",
        "tls": "",
        "persist": false,
        "proxy": "",
        "insecureHTTPParser": false,
        "authType": "",
        "senderr": false,
        "headers": [],
        "x": 220,
        "y": 200,
        "wires": [
            [
                "59d25f3c136ea6e4"
            ]
        ]
    },
    {
        "id": "a8f72b6f4884b3e4",
        "type": "api-call-service",
        "z": "63f66746ece01b5f",
        "name": "更新季节",
        "server": "6d42cb4e16b94937",
        "version": 7,
        "debugenabled": false,
        "action": "",
        "floorId": [],
        "areaId": [],
        "deviceId": [],
        "entityId": [],
        "labelId": [],
        "data": "",
        "dataType": "json",
        "mergeContext": "",
        "mustacheAltTags": false,
        "outputProperties": [],
        "queue": "none",
        "blockInputOverrides": false,
        "domain": "",
        "service": "",
        "x": 410,
        "y": 320,
        "wires": [
            [
                "7c62f2f3ae85e9c7"
            ]
        ]
    },
    {
        "id": "906ad1eb70807484",
        "type": "function",
        "z": "63f66746ece01b5f",
        "name": "构建历史数据请求",
        "func": "// 计算7天前的时间戳\nconst now = new Date();\nconst sevenDaysAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000);\nconst endTime = now.toISOString();\nconst startTime = sevenDaysAgo.toISOString();\n\n// 配置HA地址(根据你的实际情况修改)\n// 方式1: 如果Node-RED和HA在同一服务器,使用localhost\n// 方式2: 如果Node-RED是HA Add-on,可能需要使用supervisor或localhost\n// 方式3: 如果Node-RED在外部,使用完整的HA地址\nconst haBaseUrl = 'http://10.0.0.90:8123'; // 修改为你的HA地址\n// const haBaseUrl = 'http://homeassistant.local:8123'; // 或者使用.local地址\n// const haBaseUrl = 'http://192.168.1.100:8123'; // 或者使用IP地址\n\n// 配置HA长期访问令牌(在HA中:配置 -> 长期访问令牌 -> 创建令牌)\nconst haAccessToken = 'Im6Kbrv4IuWVrtWdxfzGv-szHxUpcsTPI1zwJfM'; // 替换为你的令牌\n\n// 构建HA History API请求URL\nmsg.url = `${haBaseUrl}/api/history/period/${startTime}?filter_entity_id=${msg.outdoor_sensor}&end_time=${endTime}`;\nmsg.method = 'GET';\n\n// 添加认证头\nmsg.headers = {\n    'Authorization': `Bearer ${haAccessToken}`,\n    'Content-Type': 'application/json'\n};\n\nreturn msg;",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 780,
        "y": 80,
        "wires": [
            [
                "3739259c563e17cf"
            ]
        ]
    },
    {
        "id": "7c62f2f3ae85e9c7",
        "type": "debug",
        "z": "63f66746ece01b5f",
        "name": "调试输出",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "true",
        "targetType": "full",
        "statusVal": "",
        "statusType": "auto",
        "x": 670,
        "y": 320,
        "wires": []
    },
    {
        "id": "6d252af7fe1f2b77",
        "type": "api-current-state",
        "z": "63f66746ece01b5f",
        "name": "读取当前季节",
        "server": "6d42cb4e16b94937",
        "version": 3,
        "outputs": 1,
        "halt_if": "",
        "halt_if_type": "str",
        "halt_if_compare": "is",
        "entity_id": "{{season_entity}}",
        "state_type": "str",
        "blockInputOverrides": true,
        "outputProperties": [
            {
                "property": "current_season",
                "propertyType": "msg",
                "value": "",
                "valueType": "entityState"
            }
        ],
        "for": "0",
        "forType": "num",
        "forUnits": "minutes",
        "override_topic": false,
        "state_location": "payload",
        "override_payload": "msg",
        "entity_location": "data",
        "override_data": "msg",
        "x": 540,
        "y": 80,
        "wires": [
            [
                "906ad1eb70807484"
            ]
        ]
    },
    {
        "id": "f820c2e8efa41063",
        "type": "change",
        "z": "63f66746ece01b5f",
        "name": "配置传感器",
        "rules": [
            {
                "t": "set",
                "p": "outdoor_sensor",
                "pt": "msg",
                "to": "sensor.xiaomi_cn_blt_3_1lubuvk9t0k00_mini_temperature_p_2_1001",
                "tot": "str"
            },
            {
                "t": "set",
                "p": "season_entity",
                "pt": "msg",
                "to": "input_select.season",
                "tot": "str"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 330,
        "y": 80,
        "wires": [
            [
                "6d252af7fe1f2b77"
            ]
        ]
    },
    {
        "id": "4131351f1f9ee240",
        "type": "inject",
        "z": "63f66746ece01b5f",
        "name": "每天检查一次",
        "props": [
            {
                "p": "payload"
            }
        ],
        "repeat": "",
        "crontab": "0 2 * * *",
        "once": false,
        "onceDelay": 0.1,
        "topic": "",
        "payload": "",
        "payloadType": "date",
        "x": 130,
        "y": 80,
        "wires": [
            [
                "f820c2e8efa41063"
            ]
        ]
    },
    {
        "id": "6d42cb4e16b94937",
        "type": "server",
        "name": "Home Assistant_main",
        "version": 5,
        "addon": true,
        "rejectUnauthorizedCerts": true,
        "ha_boolean": "y|yes|true|on|home|open",
        "connectionDelay": true,
        "cacheJson": true,
        "heartbeat": true,
        "heartbeatInterval": 30,
        "areaSelector": "friendlyName",
        "deviceSelector": "friendlyName",
        "entitySelector": "friendlyName",
        "statusSeparator": ": ",
        "statusYear": "hidden",
        "statusMonth": "short",
        "statusDay": "numeric",
        "statusHourCycle": "default",
        "statusTimeFormat": "h:m",
        "enableGlobalContextStore": true
    }
]