Практика: Чтение состояния дискретного входа и отправка в MQTT
Цель урока: от физического контакта к топику MQTT
На предыдущих занятиях мы рассмотрели теоретические основы: чем физический сигнал отличается от логического события, как правильно выполнять подключение датчиков типа "сухой контакт" и почему так важен единый контракт сообщения. Сегодня мы объединим все эти знания в единый практический сценарий.
Наша задача — построить полную технологическую цепочку, которая начинается с простейшего физического действия (например, открытия двери с установленным на ней герконом) и заканчивается публикацией структурированного, осмысленного сообщения в MQTT брокере. Это сообщение, в свою очередь, может быть использовано десятками других системных компонентов: логическими сценариями, панелями визуализации, системами оповещения или архиваторами данных.
> 🔗 Связанный материал: Для успешного выполнения этого урока убедитесь, что вы усвоили материал из уроков "Базовые схемы подключения: 'сухой контакт' и датчики с выходом напряжения" (COURSE-04-M01-L04) и "Контракт сообщения: структура JSON для событий от датчиков" (COURSE-04-M01-L06).
Полная цепочка прохождения сигнала выглядит следующим образом:
Ключевая цель этого процесса — обеспечение слабой связанности (decoupling) компонентов. Система, получающая данные из MQTT, не должна ничего знать о том, какой именно датчик (геркон, кнопка, датчик протечки), на какой клемме контроллера и по какой схеме был подключен. Она оперирует только стандартизированными логическими сообщениями. Это позволяет в будущем легко заменять физические датчики, переносить их на другие контроллеры или даже имитировать их работу программно без необходимости переписывать всю логику системы.
Напомним, что для подключения простейшего датчика типа "сухой контакт" (например, геркона на двери) мы используем два провода. Один проводник подключается к сигнальной клемме универсального входа (например, UI-01), а второй — к общей клемме контроллера (GND). При замыкании контактов геркона вход UI-01 будет соединен с землей.
---
Конфигурация универсального входа (UI) в режиме дискретного (DI)
Прежде чем мы сможем прочитать состояние входа в Node-RED, его необходимо правильно сконфигурировать на уровне операционной системы контроллера. Это гарантирует, что аппаратная часть будет корректно интерпретировать электрические сигналы. Эта настройка выполняется через веб-интерфейс администратора контроллера HI.
Пошаговая настройка входа:
* Параметр `debounce` задает время в миллисекундах, в течение которого драйвер входа игнорирует последующие изменения состояния после первого.
* Установите значение в диапазоне 50-100 мс. Этого достаточно для большинства бытовых датчиков и кнопок.
> 💡 Подсказка: Для большинства кнопок и герконов оптимальное значение debounce находится в диапазоне 50-100 мс. Слишком малое значение (например, 10 мс) может привести к многократным срабатываниям от дребезга, а слишком большое (например, 500 мс) — к ощутимой задержке реакции системы.
* Pull-up (Подтяжка к питанию): Вход внутренне подключается к напряжению питания через резистор. В разомкнутом состоянии он будет считывать логическую "1" (`true`). При замыкании на GND он будет считывать логический "0" (`false`). Это стандартный и рекомендуемый режим для схемы "сухой контакт на GND".
* Pull-down (Подтяжка к земле): Вход внутренне подключается к GND. В разомкнутом состоянии он считывает "0" (`false`), а при замыкании на +V — "1" (`true`).
* None (Без подтяжки): Используется, если у датчика есть собственный выход с активным уровнем (например, выход типа "открытый коллектор" с внешним pull-up резистором). Для простого "сухого контакта" этот режим использовать нельзя.
Для нашей стандартной схемы подключения геркона (один контакт на UI-01, второй на GND), выберите режим Pull-up.
После выполнения этих шагов аппаратная часть контроллера готова к работе. Теперь мы можем перейти в среду Node-RED для создания логики.
---
Чтение состояния входа в среде Node-RED
Для взаимодействия с аппаратными возможностями контроллера HI используется специальный набор нод `node-red-contrib-hi`. Для чтения состояния дискретного входа нам понадобится нода `hi-di`.
Создание потока чтения:
* Дважды кликните по ноде, чтобы открыть окно её настроек.
* Pin: В выпадающем списке выберите тот физический порт, который мы настроили на предыдущем шаге (например, `UI-01`). Список автоматически подгружается из конфигурации контроллера.
* Trigger on: Этот параметр определяет, на какое событие будет реагировать нода:
* `both edges` (по умолчанию): Нода будет отправлять сообщение при любом изменении состояния (и при замыкании, и при размыкании).
* `rising edge`: Только при переходе из `false` в `true` (из разомкнутого в замкнутое, если используется Pull-down).
* `falling edge`: Только при переходе из `true` в `false` (из замкнутого в разомкнутое, если используется Pull-up).
Для начала оставим значение `both edges`.
* Name: Дайте ноде осмысленное имя, например, "Датчик двери (UI-01)". Это улучшает читаемость потока.
По умолчанию нода `hi-di` формирует очень простое сообщение. Его свойство `msg.payload` будет содержать булево значение:
* `true` — если вход в состоянии логической единицы.
* `false` — если вход в состоянии логического ноля.
> ⚠️ Внимание: Конкретное значение (`true` или `false`) для замкнутого состояния зависит от выбранного режима подтяжки (Pull-up/Pull-down). При стандартной схеме (Pull-up, замыкание на GND) замкнутое состояние будет соответствовать `false`, а разомкнутое — `true`. Это может показаться неинтуитивным, и мы исправим это на следующем шаге.
* Перетащите на холст ноду `debug` из палитры.
* Соедините выход ноды "Датчик двери (UI-01)" с входом ноды `debug`.
* Нажмите кнопку Deploy (Развернуть) в правом верхнем углу.
* Откройте панель отладки (справа, иконка жука).
* Теперь, открывая и закрывая дверь (или замыкая/размыкая контакты вручную), вы должны видеть в панели отладки сообщения `true` и `false`.
Если вы видите эти сообщения, значит, первая часть задачи успешно выполнена: Node-RED получает события от физического датчика.
---
Преобразование данных и приведение к стандартному формату
Простое сообщение `true/false` не несет в себе достаточно информации. Чтобы другие компоненты системы могли его правильно интерпретировать, мы должны преобразовать его в структурированный JSON-объект, соответствующий нашему контракту сообщения.
> 🔗 Связанный материал: Структура объекта сообщения подробно описана в уроке "Контракт сообщения: структура JSON для событий от датчиков" (COURSE-04-M01-L06).
Напомним целевую структуру:
{
"value": true,
"source": "door-sensor-main",
"ts": 1678886400000,
"meta": {
"type": "door_contact",
"location": "main_entrance"
}
}
Для такого преобразования в Node-RED есть два основных инструмента: нода `change` и нода `function`.
### Метод 1: Использование ноды `change` (без кода)
Нода `change` идеально подходит для статических преобразований структуры сообщения.
* Правило 1: Инвертирование и перемещение значения. Как мы выяснили, при Pull-up и замыкании на GND мы получаем `false`. Для логики "дверь закрыта" это `true`. Мы можем инвертировать это.
* `Set` `msg.payload`
* `to the value` `expression` (J:)
* В поле выражения впишите: `not payload`. Это инвертирует булево значение.
* Правило 2: Формирование структуры. Теперь создадим JSON.
* `Set` `msg.payload`
* `to the value` `JSONata` (J:)
* В поле выражения вставьте:
{
"value": payload,
"source": "door-sensor-main",
"ts": $millis(),
"meta": {
"type": "door_contact",
"location": "main_entrance"
}
}
Здесь `payload` подставит инвертированное значение из предыдущего шага, а функция `$millis()` сгенерирует текущую временную метку в миллисекундах.
### Метод 2: Альтернатива с нодой `function` (гибкий подход)
Нода `function` позволяет писать JavaScript-код, что дает гораздо больше гибкости для сложной логики.
// Исходное значение от ноды hi-di (true/false)
let raw_state = msg.payload;
// Инвертируем значение, так как при Pull-up замыкание на GND дает 'false'
// Наша логика: true = дверь закрыта, false = дверь открыта
let logical_value = !raw_state;
// Формируем новый объект msg.payload согласно контракту
msg.payload = {
value: logical_value,
source: "door-sensor-main",
ts: Date.now(), // Date.now() возвращает timestamp в мс
meta: {
type: "door_contact",
location: "main_entrance"
}
};
// Также можно добавить топик для MQTT прямо здесь
msg.topic = "hi/objects/floor-1/main_entrance/door/state";
// Возвращаем измененное сообщение для дальнейшей передачи по потоку
return msg;
### Сравнение подходов
| Критерий | Нода `change` | Нода `function` | Рекомендация |
| :--- | :--- | :--- | :--- |
| Сложность | Низкая | Средняя | Для простых преобразований `change` предпочтительнее. |
| Наглядность | Высокая (визуальные правила) | Низкая (нужно читать код) | `change` легче для быстрой диагностики потока. |
| Гибкость | Ограниченная | Очень высокая | Для условной логики (if/else), циклов, сложных вычислений — только `function`. |
| Производительность | Высокая | Незначительно ниже | Разница в производительности на современных контроллерах несущественна. |
Для нашей текущей задачи оба подхода равнозначны, но для начинающих инженеров рекомендуется освоить оба.
---
Публикация сообщения о состоянии в топик MQTT
Финальный шаг — отправка нашего стандартизированного сообщения в MQTT брокер, делая его доступным для всей системы.
Настройка ноды `mqtt out`:
* Дважды кликните по ноде. В поле `Server` выберите `Add new mqtt-broker...` и нажмите на иконку карандаша.
* Name: Дайте имя соединению, например, `Local HI Broker`.
* Server: Укажите IP-адрес или hostname MQTT брокера. Для встроенного брокера на контроллере HI это `localhost` или `127.0.0.1`.
* Port: Стандартный порт MQTT — `1883`.
* При необходимости (если брокер требует аутентификации) перейдите на вкладку `Security` и введите имя пользователя и пароль.
* Нажмите `Add`, чтобы сохранить конфигурацию брокера.
* Topic: Это самое важное поле. Здесь нужно указать семантически осмысленный MQTT топик. Хорошая практика — использовать иерархическую структуру. Например: `hi/objects/floor-1/main_entrance/door/state`. Такой топик легко фильтровать и понимать его назначение. Если вы уже задали `msg.topic` в ноде `function`, можно оставить это поле пустым.
* QoS (Quality of Service): Уровень Гарантии Доставки.
* `0` - At most once (не более одного раза, возможна потеря). Самый быстрый, но ненадежный.
* `1` - At least once (не менее одного раза, возможны дубликаты). Хороший компромисс.
* `2` - Exactly once (ровно один раз). Самый надежный, но и самый медленный.
Для состояний датчиков рекомендуется использовать QoS = 1.
* Retain: Флаг "сохраняемого сообщения".
* Если `true`, брокер сохранит последнее сообщение в этом топике. Любой новый клиент, подписавшийся на этот топик, немедленно получит это сохраненное значение.
* Это полезно для датчиков состояния (дверь открыта/закрыта, свет включен/выключен), так как позволяет системе сразу узнать текущий статус объекта.
* Для событийных данных (нажатие кнопки звонка) этот флаг использовать нельзя.
Установите флаг Retain в значение `true` (`true` на выпадающем списке).
> ⚠️ Внимание: Флаг Retain (сохраняемое сообщение) следует использовать с осторожностью. Включайте его для датчиков, отражающих текущее состояние (например, датчик двери 'открыта/закрыта'), но никогда не используйте для событийных данных (например, нажатие кнопки звонка). Неправильное использование Retain может вызвать ложные срабатывания автоматики при перезагрузке MQTT клиентов.
Соедините выход ноды-преобразователя с входом ноды `mqtt out`. Ваш полный поток должен выглядеть так: `hi-di` -> `function` -> `mqtt out`. Нажмите Deploy.
Готово! Теперь каждое изменение состояния датчика двери будет публиковаться в MQTT.
---
Итоги и комплексная проверка
Мы прошли полный путь от аппаратной настройки до публикации логического события. Мы настроили универсальный вход контроллера, создали поток Node-RED для чтения его состояния, преобразовали сырые данные в стандартизированный JSON-формат и отправили их в MQTT брокер с корректными флагами QoS и Retain.
Теперь необходимо убедиться, что всё работает как единое целое. Для этого мы воспользуемся внешним инструментом для мониторинга MQTT.
Проверка с помощью MQTT Explorer
Если вы работаете в терминале Linux, вы можете подписаться на топик командой:
mosquitto_sub -h localhost -t "hi/objects/floor-1/main_entrance/door/state" -v
Опция `-v` выведет и топик, и сообщение. Результат будет выглядеть так:
hi/objects/floor-1/main_entrance/door/state {"value":false,"source":"door-sensor-main","ts":1678887100123,"meta":{"type":"door_contact","location":"main_entrance"}}
hi/objects/floor-1/main_entrance/door/state {"value":true,"source":"door-sensor-main","ts":1678887102456,"meta":{"type":"door_contact","location":"main_entrance"}}
Если вы видите эти сообщения, и их содержимое соответствует контракту — вы успешно справились с задачей. Вы создали надежный и стандартизированный источник данных для всей вашей системы автоматизации.
Что дальше?
В следующем уроке мы перейдем от бинарных сигналов к более сложным — аналоговым. Мы научимся подключать датчики с выходным напряжением (например, датчики освещенности 0-10В), считывать их показания с помощью аналоговых входов (AI) контроллера и преобразовывать их в реальные физические величины (люксы, проценты и т.д.).