ГлавнаяАкадемияДатчики и входы: нормализация сигналов → Практика: Чтение состояния дискретного входа и отправка в MQTT

Практика: Чтение состояния дискретного входа и отправка в MQTT

Урок 6 · Датчики и входы: нормализация сигналов · 30 мин · theory

Цель урока: от физического контакта к топику MQTT

На предыдущих занятиях мы рассмотрели теоретические основы: чем физический сигнал отличается от логического события, как правильно выполнять подключение датчиков типа "сухой контакт" и почему так важен единый контракт сообщения. Сегодня мы объединим все эти знания в единый практический сценарий.

Наша задача — построить полную технологическую цепочку, которая начинается с простейшего физического действия (например, открытия двери с установленным на ней герконом) и заканчивается публикацией структурированного, осмысленного сообщения в MQTT брокере. Это сообщение, в свою очередь, может быть использовано десятками других системных компонентов: логическими сценариями, панелями визуализации, системами оповещения или архиваторами данных.

> 🔗 Связанный материал: Для успешного выполнения этого урока убедитесь, что вы усвоили материал из уроков "Базовые схемы подключения: 'сухой контакт' и датчики с выходом напряжения" (COURSE-04-M01-L04) и "Контракт сообщения: структура JSON для событий от датчиков" (COURSE-04-M01-L06).

