Инструменты пользователя

Инструменты сайта


iot:software:node-red:flows:heating_monitoring

Мониторинг отопления с Node-RED

Давно было интересно, как меняется температура отопления и горячей воды в зимний период и соотношение ее с температурой на улице.

Для этого было куплено несколько1) датчиков DS18B20 и столько же плат ESP-01S. Датчики крепились каптоновой лентой к зачищенным и немного подровненным трубам на термопасту, а снаружи оборачивались сантиметровой пеной и скотчем для изоляции.

Выглядит в итоге это как-то так:

Теперь же руки добрались до написания потока Node-RED для сохранения этой телеметрии в базу данных для последующего анализа.

Нестандартные узлы

В этом потоке используются следующие узлы, которых нет в стандартной поставке:

Описание работы цепочки

Т.к. все устройства совершенно однотипные и сгруппированы в MQTT, для получения данных используется один узел «mqtt in» с подстановочным знаком «+» в теме.

Далее ожидаются 42) сообщения от разных узлов, которые объединяются в одно сообщение, которое уже после обработки записывается в базу данных SQLite. Если какой-то датчик по какой-то причине продублирует свое сообщение пока не были получены все 4 разных сообщения от разных датчиков, в узле «join» останется только его последнее сообщение. Если в течение 630 секунд3) не поступят все сообщения, накопленный результат отправляется, как есть, иначе один единственный повисший датчик, намертво заблокирует весь поток. А так,.. лучше отправить, что есть, чем вообще ничего!

Помимо этого, в потоковый контекст постоянно записываются данные от уличного датчика температуры, срок актуальности которых установлен в 1 час. Т.е. если уличный датчик по какой-то причине перестал передавать данные, то через час они будут удалены из контекста и в базу данных не попадут, что повышает достоверность собранного набора данных.

Скриншот потока

Сопутствующие статьи

Код для загрузки

