Давно было интересно, как меняется температура отопления и горячей воды в зимний период и соотношение ее с температурой на улице.
Для этого было куплено несколько1) датчиков DS18B20 и столько же плат ESP-01S. Датчики крепились каптоновой лентой к зачищенным и немного подровненным трубам на термопасту, а снаружи оборачивались сантиметровой пеной и скотчем для изоляции.
Выглядит в итоге это как-то так:
Теперь же руки добрались до написания потока Node-RED для сохранения этой телеметрии в базу данных для последующего анализа.
В этом потоке используются следующие узлы, которых нет в стандартной поставке:
Т.к. все устройства совершенно однотипные и сгруппированы в MQTT, для получения данных используется один узел «mqtt in» с подстановочным знаком «+» в теме.
Далее ожидаются 42) сообщения от разных узлов, которые объединяются в одно сообщение, которое уже после обработки записывается в базу данных SQLite. Если какой-то датчик по какой-то причине продублирует свое сообщение пока не были получены все 4 разных сообщения от разных датчиков, в узле «join» останется только его последнее сообщение. Если в течение 630 секунд3) не поступят все сообщения, накопленный результат отправляется, как есть, иначе один единственный повисший датчик, намертво заблокирует весь поток. А так,.. лучше отправить, что есть, чем вообще ничего!
Помимо этого, в потоковый контекст постоянно записываются данные от уличного датчика температуры, срок актуальности которых установлен в 1 час. Т.е. если уличный датчик по какой-то причине перестал передавать данные, то через час они будут удалены из контекста и в базу данных не попадут, что повышает достоверность собранного набора данных.
[ { "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" } ]
Обсуждение