[空调控制]自动选择空调执行参数

24

开启空调时自动识别当前室内温度室外温度差, 设置一个舒适的空调模式,15分钟检查一次判断是否继续调整,结合当前系统判断季节进一步锁死空调模式功能.

[
    {
        "id": "ab6f64c4dad8b73c",
        "type": "server-state-changed",
        "z": "8dde7d6f02804d4f",
        "name": "客厅空调状态",
        "server": "6d42cb4e16b94937",
        "version": 6,
        "outputs": 1,
        "exposeAsEntityConfig": "",
        "entities": {
            "entity": [
                "climate.777ca934"
            ],
            "substring": [],
            "regex": []
        },
        "outputInitially": true,
        "stateType": "str",
        "ifState": "",
        "ifStateType": "str",
        "ifStateOperator": "is",
        "outputOnlyOnStateChange": true,
        "for": "0",
        "forType": "num",
        "forUnits": "minutes",
        "ignorePrevStateNull": false,
        "ignorePrevStateUnknown": false,
        "ignorePrevStateUnavailable": false,
        "ignoreCurrentStateUnknown": false,
        "ignoreCurrentStateUnavailable": false,
        "outputProperties": [
            {
                "property": "ac_state",
                "propertyType": "msg",
                "value": "",
                "valueType": "entityState"
            },
            {
                "property": "data",
                "propertyType": "msg",
                "value": "",
                "valueType": "eventData"
            },
            {
                "property": "topic",
                "propertyType": "msg",
                "value": "",
                "valueType": "triggerId"
            }
        ],
        "x": 130,
        "y": 4080,
        "wires": [
            [
                "9ef0f8d8c11f1a37"
            ]
        ]
    },
    {
        "id": "9ef0f8d8c11f1a37",
        "type": "api-current-state",
        "z": "8dde7d6f02804d4f",
        "name": "读取空调状态",
        "server": "6d42cb4e16b94937",
        "version": 3,
        "outputs": 1,
        "halt_if": "",
        "halt_if_type": "str",
        "halt_if_compare": "is",
        "entity_id": "climate.777ca934",
        "state_type": "str",
        "blockInputOverrides": true,
        "outputProperties": [
            {
                "property": "ac_state",
                "propertyType": "msg",
                "value": "",
                "valueType": "entityState"
            },
            {
                "property": "topic",
                "propertyType": "msg",
                "value": "",
                "valueType": "triggerId"
            }
        ],
        "for": "0",
        "forType": "num",
        "forUnits": "minutes",
        "override_topic": false,
        "state_location": "payload",
        "override_payload": "msg",
        "entity_location": "data",
        "override_data": "msg",
        "x": 320,
        "y": 4080,
        "wires": [
            [
                "25cc57eee2a7985c"
            ]
        ]
    },
    {
        "id": "fb501e4eeb568778",
        "type": "mqtt in",
        "z": "8dde7d6f02804d4f",
        "name": "",
        "topic": "home/ac_comfort_cycle",
        "qos": "2",
        "datatype": "auto-detect",
        "broker": "0fea454ccb5d8006",
        "nl": false,
        "rap": true,
        "rh": 0,
        "inputs": 0,
        "x": 160,
        "y": 4200,
        "wires": [
            [
                "9ef0f8d8c11f1a37"
            ]
        ]
    },
    {
        "id": "25cc57eee2a7985c",
        "type": "api-current-state",
        "z": "8dde7d6f02804d4f",
        "name": "家里是否有人",
        "server": "6d42cb4e16b94937",
        "version": 3,
        "outputs": 2,
        "halt_if": "on",
        "halt_if_type": "str",
        "halt_if_compare": "is",
        "entity_id": "input_boolean.person_maybe_left",
        "state_type": "str",
        "blockInputOverrides": true,
        "outputProperties": [
            {
                "property": "payload",
                "propertyType": "msg",
                "value": "",
                "valueType": "entityState"
            },
            {
                "property": "data",
                "propertyType": "msg",
                "value": "",
                "valueType": "entity"
            }
        ],
        "for": "0",
        "forType": "num",
        "forUnits": "minutes",
        "override_topic": false,
        "state_location": "payload",
        "override_payload": "msg",
        "entity_location": "data",
        "override_data": "msg",
        "x": 520,
        "y": 4080,
        "wires": [
            [
                "ad19128ccc580383"
            ],
            []
        ]
    },
    {
        "id": "ad19128ccc580383",
        "type": "change",
        "z": "8dde7d6f02804d4f",
        "name": "配置实体传感器(按需修改室内传感器ID)",
        "rules": [
            {
                "t": "set",
                "p": "entity_id",
                "pt": "msg",
                "to": "topic",
                "tot": "msg"
            },
            {
                "t": "set",
                "p": "indoor_sensor",
                "pt": "msg",
                "to": "sensor.xiaomi_cn_blt_3_1lgn2hjk8c402_mini_temperature_p_2_1001",
                "tot": "str"
            },
            {
                "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"
            }
        ],
        "x": 780,
        "y": 4080,
        "wires": [
            [
                "a759ef419ee93ce0"
            ]
        ]
    },
    {
        "id": "a759ef419ee93ce0",
        "type": "api-current-state",
        "z": "8dde7d6f02804d4f",
        "name": "读取空调状态",
        "server": "6d42cb4e16b94937",
        "version": 3,
        "outputs": 1,
        "halt_if": "",
        "halt_if_type": "str",
        "halt_if_compare": "is",
        "entity_id": "{{entity_id}}",
        "state_type": "str",
        "blockInputOverrides": true,
        "outputProperties": [
            {
                "property": "ac_state",
                "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": 1040,
        "y": 4080,
        "wires": [
            [
                "9d242f88f0761cea"
            ]
        ]
    },
    {
        "id": "9d242f88f0761cea",
        "type": "function",
        "z": "8dde7d6f02804d4f",
        "name": "仅当空调非off/unavailable",
        "func": "const s = (msg.ac_state || '').toLowerCase();\nif (s && s !== 'off' && s !== 'unavailable') { return msg; }\nreturn null;",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 550,
        "y": 4180,
        "wires": [
            [
                "ef6b35d44085a575"
            ]
        ]
    },
    {
        "id": "ef6b35d44085a575",
        "type": "api-current-state",
        "z": "8dde7d6f02804d4f",
        "name": "读取室内温度",
        "server": "6d42cb4e16b94937",
        "version": 3,
        "outputs": 1,
        "halt_if": "",
        "halt_if_type": "str",
        "halt_if_compare": "is",
        "entity_id": "{{indoor_sensor}}",
        "state_type": "str",
        "blockInputOverrides": true,
        "outputProperties": [
            {
                "property": "indoor_temp",
                "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": 780,
        "y": 4180,
        "wires": [
            [
                "633a7c044b912f9c"
            ]
        ]
    },
    {
        "id": "633a7c044b912f9c",
        "type": "api-current-state",
        "z": "8dde7d6f02804d4f",
        "name": "读取室外温度",
        "server": "6d42cb4e16b94937",
        "version": 3,
        "outputs": 1,
        "halt_if": "",
        "halt_if_type": "str",
        "halt_if_compare": "is",
        "entity_id": "{{outdoor_sensor}}",
        "state_type": "str",
        "blockInputOverrides": true,
        "outputProperties": [
            {
                "property": "outdoor_temp",
                "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": 960,
        "y": 4180,
        "wires": [
            [
                "b7b54536cbbf32e7"
            ]
        ]
    },
    {
        "id": "b7b54536cbbf32e7",
        "type": "api-current-state",
        "z": "8dde7d6f02804d4f",
        "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": "season_state",
                "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": 1140,
        "y": 4180,
        "wires": [
            [
                "c2ad72ca104f21ae"
            ]
        ]
    },
    {
        "id": "c2ad72ca104f21ae",
        "type": "function",
        "z": "8dde7d6f02804d4f",
        "name": "舒适策略 + 季节约束",
        "func": "const entity_id = msg.entity_id;\nconst ti = parseFloat(msg.indoor_temp);\nconst to = parseFloat(msg.outdoor_temp);\nlet season = (msg.season_state || '').toString().toLowerCase();\nif (!season) {\n    node.warn('无法读取季节状态,使用默认值transition');\n    season = 'transition'; // 默认过渡季\n}\n\nif (!entity_id || isNaN(ti) || isNaN(to)) {\n    node.warn('缺少必要数据: entity_id/indoor_temp/outdoor_temp');\n    return null;\n}\n\n// 动态舒适带\nlet comfortLow = 22;\nlet comfortHigh = 26;\nif (to >= 32) comfortHigh -= 1;\nif (to <= 10) comfortLow += 1;\n\nconst delta = ti - to;\nlet actions = [];\nconst set = (action, data) => actions.push({ action, data });\n\n// 先依据室内+室外生成建议动作\nif (ti >= comfortHigh + 2) {\n    set('climate.set_hvac_mode', { entity_id, hvac_mode: 'cool' });\n    const target = (to >= 32 || delta <= 1) ? 25 : 26;\n    const fan = (to >= 32 || delta <= 1) ? 'high' : 'medium';\n    set('climate.set_temperature', { entity_id, temperature: target });\n    set('climate.set_fan_mode', { entity_id, fan_mode: fan });\n} else if (ti >= comfortHigh) {\n    set('climate.set_hvac_mode', { entity_id, hvac_mode: 'cool' });\n    const target = (to >= 30 || delta <= 1) ? 26 : 27;\n    const fan = (to >= 30 || delta <= 1) ? 'medium' : 'low';\n    set('climate.set_temperature', { entity_id, temperature: target });\n    set('climate.set_fan_mode', { entity_id, fan_mode: fan });\n} else if (ti > comfortLow && ti < comfortHigh) {\n    // 舒适区:按季节强制模式\n    if (season === 'summer') {\n        set('climate.set_hvac_mode', { entity_id, hvac_mode: 'cool' });\n    } else if (season === 'winter') {\n        set('climate.set_hvac_mode', { entity_id, hvac_mode: 'heat' });\n    } // transition 不强制改模式\n    set('climate.set_temperature', { entity_id, temperature: Math.round((comfortLow + comfortHigh) / 2) });\n    set('climate.set_fan_mode', { entity_id, fan_mode: 'low' });\n} else if (ti <= comfortLow - 2) {\n    set('climate.set_hvac_mode', { entity_id, hvac_mode: 'heat' });\n    const target = (to <= 10 || delta >= 1) ? 24 : 23;\n    const fan = (to <= 10 || delta >= 3) ? 'high' : 'medium';\n    set('climate.set_temperature', { entity_id, temperature: target });\n    set('climate.set_fan_mode', { entity_id, fan_mode: fan });\n} else {\n    set('climate.set_hvac_mode', { entity_id, hvac_mode: 'heat' });\n    const target = (to <= 12 || delta >= 1) ? 23 : 22;\n    const fan = (to <= 12 || delta >= 2) ? 'medium' : 'low';\n    set('climate.set_temperature', { entity_id, temperature: target });\n    set('climate.set_fan_mode', { entity_id, fan_mode: fan });\n}\n\n// 再应用季节白名单过滤\nconst allow = {\n  summer: { heat: false, cool: true },\n  winter: { heat: true, cool: false },\n  transition: { heat: true, cool: true }\n}[season] || { heat: true, cool: true };\n\nactions = actions.filter(act => {\n  if (act.action === 'climate.set_hvac_mode') {\n    const mode = (act.data && act.data.hvac_mode) || '';\n    if (mode === 'heat' && !allow.heat) return false;\n    if (mode === 'cool' && !allow.cool) return false;\n  }\n  return true;\n});\n\nmsg.payload = actions;\nnode.warn(JSON.stringify({ entity_id, ti, to, season, comfortLow, comfortHigh, delta, actions }));\nreturn msg;",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 640,
        "y": 4280,
        "wires": [
            [
                "d6029c2e2779168a",
                "9cda333e200fecbc"
            ]
        ]
    },
    {
        "id": "d6029c2e2779168a",
        "type": "debug",
        "z": "8dde7d6f02804d4f",
        "name": "预览动作",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "true",
        "targetType": "full",
        "statusVal": "",
        "statusType": "auto",
        "x": 840,
        "y": 4320,
        "wires": []
    },
    {
        "id": "9cda333e200fecbc",
        "type": "split",
        "z": "8dde7d6f02804d4f",
        "name": "拆分每个指令",
        "splt": "",
        "spltType": "str",
        "arraySplt": 1,
        "arraySpltType": "len",
        "stream": false,
        "addname": "",
        "property": "payload",
        "x": 860,
        "y": 4280,
        "wires": [
            [
                "579bcff911d5b0b1"
            ]
        ]
    },
    {
        "id": "579bcff911d5b0b1",
        "type": "api-call-service",
        "z": "8dde7d6f02804d4f",
        "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": 1060,
        "y": 4280,
        "wires": [
            [
                "cd980dec595a9b1f"
            ]
        ]
    },
    {
        "id": "cd980dec595a9b1f",
        "type": "delay",
        "z": "8dde7d6f02804d4f",
        "name": "节流:15分钟仅1次",
        "pauseType": "rate",
        "timeout": "5",
        "timeoutUnits": "seconds",
        "rate": "1",
        "nbRateUnits": "15",
        "rateUnits": "second",
        "randomFirst": "0",
        "randomLast": "0",
        "randomUnits": "seconds",
        "drop": true,
        "allowrate": false,
        "outputs": 1,
        "x": 1170,
        "y": 4340,
        "wires": [
            [
                "ee417197a9fc4e15"
            ]
        ]
    },
    {
        "id": "ee417197a9fc4e15",
        "type": "mqtt out",
        "z": "8dde7d6f02804d4f",
        "name": "",
        "topic": "home/ac_comfort_trigger",
        "qos": "",
        "retain": "",
        "respTopic": "",
        "contentType": "",
        "userProps": "",
        "correl": "",
        "expiry": "",
        "broker": "0fea454ccb5d8006",
        "x": 1170,
        "y": 4400,
        "wires": []
    },
    {
        "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
    },
    {
        "id": "0fea454ccb5d8006",
        "type": "mqtt-broker",
        "name": "",
        "broker": "10.0.0.90",
        "port": 1883,
        "clientid": "",
        "autoConnect": true,
        "usetls": false,
        "protocolVersion": 4,
        "keepalive": 60,
        "cleansession": true,
        "autoUnsubscribe": true,
        "birthTopic": "",
        "birthQos": "0",
        "birthRetain": "false",
        "birthPayload": "",
        "birthMsg": {},
        "closeTopic": "",
        "closeQos": "0",
        "closeRetain": "false",
        "closePayload": "",
        "closeMsg": {},
        "willTopic": "",
        "willQos": "0",
        "willRetain": "false",
        "willPayload": "",
        "willMsg": {},
        "userProps": "",
        "sessionExpiry": ""
    }
]