Полная цепочка прохождения сигнала выглядит следующим образом:

  • Датчик: Геркон (магнитоконтактный датчик) или настенная кнопка замыкает или размыкает свои контакты. Это — физический сигнал.
  • Клеммы контроллера: Сигнал поступает на одну из универсальных клемм (Universal Input, UI) контроллера HI и его общую клемму (GND).
  • Драйвер входа: Аппаратно-программная часть контроллера, предварительно настроенная нами, преобразует изменение электрического состояния (наличие/отсутствие замыкания) в логическое событие `true/false`. На этом же этапе происходит фильтрация помех (антидребезг).
  • Нода Node-RED (`hi-di`): Специализированная нода в среде Node-RED подхватывает это событие от драйвера и передает его в виде сообщения `msg` в наш поток (flow).
  • Нода-преобразователь (`change` или `function`): Мы преобразуем примитивное сообщение (`true/false`) в стандартизированный JSON-объект, обогащая его метаданными: идентификатором датчика, временной меткой и т.д.
  • Нода MQTT (`mqtt out`): Финальный узел нашего потока отправляет (публикует) сформированный JSON-объект в определенный топик на MQTT брокере.
  • MQTT Брокер: Центральный узел обмена сообщениями, который доставляет наше событие всем, кто на него подписан.
  • Ключевая цель этого процесса — обеспечение слабой связанности (decoupling) компонентов. Система, получающая данные из MQTT, не должна ничего знать о том, какой именно датчик (геркон, кнопка, датчик протечки), на какой клемме контроллера и по какой схеме был подключен. Она оперирует только стандартизированными логическими сообщениями. Это позволяет в будущем легко заменять физические датчики, переносить их на другие контроллеры или даже имитировать их работу программно без необходимости переписывать всю логику системы.

    Напомним, что для подключения простейшего датчика типа "сухой контакт" (например, геркона на двери) мы используем два провода. Один проводник подключается к сигнальной клемме универсального входа (например, UI-01), а второй — к общей клемме контроллера (GND). При замыкании контактов геркона вход UI-01 будет соединен с землей.

    ---

    Конфигурация универсального входа (UI) в режиме дискретного (DI)

    Прежде чем мы сможем прочитать состояние входа в Node-RED, его необходимо правильно сконфигурировать на уровне операционной системы контроллера. Это гарантирует, что аппаратная часть будет корректно интерпретировать электрические сигналы. Эта настройка выполняется через веб-интерфейс администратора контроллера HI.

    Пошаговая настройка входа:

  • Доступ к веб-интерфейсу: Откройте браузер и введите IP-адрес вашего контроллера HI. После авторизации перейдите в раздел "Настройки оборудования" или "Конфигурация вводов-выводов".
  • Выбор физического входа: Вы увидите список всех доступных входов и выходов. Найдите нужный нам универсальный вход, к которому физически подключен датчик, например, UI-01.
  • Установка режима работы: Для каждого универсального входа доступен выпадающий список с режимами. Выберите режим "Дискретный вход" (Discrete Input). Это сообщит контроллеру, что данный порт должен работать с бинарными состояниями: замкнуто/разомкнуто.
  • Настройка антидребезга (Debounce): Это один из самых важных параметров для дискретных входов. Любой механический контакт (кнопка, реле, геркон) в момент замыкания или размыкания создает серию очень быстрых, хаотичных импульсов — это явление называется дребезгом контактов. Если не отфильтровать этот "шум", система может зарегистрировать одно нажатие как десятки срабатываний.
  • * Параметр `debounce` задает время в миллисекундах, в течение которого драйвер входа игнорирует последующие изменения состояния после первого.

    * Установите значение в диапазоне 50-100 мс. Этого достаточно для большинства бытовых датчиков и кнопок.

    > 💡 Подсказка: Для большинства кнопок и герконов оптимальное значение debounce находится в диапазоне 50-100 мс. Слишком малое значение (например, 10 мс) может привести к многократным срабатываниям от дребезга, а слишком большое (например, 500 мс) — к ощутимой задержке реакции системы.

  • Настройка подтяжки (Pull Mode): Этот параметр определяет поведение входа, когда контакты датчика разомкнуты ("висят в воздухе").
  • * 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`.

    Создание потока чтения:

  • Добавление ноды на холст: В палитре нод слева найдите узел `hi-di` (обычно в секции HI Platform) и перетащите его на рабочую область.
  • Базовая настройка ноды `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` идеально подходит для статических преобразований структуры сообщения.

  • Добавление и подключение: Добавьте ноду `change` на холст и разместите её между нодой `hi-di` и `debug`.
  • Настройка правил: Откройте настройки ноды `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()` сгенерирует текущую временную метку в миллисекундах.

  • Развертывание и проверка: Нажмите `Deploy`. Теперь при срабатывании датчика в окне отладки вы будете видеть полноценный JSON-объект.
  • ### Метод 2: Альтернатива с нодой `function` (гибкий подход)

    Нода `function` позволяет писать JavaScript-код, что дает гораздо больше гибкости для сложной логики.

  • Замена ноды: Удалите ноду `change` и поставьте на её место ноду `function`.
  • Написание кода: Вставьте в редактор кода следующий JS-сниппет:
  •     // Исходное значение от ноды 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`.
  • ### Сравнение подходов

    | Критерий | Нода `change` | Нода `function` | Рекомендация |

    | :--- | :--- | :--- | :--- |

    | Сложность | Низкая | Средняя | Для простых преобразований `change` предпочтительнее. |

    | Наглядность | Высокая (визуальные правила) | Низкая (нужно читать код) | `change` легче для быстрой диагностики потока. |

    | Гибкость | Ограниченная | Очень высокая | Для условной логики (if/else), циклов, сложных вычислений — только `function`. |

    | Производительность | Высокая | Незначительно ниже | Разница в производительности на современных контроллерах несущественна. |

    Для нашей текущей задачи оба подхода равнозначны, но для начинающих инженеров рекомендуется освоить оба.

    ---

    Публикация сообщения о состоянии в топик MQTT

    Финальный шаг — отправка нашего стандартизированного сообщения в MQTT брокер, делая его доступным для всей системы.

    Настройка ноды `mqtt out`:

  • Добавление ноды: Найдите в палитре узел `mqtt out` и поместите его в конец нашего потока, после ноды `change` или `function`.
  • Настройка соединения с брокером:
  • * Дважды кликните по ноде. В поле `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

  • Установка и запуск: Скачайте и установите MQTT Explorer — это удобная утилита для визуализации активности на MQTT брокере.
  • Подключение: Запустите программу и создайте новое подключение к вашему брокеру (указав его IP-адрес, порт `1883` и, если нужно, учетные данные).
  • Подписка и проверка: MQTT Explorer автоматически подписывается на все топики (`#`). В левой части экрана вы увидите иерархию топиков. Найдите свой топик: `hi/objects/floor-1/main_entrance/door/state`.
  • Анализ сообщения: Кликните на топик. Справа вы увидите последнее полученное сообщение. Откройте и закройте дверь несколько раз. Вы должны видеть, как поле `value` в JSON-сообщении меняется с `true` на `false`, а `ts` (timestamp) обновляется при каждом изменении.
  • Альтернатива - консольная утилита `mosquitto_sub`:

    Если вы работаете в терминале 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) контроллера и преобразовывать их в реальные физические величины (люксы, проценты и т.д.).