08 · IoT-интеграция
IoT: встраиваем ферму в существующий стенд
Ключевая идея — ничего не ломать. Ваш стенд уже умеет датчик → MQTT → Node-RED → TimescaleDB → Grafana и командовать актуаторами через cmd/<hw_id>/set + квитанцию ack. Ферма — просто новые узлы в этой же схеме.
flowchart LR
ESP["ESP32 фермы"] -->|"wifi/hydro/<node>/<metric>"| M["Mosquitto"]
M --> NR["Node-RED<br/>парсит и вставляет"]
NR -->|INSERT| DB[("TimescaleDB<br/>readings")]
DB --> G["Grafana<br/>дашборд фермы"]
NR -->|"cmd/<hw_id>/set"| M
M --> ESP
ESP -->|"cmd/<hw_id>/ack"| M
Соглашение о топиках (в стиле существующего стенда)
Телеметрия (устройство → брокер), совместимо с текущим 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_415 … spec_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.
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).
-1 при отказе и сравнить его с порогом как число: -1 < 60 даёт «мало воды», а вот проверка если значение ≥ 0 И значение < порога при -1 пропускает блокировку, то есть отказ датчика превращается в разрешение качать. В прошивке поэтому три поля — значение, возраст замера и признак достоверности (levelValid), а решение принимает единственная функция levelPermitsPumping().DOSE_MAX_MS) — команда извне не должна уметь запросить десять минут подачи кислоты. Дозирование неблокирующее: насос гасит loop(), а не delay() внутри обработчика, иначе во время дозы устройство не принимает команду «стоп». При потере брокера все актуаторы обесточиваются. Насосы и свет — 12 В; общий минус ESP32 и силовой шины соединён; провода к баку — с петлёй-капельником.Grafana (полируем позже)
Готовый дашборд фермы: hydro-dashboard.json — переработан из вашего iot-overview.json (переменные $metric/$node, таблица «свежести», временной ряд, последнее значение). На этапе прототипа достаточно, чтобы данные доезжали и были видны; тонкую настройку панелей и тревог оставляем на потом — как вы и просили.