Это многофункциональный поток Node-RED, совмещающий в себе функции сохранения данных с датчиков температуры и влажности Sonoff SNZB-02 в базу SQLite, выгрузки данных с уличного датчика в проект «Народный мониторинг» и резервного копирования базы на FTP-сервер.
В этой статье описана цепочка узлов, занимающихся сохранением данных, другие зависимые цепочки рассмотрены в отдельных статьях:
Также есть дополнения для сохранения телеметрии с умного увлажнителя и цифрового барометра.
В этой цепочке используются следующие узлы, которых нет в стандартной поставке:
Эти подпотоки входят в состав кода ниже и загружать отдельно их не надо! Однако, если использовать эту цепочку без цепочки резервного копирования, необходимо заменить нижний узел «link out» на подпоток «sendmail».
Соответствующий узел подключается к MQTT-брокеру и следит за данными, поступающими с датчиков. Далее, определяются названия таблиц в базе данных и подготавливаются данные для записи. На следующем этапе данные сохраняются в соответствующую таблицу.
Далее вступает в работу другая ветка цепи, которая отлавливает ошибки SQL и производит одно из двух действий – если ошибка заключается в отсутствии таблицы, таблица автоматически создается и отправляется уведомление на почту; если происходит любая другая ошибка, просто отправляется уведомление на почту.
По ссылке пример создаваемой базы данных. Она же, актуальная телеметрия по Владивостоку с Апреля 2021 года.
Для просмотра, редактирования и постройки экспресс-графиков можно использовать DB Browser for SQLite.
[ { "id": "38c9a069.18cd6", "type": "subflow", "name": "sendmail", "info": "Отправляет почту по настроенным реквизитам.", "category": "", "in": [ { "x": 60, "y": 60, "wires": [ { "id": "be1032f6.e2b02" } ] } ], "out": [], "env": [], "color": "#C7E9C0", "icon": "node-red/envelope.svg" }, { "id": "be1032f6.e2b02", "type": "e-mail", "z": "38c9a069.18cd6", "server": "smtp.beget.com", "port": "465", "secure": true, "tls": true, "name": "nikolay@soloshin.su", "dname": "Отправка сообщения", "x": 240, "y": 60, "wires": [] }, { "id": "98ba2d02.5ee68", "type": "subflow", "name": "watchdog", "info": "Ожидает поступление сообщений в установленный интервал, если сообщения нет, генерирует письмо с предупреждением.\n\nПринимает **msg.topic** в качестве ID датчика и **msg.payload** в качестве названия, которые будут указаны в теле письма.", "category": "", "in": [ { "x": 80, "y": 40, "wires": [ { "id": "fee5d855.a15048" } ] } ], "out": [], "env": [ { "name": "msgnote35634", "type": "str", "value": "", "ui": { "label": { "en-US": "Укажите задержку срабатывания таймера в миллисекундах." }, "type": "none" } }, { "name": "msgdelay", "type": "num", "value": "3600000", "ui": { "icon": "font-awesome/fa-clock-o", "label": { "en-US": "Задержка (мс)" }, "type": "input", "opts": { "types": [ "num" ] } } } ], "color": "#FFAAAA", "icon": "font-awesome/fa-clock-o" }, { "id": "7cada431.44b94c", "type": "trigger", "z": "98ba2d02.5ee68", "name": "Таймер", "op1": "", "op2": "", "op1type": "pay", "op2type": "date", "duration": "10", "extend": true, "overrideDelay": true, "units": "s", "reset": "", "bytopic": "topic", "topic": "topic", "outputs": 2, "x": 500, "y": 40, "wires": [ [ "536fe262.24fb2c" ], [ "4691ad9.e6cf254" ] ], "outputLabels": [ "Первое", "Ошибка" ] }, { "id": "3cfc87ed.72e3d8", "type": "subflow:38c9a069.18cd6", "z": "98ba2d02.5ee68", "name": "Отправка почты", "env": [], "x": 710, "y": 400, "wires": [] }, { "id": "ca1cc7d1.c86308", "type": "change", "z": "98ba2d02.5ee68", "name": "Установка текстовых полей письма", "rules": [ { "t": "move", "p": "firstline", "pt": "msg", "to": "payload", "tot": "msg" }, { "t": "set", "p": "topic", "pt": "msg", "to": "\"Изменился статус датчика (\" & $now('[H01]:[m01]:[s01] [D01].[M01].[Y0001]', '+1000') & \")\"", "tot": "jsonata" } ], "action": "", "property": "", "from": "", "to": "", "reg": false, "x": 650, "y": 340, "wires": [ [ "3cfc87ed.72e3d8" ] ] }, { "id": "fee5d855.a15048", "type": "change", "z": "98ba2d02.5ee68", "name": "Установка задержки таймера", "rules": [ { "t": "set", "p": "delay", "pt": "msg", "to": "msgdelay", "tot": "env" }, { "t": "set", "p": "savedtopic", "pt": "msg", "to": "topic", "tot": "msg" }, { "t": "set", "p": "savedpayload", "pt": "msg", "to": "payload", "tot": "msg" } ], "action": "", "property": "", "from": "", "to": "", "reg": false, "x": 270, "y": 40, "wires": [ [ "7cada431.44b94c" ] ] }, { "id": "536fe262.24fb2c", "type": "change", "z": "98ba2d02.5ee68", "name": "Подготовка запроса", "rules": [ { "t": "set", "p": "topic", "pt": "msg", "to": "\"SELECT * FROM watchdog WHERE topic = \\\"\" & savedtopic & \"\\\"\"", "tot": "jsonata" } ], "action": "", "property": "", "from": "", "to": "", "reg": false, "x": 700, "y": 40, "wires": [ [ "25c0b13d.61990e" ] ] }, { "id": "25c0b13d.61990e", "type": "sqlite", "z": "98ba2d02.5ee68", "mydb": "abf4792e.cd5078", "sqlquery": "msg.topic", "sql": "", "name": "Запрос на наличие записи в БД", "x": 360, "y": 100, "wires": [ [ "43ffca62.b3b554" ] ] }, { "id": "43ffca62.b3b554", "type": "switch", "z": "98ba2d02.5ee68", "name": "Маршрутизация", "property": "payload", "propertyType": "msg", "rules": [ { "t": "nempty" }, { "t": "else" } ], "checkall": "false", "repair": false, "outputs": 2, "x": 710, "y": 100, "wires": [ [ "2557a088.c4251" ], [ "90a4953e.e690a8" ] ] }, { "id": "90a4953e.e690a8", "type": "change", "z": "98ba2d02.5ee68", "name": "Подготовка запроса", "rules": [ { "t": "set", "p": "topic", "pt": "msg", "to": "\"INSERT INTO watchdog (topic, name) VALUES (\\\"\" & savedtopic & \"\\\", \\\"\" & savedpayload & \"\\\")\"", "tot": "jsonata" } ], "action": "", "property": "", "from": "", "to": "", "reg": false, "x": 160, "y": 160, "wires": [ [ "235c7484.7cd52c" ] ] }, { "id": "235c7484.7cd52c", "type": "sqlite", "z": "98ba2d02.5ee68", "mydb": "abf4792e.cd5078", "sqlquery": "msg.topic", "sql": "", "name": "Доб. отсутствующей записи", "x": 420, "y": 160, "wires": [ [ "2557a088.c4251" ] ] }, { "id": "2557a088.c4251", "type": "change", "z": "98ba2d02.5ee68", "name": "Подготовка запроса", "rules": [ { "t": "set", "p": "topic", "pt": "msg", "to": "\"SELECT * FROM watchdog WHERE topic = \\\"\" & savedtopic & \"\\\" AND sent = false\"", "tot": "jsonata" } ], "action": "", "property": "", "from": "", "to": "", "reg": false, "x": 700, "y": 160, "wires": [ [ "b903c102.384fe" ] ] }, { "id": "b903c102.384fe", "type": "sqlite", "z": "98ba2d02.5ee68", "mydb": "abf4792e.cd5078", "sqlquery": "msg.topic", "sql": "", "name": "Выборка записей, которые не отправлялись", "x": 240, "y": 220, "wires": [ [ "2d7a7c9c.c9c0a4" ] ] }, { "id": "2d7a7c9c.c9c0a4", "type": "switch", "z": "98ba2d02.5ee68", "name": "Отсечка пустых сообщений", "property": "payload", "propertyType": "msg", "rules": [ { "t": "nempty" } ], "checkall": "false", "repair": false, "outputs": 1, "x": 680, "y": 220, "wires": [ [ "29c18b99.2c4044" ] ] }, { "id": "27141a53.5a2246", "type": "change", "z": "98ba2d02.5ee68", "name": "Настройка текста письма (false)", "rules": [ { "t": "set", "p": "topic", "pt": "msg", "to": "false", "tot": "bool" }, { "t": "set", "p": "firstline", "pt": "msg", "to": "\"Датчик \\\"\" & savedpayload & \"\\\" не передает показания уже \" & $ceil(delay/1000/60) & \" мин!\\n\\nИдентификатор устройства \\\"\" & savedtopic & \"\\\".\"", "tot": "jsonata" } ], "action": "", "property": "", "from": "", "to": "", "reg": false, "x": 660, "y": 280, "wires": [ [ "b13655cc.bdf9d8" ] ] }, { "id": "b13655cc.bdf9d8", "type": "change", "z": "98ba2d02.5ee68", "name": "Подготовка запроса", "rules": [ { "t": "set", "p": "topic", "pt": "msg", "to": "\"UPDATE watchdog SET sent = \" & topic & \", time = \\\"\" & $now('[Y0001].[M01].[D01] [H01]:[m01]:[s01]', '+1000') & \"\\\" WHERE topic = \\\"\" & savedtopic & \"\\\"\"", "tot": "jsonata" } ], "action": "", "property": "", "from": "", "to": "", "reg": false, "x": 160, "y": 340, "wires": [ [ "b4d28c09.f2007" ] ] }, { "id": "b4d28c09.f2007", "type": "sqlite", "z": "98ba2d02.5ee68", "mydb": "abf4792e.cd5078", "sqlquery": "msg.topic", "sql": "", "name": "Уст. флага отправки", "x": 380, "y": 340, "wires": [ [ "ca1cc7d1.c86308" ] ] }, { "id": "29c18b99.2c4044", "type": "change", "z": "98ba2d02.5ee68", "name": "Настройка текста письма (true)", "rules": [ { "t": "set", "p": "topic", "pt": "msg", "to": "true", "tot": "bool" }, { "t": "set", "p": "firstline", "pt": "msg", "to": "\"Датчик \\\"\" & savedpayload & \"\\\" передал показания! Интервал проверки \" & $ceil(delay/1000/60) & \" мин.\\n\\nИдентификатор устройства \\\"\" & savedtopic & \"\\\".\"", "tot": "jsonata" } ], "action": "", "property": "", "from": "", "to": "", "reg": false, "x": 190, "y": 280, "wires": [ [ "b13655cc.bdf9d8" ] ] }, { "id": "4691ad9.e6cf254", "type": "link out", "z": "98ba2d02.5ee68", "name": "", "links": [ "ab7f4159.256a4" ], "x": 75, "y": 100, "wires": [] }, { "id": "ab7f4159.256a4", "type": "link in", "z": "98ba2d02.5ee68", "name": "", "links": [ "4691ad9.e6cf254" ], "x": 435, "y": 280, "wires": [ [ "27141a53.5a2246" ] ] }, { "id": "abf4792e.cd5078", "type": "sqlitedb", "z": "98ba2d02.5ee68", "db": "c:\\noderedDB\\watchdog.db", "mode": "RWC" }, { "id": "551f568e.e62c18", "type": "tab", "label": "Сохранение телеметрии", "disabled": false, "info": "" }, { "id": "34dfd14f.ad355e", "type": "subflow:98ba2d02.5ee68", "z": "551f568e.e62c18", "name": "Сторожевой таймер", "env": [], "x": 700, "y": 160, "wires": [] }, { "id": "6611f5d3.167edc", "type": "sqlite", "z": "551f568e.e62c18", "mydb": "dbb6e10a.23844", "sqlquery": "msg.topic", "sql": "", "name": "Запись в БД", "x": 470, "y": 160, "wires": [ [] ] }, { "id": "ff007eab.b7d18", "type": "change", "z": "551f568e.e62c18", "name": "Подготовка данных", "rules": [ { "t": "set", "p": "topic", "pt": "msg", "to": "\"INSERT INTO \\\"\" & payload & \"\\\" (timestamp, temperature, humidity, pressureSt, pressureSl, pressureBt, battery, linkquality) VALUES (\\\"\" & $now('[Y0001]-[M01]-[D01] [H01]:[m01]:[s01]', '+1000') & \"\\\", \" & ( $type( originalpayload.temperature ) = 'number' ? originalpayload.temperature : null ) & \", \" & ( $type( originalpayload.humidity ) = 'number' ? originalpayload.humidity : null ) & \", \" & ( $type( originalpayload.pressure_st ) = 'number' ? originalpayload.pressure_st : null ) & \", \" & ( $type( originalpayload.pressure_sl ) = 'number' ? originalpayload.pressure_sl : null ) & \", \" & ( $type( originalpayload.pressure_bt ) = 'number' ? originalpayload.pressure_bt : null ) & \", \" & ( $type( originalpayload.battery ) = 'number' ? originalpayload.battery : null ) & \", \" & ( $type( originalpayload.linkquality ) = 'number' ? originalpayload.linkquality : null ) & \")\"", "tot": "jsonata" } ], "action": "", "property": "", "from": "", "to": "", "reg": false, "x": 240, "y": 160, "wires": [ [ "6611f5d3.167edc" ] ] }, { "id": "2c1ed738.58c7a8", "type": "change", "z": "551f568e.e62c18", "name": "Именование таблиц", "rules": [ { "t": "change", "p": "payload", "pt": "msg", "from": "zigbee2mqtt/0x00124b002268acb0", "fromt": "str", "to": "shadow", "tot": "str" }, { "t": "change", "p": "payload", "pt": "msg", "from": "barometer_1A2AE9/tele/SENSOR", "fromt": "str", "to": "balcony", "tot": "str" }, { "t": "change", "p": "payload", "pt": "msg", "from": "humidifier_00896D/tele/SENSOR", "fromt": "str", "to": "humidifier", "tot": "str" } ], "action": "", "property": "", "from": "", "to": "", "reg": false, "x": 700, "y": 100, "wires": [ [ "ff007eab.b7d18", "34dfd14f.ad355e" ] ] }, { "id": "94ebbe09.ee7b", "type": "catch", "z": "551f568e.e62c18", "name": "Отлов ошибок SQLite", "scope": [ "6611f5d3.167edc" ], "uncaught": false, "x": 160, "y": 220, "wires": [ [ "7041377d.8779b8" ] ] }, { "id": "7041377d.8779b8", "type": "switch", "z": "551f568e.e62c18", "name": "Определение ошибки", "property": "error.message", "propertyType": "msg", "rules": [ { "t": "cont", "v": "no such table", "vt": "str" }, { "t": "else" } ], "checkall": "false", "repair": false, "outputs": 2, "x": 160, "y": 280, "wires": [ [ "1332f40b.7fab7c" ], [ "1fda0b91.6192f4" ] ], "outputLabels": [ "Нет таблицы", "Прочие ошибки" ] }, { "id": "d6aeb8ba.178f68", "type": "change", "z": "551f568e.e62c18", "name": "Перемещение данных", "rules": [ { "t": "move", "p": "payload", "pt": "msg", "to": "originalpayload", "tot": "msg" }, { "t": "set", "p": "payload", "pt": "msg", "to": "topic", "tot": "msg" } ], "action": "", "property": "", "from": "", "to": "", "reg": false, "x": 410, "y": 100, "wires": [ [ "2c1ed738.58c7a8" ] ] }, { "id": "1fda0b91.6192f4", "type": "change", "z": "551f568e.e62c18", "name": "Письмо об ошибке", "rules": [ { "t": "set", "p": "topic", "pt": "msg", "to": "\"Ошибка в модуле телеметрии (\" & $now('[H01]:[m01]:[s01] [D01].[M01].[Y0001]', '+1000') & \")!\"", "tot": "jsonata" }, { "t": "set", "p": "payload", "pt": "msg", "to": "\"Название модуля:\\n\\n\\\"\" & error.source.name & \"\\\" ( id: \" & error.source.id & \", type: \" & error.source.type & \").\\n\\nТекст ошибки:\\n\\n\\\"\" & error.message & \"\\\"\\n\\nСкорее всего, для нового датчика не добавлено название таблицы!\"", "tot": "jsonata" } ], "action": "", "property": "", "from": "", "to": "", "reg": false, "x": 410, "y": 280, "wires": [ [ "f1dd250e.d302b8" ] ] }, { "id": "1332f40b.7fab7c", "type": "change", "z": "551f568e.e62c18", "name": "Создание таблицы", "rules": [ { "t": "set", "p": "topic", "pt": "msg", "to": "\"CREATE TABLE \\\"\" & payload & \"\\\" (_id INTEGER PRIMARY KEY AUTOINCREMENT, timestamp TEXT, temperature REAL, humidity REAL, pressureSt REAL, pressureSl REAL, pressureBt REAL, battery INTEGER, linkquality INTEGER)\"", "tot": "jsonata" } ], "action": "", "property": "", "from": "", "to": "", "reg": false, "x": 410, "y": 220, "wires": [ [ "6611f5d3.167edc", "8df1000b.ebdd1" ] ] }, { "id": "8df1000b.ebdd1", "type": "change", "z": "551f568e.e62c18", "name": "Письмо о создании таблицы", "rules": [ { "t": "set", "p": "topic", "pt": "msg", "to": "\"Добавлен новый датчик телеметрии (\" & $now('[H01]:[m01]:[s01] [D01].[M01].[Y0001]', '+1000') & \")!\"", "tot": "jsonata" }, { "t": "set", "p": "payload", "pt": "msg", "to": "\"Для нового датчика \\\"\" & payload & \"\\\" автоматически была создана таблица!\\n\\nЕсли необходимо изменить название таблицы и (или) датчика, добавьте соответствующую запись в блок \\\"Определение названий таблиц\\\". Будет создана новая таблица с корректным именем. Записанные ранее данные, если такие были, останутся в старой таблице!\"", "tot": "jsonata" } ], "action": "", "property": "", "from": "", "to": "", "reg": false, "x": 670, "y": 220, "wires": [ [ "f1dd250e.d302b8" ] ] }, { "id": "b86859a5.ed63b8", "type": "mqtt in", "z": "551f568e.e62c18", "name": "Датчик на улице", "topic": "zigbee2mqtt/0x00124b002268acb0", "qos": "2", "datatype": "json", "broker": "8745b931.133868", "x": 140, "y": 100, "wires": [ [ "d6aeb8ba.178f68", "c8c40256.0249b" ] ] }, { "id": "c8c40256.0249b", "type": "link out", "z": "551f568e.e62c18", "name": "На narodmon", "links": [ "d8544fd0.c205f", "cdbabee6.435e9" ], "x": 75, "y": 160, "wires": [] }, { "id": "257306e4.0dd02a", "type": "comment", "z": "551f568e.e62c18", "name": "Сохранение телеметрии в базу", "info": "", "x": 190, "y": 40, "wires": [] }, { "id": "f1dd250e.d302b8", "type": "link out", "z": "551f568e.e62c18", "name": "Отправка почты", "links": [ "7c896fde.621af", "e24d06b9.f12878" ], "x": 575, "y": 280, "wires": [] }, { "id": "ec90713c.79299", "type": "link in", "z": "551f568e.e62c18", "name": "", "links": [ "20b71be1.6e7934" ], "x": 775, "y": 40, "wires": [ [ "2c1ed738.58c7a8" ] ] }, { "id": "dbb6e10a.23844", "type": "sqlitedb", "db": "c:\\noderedDB\\telemetry.db", "mode": "RWC" }, { "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": "" } ]
Обсуждение