heating-monitoring.json
[
    {
        "id": "9c857462.bef2a8",
        "type": "tab",
        "label": "Телеметрия отопления",
        "disabled": false,
        "info": ""
    },
    {
        "id": "f9129ac0.b07658",
        "type": "mqtt in",
        "z": "9c857462.bef2a8",
        "name": "Датчики в квартире",
        "topic": "heating/+/tele/SENSOR",
        "qos": "2",
        "datatype": "json",
        "broker": "8745b931.133868",
        "x": 130,
        "y": 40,
        "wires": [
            [
                "e6e74c46.c6f8d"
            ]
        ]
    },
    {
        "id": "6116c20f.2ab95c",
        "type": "link in",
        "z": "9c857462.bef2a8",
        "name": "Уличный датчик",
        "links": [
            "2156bb31.7f3094"
        ],
        "x": 120,
        "y": 160,
        "wires": [
            [
                "99cab6e6.970e38"
            ]
        ],
        "l": true,
        "info": "Температура на улице в тени."
    },
    {
        "id": "99cab6e6.970e38",
        "type": "change",
        "z": "9c857462.bef2a8",
        "name": "Запись в контекст",
        "rules": [
            {
                "t": "set",
                "p": "temperature.outside",
                "pt": "flow",
                "to": "payload.temperature",
                "tot": "msg"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 330,
        "y": 160,
        "wires": [
            [
                "5e2b4155.c133a"
            ]
        ]
    },
    {
        "id": "16050c98.3521c3",
        "type": "join",
        "z": "9c857462.bef2a8",
        "name": "Объединение сообщений",
        "mode": "custom",
        "build": "object",
        "property": "temperature.devices",
        "propertyType": "msg",
        "key": "topic",
        "joiner": "\\n",
        "joinerType": "str",
        "accumulate": false,
        "timeout": "630",
        "count": "4",
        "reduceRight": false,
        "reduceExp": "",
        "reduceInit": "",
        "reduceInitType": "",
        "reduceFixup": "",
        "x": 800,
        "y": 40,
        "wires": [
            [
                "a5e43fba.1a21"
            ]
        ]
    },
    {
        "id": "e6e74c46.c6f8d",
        "type": "change",
        "z": "9c857462.bef2a8",
        "name": "Подготовка объединения",
        "rules": [
            {
                "t": "set",
                "p": "topic",
                "pt": "msg",
                "to": "( topic ~> /^.+\\/(.+)_.+$/ ~> $lookup( \"groups\" ) )[0]",
                "tot": "jsonata"
            },
            {
                "t": "move",
                "p": "payload.DS18B20.Temperature",
                "pt": "msg",
                "to": "temperature.devices",
                "tot": "msg"
            },
            {
                "t": "set",
                "p": "temperature.devices",
                "pt": "flow",
                "to": "$merge( [ $flowContext( \"temperature.devices\" ), { $.topic: temperature.devices } ] )",
                "tot": "jsonata"
            },
            {
                "t": "delete",
                "p": "payload",
                "pt": "msg"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 440,
        "y": 40,
        "wires": [
            [
                "16050c98.3521c3"
            ]
        ],
        "info": "flow.temperature.devices сохраняется только для диагностики и не более."
    },
    {
        "id": "a5e43fba.1a21",
        "type": "change",
        "z": "9c857462.bef2a8",
        "name": "Подготовка за проса к базе",
        "rules": [
            {
                "t": "set",
                "p": "topic",
                "pt": "msg",
                "to": "\"INSERT INTO telemetry ( timestamp, \" & $join( $keys( temperature.devices ), \", \") & \", outside ) VALUES ( \\\"\" & $now( '[Y0001]-[M01]-[D01] [H01]:[m01]:[s01]', '+1000' ) & \"\\\", \" & $join ( temperature.devices.*.$string(), \", \" ) & \", \" & ( $type( $flowContext( \"temperature.outside\" ) ) = 'number' ? $flowContext( \"temperature.outside\" ) : null ) & \" )\"",
                "tot": "jsonata"
            },
            {
                "t": "delete",
                "p": "temperature",
                "pt": "msg"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 160,
        "y": 100,
        "wires": [
            [
                "5aff2fce.a25b4"
            ]
        ]
    },
    {
        "id": "5aff2fce.a25b4",
        "type": "sqlite",
        "z": "9c857462.bef2a8",
        "mydb": "309e3304.c5378c",
        "sqlquery": "msg.topic",
        "sql": "",
        "name": "Запись в базу данных",
        "x": 460,
        "y": 100,
        "wires": [
            []
        ]
    },
    {
        "id": "4350373e.039cd8",
        "type": "comment",
        "z": "9c857462.bef2a8",
        "name": "SQL-запрос для создания таблицы",
        "info": "CREATE TABLE \"telemetry\" (\n\t\"_id\"\tINTEGER,\n\t\"timestamp\"\tTEXT,\n\t\"bathroom\"\tREAL,\n\t\"bedroom\"\tREAL,\n\t\"nursery\"\tREAL,\n\t\"kitchen\"\tREAL,\n\t\"outside\"\tREAL,\n\tPRIMARY KEY(\"_id\" AUTOINCREMENT)\n);",
        "x": 780,
        "y": 100,
        "wires": []
    },
    {
        "id": "5e2b4155.c133a",
        "type": "trigger",
        "z": "9c857462.bef2a8",
        "name": "Задержка удаления",
        "op1": "",
        "op2": "",
        "op1type": "nul",
        "op2type": "date",
        "duration": "1",
        "extend": true,
        "overrideDelay": false,
        "units": "hr",
        "reset": "",
        "bytopic": "all",
        "topic": "topic",
        "outputs": 1,
        "x": 560,
        "y": 160,
        "wires": [
            [
                "bb18e8aa.216bd8"
            ]
        ]
    },
    {
        "id": "bb18e8aa.216bd8",
        "type": "change",
        "z": "9c857462.bef2a8",
        "name": "Удаление температуры",
        "rules": [
            {
                "t": "set",
                "p": "temperature.outside",
                "pt": "flow",
                "to": "failure",
                "tot": "str"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 810,
        "y": 160,
        "wires": [
            []
        ]
    },
    {
        "id": "8745b931.133868",
        "type": "mqtt-broker",
        "name": "localhost",
        "broker": "localhost",
        "port": "1883",
        "clientid": "",
        "usetls": false,
        "compatmode": false,
        "keepalive": "60",
        "cleansession": true,
        "birthTopic": "",
        "birthQos": "0",
        "birthPayload": "",
        "closeTopic": "",
        "closeQos": "0",
        "closePayload": "",
        "willTopic": "",
        "willQos": "0",
        "willPayload": ""
    },
    {
        "id": "309e3304.c5378c",
        "type": "sqlitedb",
        "db": "c:\\noderedDB\\heating_telemetry.db",
        "mode": "RWC"
    }
]

Дисклеймер

  • Использование материалов данной базы знаний разрешено на условиях лицензии, указанной внизу каждой страницы! При использовании материалов активная гиперссылка на соответствующую страницу данной базы знаний обязательна!
  • Автор не несет и не может нести какую либо ответственность за последствия использования материалов, размещенных в данной базе знаний. Все материалы предоставляются по принципу «как есть». Используйте их исключительно на свой страх и риск.
  • Все высказывания, мысли или идеи автора, размещенные в материалах данной базе знаний, являются исключительно его личным субъективным мнением и могут не совпадать с мнением читателей!
  • При размещении ссылок в данной базе знаний на интернет-страницы третьих лиц автор не несет ответственности за их техническую функциональность (особенно отсутствие вирусов) и содержание! При обнаружении таких ссылок, можно и желательно сообщить о них в комментариях к соответствующей статье.
1)
По количеству комнат + санузел.
2)
По количеству датчиков.
3)
При условии, что датчики передают телеметрию один раз в 10 минут.

Обсуждение

Ваш комментарий:
E E Y N W N D Y Z᠎ P C C W U W S
 
Последнее изменение: 2023/12/03 22:01 — Николай Солошин