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

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


iot:software:node-red:flows:telemetry_saving

Это старая версия документа!


Сохранение телеметрии в Node-RED

Это многофункциональный поток Node-RED, совмещающий в себе функции сохранения данных с датчиков температуры и влажности Sonoff SNZB-02 в базу SQLite, выгрузки данных с уличного датчика в проект «Народный мониторинг» и резервного копирования базы на FTP-сервер.

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

Также есть небольшое дополнение для сохранения телеметрии с увлажнителя.

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

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

Дополнительные подпотоки

Эти подпотоки входят в состав кода ниже и загружать отдельно их не надо! Однако, если использовать эту цепочку без цепочки резервного копирования, необходимо заменить нижний узел «link out» на подпоток «sendmail».

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

Соответствующий узел подключается к MQTT-брокеру и следит за данными, поступающими с датчиков. Далее, определяются названия таблиц в базе данных и подготавливаются данные для записи. На следующем этапе данные сохраняются в соответствующую таблицу.

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

Пример базы данных

По ссылке пример создаваемой базы данных. Она же, актуальная телеметрия по Владивостоку с Апреля 2021 года. 8-)

Для просмотра, редактирования и постройки экспресс-графиков можно использовать DB Browser for SQLite.

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

telemetry-saving.json
[
    {
        "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": 540,
        "y": 40,
        "wires": [
            [
                "536fe262.24fb2c"
            ],
            [
                "27141a53.5a2246"
            ]
        ],
        "outputLabels": [
            "Первое",
            "Ошибка"
        ]
    },
    {
        "id": "3cfc87ed.72e3d8",
        "type": "subflow:38c9a069.18cd6",
        "z": "98ba2d02.5ee68",
        "name": "Отправка почты",
        "env": [],
        "x": 710,
        "y": 580,
        "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": 210,
        "y": 580,
        "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": 290,
        "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": 160,
        "y": 100,
        "wires": [
            [
                "25c0b13d.61990e"
            ]
        ]
    },
    {
        "id": "25c0b13d.61990e",
        "type": "sqlite",
        "z": "98ba2d02.5ee68",
        "mydb": "abf4792e.cd5078",
        "sqlquery": "msg.topic",
        "sql": "",
        "name": "Запрос на наличие записи в БД",
        "x": 200,
        "y": 160,
        "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": 650,
        "y": 160,
        "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": 220,
        "wires": [
            [
                "235c7484.7cd52c"
            ]
        ]
    },
    {
        "id": "235c7484.7cd52c",
        "type": "sqlite",
        "z": "98ba2d02.5ee68",
        "mydb": "abf4792e.cd5078",
        "sqlquery": "msg.topic",
        "sql": "",
        "name": "Добавление отсутствующей записи",
        "x": 210,
        "y": 280,
        "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": 540,
        "y": 280,
        "wires": [
            [
                "b903c102.384fe"
            ]
        ]
    },
    {
        "id": "b903c102.384fe",
        "type": "sqlite",
        "z": "98ba2d02.5ee68",
        "mydb": "abf4792e.cd5078",
        "sqlquery": "msg.topic",
        "sql": "",
        "name": "Выборка записей, которые не отправлялись",
        "x": 240,
        "y": 340,
        "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": 180,
        "y": 400,
        "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": 100,
        "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 & \" WHERE topic = \\\"\" & savedtopic & \"\\\"\"",
                "tot": "jsonata"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 700,
        "y": 460,
        "wires": [
            [
                "b4d28c09.f2007"
            ]
        ]
    },
    {
        "id": "b4d28c09.f2007",
        "type": "sqlite",
        "z": "98ba2d02.5ee68",
        "mydb": "abf4792e.cd5078",
        "sqlquery": "msg.topic",
        "sql": "",
        "name": "Установка флага отправки",
        "x": 180,
        "y": 520,
        "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": 460,
        "wires": [
            [
                "b13655cc.bdf9d8"
            ]
        ]
    },
    {
        "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": 340,
        "wires": []
    },
    {
        "id": "6611f5d3.167edc",
        "type": "sqlite",
        "z": "551f568e.e62c18",
        "mydb": "dbb6e10a.23844",
        "sqlquery": "msg.topic",
        "sql": "",
        "name": "Запись данных в базу",
        "x": 440,
        "y": 280,
        "wires": [
            []
        ]
    },
    {
        "id": "ff007eab.b7d18",
        "type": "change",
        "z": "551f568e.e62c18",
        "name": "Подготовка данных",
        "rules": [
            {
                "t": "set",
                "p": "topic",
                "pt": "msg",
                "to": "\"INSERT INTO \\\"\" & payload & \"\\\" (timestamp, temperature, humidity, battery, linkquality) VALUES (\\\"\" & $now('[Y0001].[M01].[D01] [H01]:[m01]:[s01]', '+1000') & \"\\\", \" & originalpayload.temperature & \", \" & originalpayload.humidity & \", \" & originalpayload.battery & \", \" & ( $type( originalpayload.linkquality ) = 'number' ? originalpayload.linkquality : null ) & \")\"",
                "tot": "jsonata"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 440,
        "y": 220,
        "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": "zigbee2mqtt/0x00124b0022670244",
                "fromt": "str",
                "to": "balcony",
                "tot": "str"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 470,
        "y": 160,
        "wires": [
            [
                "ff007eab.b7d18",
                "34dfd14f.ad355e"
            ]
        ]
    },
    {
        "id": "94ebbe09.ee7b",
        "type": "catch",
        "z": "551f568e.e62c18",
        "name": "Отлов ошибок SQLite",
        "scope": [
            "6611f5d3.167edc"
        ],
        "uncaught": false,
        "x": 160,
        "y": 280,
        "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": 340,
        "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": 480,
        "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": 190,
        "y": 400,
        "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, battery INTEGER, linkquality INTEGER)\"",
                "tot": "jsonata"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 430,
        "y": 340,
        "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": 640,
        "y": 400,
        "wires": [
            [
                "f1dd250e.d302b8"
            ]
        ]
    },
    {
        "id": "c23c9680.557e48",
        "type": "mqtt in",
        "z": "551f568e.e62c18",
        "name": "Датчик на балконе",
        "topic": "zigbee2mqtt/0x00124b0022670244",
        "qos": "2",
        "datatype": "json",
        "broker": "8745b931.133868",
        "x": 150,
        "y": 40,
        "wires": [
            [
                "d6aeb8ba.178f68"
            ]
        ]
    },
    {
        "id": "b86859a5.ed63b8",
        "type": "mqtt in",
        "z": "551f568e.e62c18",
        "name": "Датчик на улице",
        "topic": "zigbee2mqtt/0x00124b002268acb0",
        "qos": "2",
        "datatype": "json",
        "broker": "8745b931.133868",
        "x": 420,
        "y": 40,
        "wires": [
            [
                "d6aeb8ba.178f68",
                "c8c40256.0249b"
            ]
        ]
    },
    {
        "id": "c8c40256.0249b",
        "type": "link out",
        "z": "551f568e.e62c18",
        "name": "На narodmon",
        "links": [
            "d8544fd0.c205f",
            "cdbabee6.435e9"
        ],
        "x": 575,
        "y": 40,
        "wires": []
    },
    {
        "id": "f1dd250e.d302b8",
        "type": "link out",
        "z": "551f568e.e62c18",
        "name": "Отправка почты",
        "links": [
            "7c896fde.621af"
        ],
        "x": 395,
        "y": 400,
        "wires": []
    },
    {
        "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": ""
    }
]

Обсуждение

Ваш комментарий:
P K C L K Y F D V L T Y D K U W
 
Последнее изменение: 2022/02/16 21:20 — Николай Солошин