Skip to content
08 · IoT-интеграция

08 · IoT-интеграция

IoT: встраиваем ферму в существующий стенд

Ключевая идея — ничего не ломать. Ваш стенд уже умеет датчик → MQTT → Node-RED → TimescaleDB → Grafana и командовать актуаторами через cmd/<hw_id>/set + квитанцию ack. Ферма — просто новые узлы в этой же схеме.

    flowchart LR
    ESP["ESP32 фермы"] -->|"wifi/hydro/&lt;node&gt;/&lt;metric&gt;"| M["Mosquitto"]
    M --> NR["Node-RED<br/>парсит и вставляет"]
    NR -->|INSERT| DB[("TimescaleDB<br/>readings")]
    DB --> G["Grafana<br/>дашборд фермы"]
    NR -->|"cmd/&lt;hw_id&gt;/set"| M
    M --> ESP
    ESP -->|"cmd/&lt;hw_id&gt;/ack"| M
  
Почему это методически ценно. Ученик не строит «игрушку в вакууме», а подключается к настоящей инфраструктуре с принципом наименьших привилегий (ACL), провижинингом и воспроизводимостью (контейнеры-quadlet). Он видит, как телеметрия и обратное управление разведены по правам: датчик шлёт свою метрику и слушает только свою команду; командует — одна роль. Это переносимая инженерная культура, а не только «помигать светодиодом».

Соглашение о топиках (в стиле существующего стенда)

Телеметрия (устройство → брокер), совместимо с текущим wifi/#:

wifi/hydro/<node_id>/<metric>      payload = число (или JSON {"v":..,"ts":..})

Примеры метрик: ph, ec, tds, water_temp, level_mm, air_temp, air_hum, pressure, lux, height_mm, spec_415spec_nir.

Команды (Node-RED → устройство и обратно), как в acl-with-commands.conf:

cmd/<hw_id>/set    <- команда актуатору (кто МОЖЕТ слать — узко: nodered)
cmd/<hw_id>/ack    -> устройство подтверждает исполнение

Примеры hw_id: hydro-pump-main, hydro-dose-phdown, hydro-dose-phup, hydro-dose-fe, hydro-led.

Схема данных (совместима с текущей readings)

Ферма пишет в ту же таблицу readings(time, node_id, metric, value, transport) и справочник devices(node_id, label). Ничего пересоздавать не нужно — только досеять устройства фермы:

Файл hydro-seed.sql — аддитивный, безопасно выполняется на живой БД: добавляет узлы фермы в devices, создаёт удобное представление hydro_latest и напоминает про грант grafana_ro.

ACL: добавляем узлы фермы, сохраняя наименьшие привилегии

Файл acl-hydro-additions.conf — строки, которые дописываются к вашему acl.conf. Датчик фермы пишет только свою телеметрию и слушает только свою команду; командир — по-прежнему nodered.

Разбор безопасности (занятие 9). Как отмечено в комментариях вашего acl-with-commands.conf, cmd/+/set разрешает узлу видеть и чужие команды — для доверенной LAN учебного стенда допустимо, но в «проде» сужают до конкретного hw_id (через client-id %c или отдельные строки на устройство). На занятии ученики сами находят эту дыру и предлагают сужение — это ровно тот «отдельный разбор в L02», что заложен в конфиге.

Node-RED: парсер и вставка

Функция nodered-hydro-insert.js разбирает топик wifi/hydro/<node>/<metric>, приводит payload к числу и формирует объект для узла Postgres (INSERT INTO readings ...). Дозирование/управление насосом идёт отдельным flow, публикующим cmd/<hw_id>/set и ожидающим ack (таймаут → тревога).

Занятие 6–7 · От датчика к графику (worked example, к Ц3)

Микроцель зан. 6: прочитать физвеличину с датчика. Зан. 7: довести её до Grafana.

Шаг 1. Прочитать датчик

Аналоговые pH/TDS — через ADC1 ESP32 (см. ТБ по ADC2/Wi-Fi в разделе 04). I²C-датчики — по адресам из карты шины. Убедиться, что значение печатается в монитор порта и физически осмысленно (pH ~6, EC ~1,5).

Шаг 2. Опубликовать в MQTT

Подключиться к брокеру (<COMPUTE_IP>:1883, логин/пароль узла), публиковать в wifi/hydro/<node>/<metric>. Проверить mosquitto_sub -t 'wifi/hydro/#'.

Шаг 3. Увидеть в БД и Grafana

Node-RED вставил строку в readings → открыть дашборд фермы, значение появилось на графике «свежести узлов». Если «молчит» — идём по цепочке назад (тема, ACL, Node-RED, БД).

Прошивка-шаблон: esp32-hydro-node.ino — Wi-Fi + MQTT, чтение датчиков, публикация телеметрии, приём команды с ответом ack и блокировкой сухого хода по уровню.

Занятие 9 · Актуаторы и безопасное дозирование

Насос/свет управляются через LR7843 (GPIO → затвор). Дозирование pH/раствора — перистальтическими насосами по команде cmd/.../set с обязательной квитанцией ack. Логика: команда → исполнение → подтверждение → запись. Нет ack в таймаут → тревога и остановка (защита от «залил весь бак кислотой»).

Уровень ≠ расстояние (worked example, 10 мин)

Дальномер стоит сверху и меряет расстояние до поверхности воды. Уровень воды — величина обратная:

уровень = высота_датчика − расстояние

Полный бак → расстояние маленькое. Пустой бак → расстояние большое. Если подставить в защиту расстояние вместо уровня, условие «мало воды» сработает на полном баке, а на пустом насос будет спокойно работать всухую — защита не просто не поможет, она сработает наоборот. В прошивке этот пересчёт живёт в одной функции updateLevel(), и только там; SENSOR_HEIGHT_MM нужно измерить рулеткой и вписать (занятие 5).

Три состояния, а не два. У датчика уровня есть не только «много воды» и «мало воды», но и «ответа нет» (отключился, вне диапазона, слепая зона). Защитный контур обязан быть fail-closed: нет свежего достоверного замера → насос не включается. Типичная ошибка — вернуть -1 при отказе и сравнить его с порогом как число: -1 < 60 даёт «мало воды», а вот проверка если значение ≥ 0 И значение < порога при -1 пропускает блокировку, то есть отказ датчика превращается в разрешение качать. В прошивке поэтому три поля — значение, возраст замера и признак достоверности (levelValid), а решение принимает единственная функция levelPermitsPumping().
ТБ актуаторов. Дозирование кислоты/щёлочи — малыми шагами с паузой на перемешивание и перезамер (pH инерционен). Блокировка сухого хода насоса по датчику уровня — в прошивке, не «на честном слове», и fail-closed (см. выше). Разовая доза ограничена сверху в коде (DOSE_MAX_MS) — команда извне не должна уметь запросить десять минут подачи кислоты. Дозирование неблокирующее: насос гасит loop(), а не delay() внутри обработчика, иначе во время дозы устройство не принимает команду «стоп». При потере брокера все актуаторы обесточиваются. Насосы и свет — 12 В; общий минус ESP32 и силовой шины соединён; провода к баку — с петлёй-капельником.

Grafana (полируем позже)

Готовый дашборд фермы: hydro-dashboard.json — переработан из вашего iot-overview.json (переменные $metric/$node, таблица «свежести», временной ряд, последнее значение). На этапе прототипа достаточно, чтобы данные доезжали и были видны; тонкую настройку панелей и тревог оставляем на потом — как вы и просили.

Уровни сложности