ГлавнаяАкадемияОсновы умного дома → От сигнала к событию: контракт сообщения

От сигнала к событию: контракт сообщения

Урок 1 · Основы умного дома · 30 мин · theory
id: COURSE-01-M02-L02

title: "От сигнала к событию: контракт сообщения"

level: foundation

course: COURSE-01

module: COURSE-01-M02

tags: [mqtt, node-red, json, data-model, events]

prerequisites:

- COURSE-01-M02-L01

version: 1.0.0

status: published

learning_outcomes:

- "Объяснять разницу между 'сигналом' и 'событием' в контексте умного дома."

- "Проектировать базовый 'контракт сообщения' для передачи данных с устройств."

- "Использовать ноду 'function' в Node-RED для преобразования сырых данных в структурированный объект события."

- "Понимать важность стандартизации формата сообщений для надежности системы."

---

# Урок COURSE-01-M02-L02: От сигнала к событию: контракт сообщения

Введение: от сигнала к событию

> 🔗 Связанный материал: Для полного понимания этой секции необходимо изучить материал урока COURSE-01-M02-L01 "Что такое сигнал: аналоговый vs дискретный".

В предыдущем уроке мы установили, что сигнал — это сырое, необработанное значение, поступающее от аппаратного входа контроллера. Например, значение `1` от датчика движения (геркона), `0` от датчика протечки, или `23.5` от датчика температуры.

Сигналы являются фундаментом автоматизации, но сами по себе они несут ограниченную информацию. Рассмотрим простой сигнал `1` от дискретного входа. Что он означает?

Без дополнительной информации (контекста) система не может принять верное решение. Сигнал отвечает на вопрос «Что?», но оставляет без ответа критически важные вопросы: «Где?», «Когда?» и «Откуда?».

В этом заключается ключевое различие между сигналом и событием.

📋 Ключевые понятия:

Использование неструктурированных сигналов напрямую в сценариях автоматизации приводит к созданию хрупких и трудноподдерживаемых систем. Каждый раз, когда вы захотите использовать данные от датчика, вам придется «вспоминать», что означает `1` для этого конкретного устройства, в каком топике оно публикуется и как его обработать.

Контракт сообщения решает эту проблему, вводя единый стандарт. Вместо того чтобы каждый раз интерпретировать сырой сигнал, мы преобразуем его в структурированное событие один раз — на входе в систему. Все последующие сценарии работают уже с предсказуемым, понятным и самодостаточным объектом данных.

---

Анатомия события в MQTT: топик и полезная нагрузка (payload)

Как мы обсуждали ранее, протокол MQTT является транспортным уровнем для обмена сообщениями в нашей экосистеме. Каждое сообщение в MQTT состоит из двух основных частей: топика (topic) и полезной нагрузки (payload). При создании системы событий мы используем обе эти части для передачи контекста.

1. Топик (Topic) — Адрес События

Топик — это иерархическая строка, которая действует как адрес или канал для сообщения. Правильное проектирование иерархии топиков — это первый и важнейший шаг к созданию читаемой и масштабируемой системы. Мы придерживаемся следующей структуры:

`/префикс/локация/устройство/параметр`

Пример:

> 💡 Подсказка: Проектируйте иерархию топиков так, чтобы любой инженер, даже не знакомый с проектом, мог по названию топика понять, где находится устройство и за что оно отвечает. Это значительно упрощает отладку и поддержку.

2. Полезная нагрузка (Payload) — Содержание События

Если топик — это адрес на конверте, то полезная нагрузка — это само письмо. В `payload` мы помещаем детальную информацию о событии в стандартизированном формате. Для сырых сигналов `payload` часто содержит одно-единственное значение (`22.3`, `1`, `true`), но для структурированных событий мы всегда используем формат JSON (JavaScript Object Notation).

В Node-RED эти два компонента представлены в объекте `msg`:

Связь между ними позволяет создавать мощную логику. Мы можем использовать `msg.topic` для маршрутизации, а `msg.payload` — для выполнения условных действий.

---

Контракт сообщения: стандартная структура msg.payload

Чтобы система была предсказуемой, мы вводим строгий контракт сообщения для всех событий, циркулирующих в домене `/events`. Все полезные нагрузки в этом домене должны быть в формате JSON и соответствовать следующей структуре.

Формат Полезной Нагрузки (Payload)

Мы используем JSON-объект со следующими полями:

| Поле | Тип | Описание | Обязательность |

|------|------|------------|----------------|

| `ts` | String | Временная метка события в формате ISO 8601 (например, `2023-10-27T12:35:01.123Z`). Используем UTC (Z) для однозначности. | Обязательное |

| `val`| any | Непосредственно значение сигнала (число, строка, булево). | Обязательное |

| `src`| String | Источник данных. Обычно это топик сырого сигнала (например, `/devices/wb-m1w2_34/temperature`). Критически важно для отладки. | Обязательное |

| `unit`| String | Единица измерения (`°C`, `ppm`, `%`, `V`). | Рекомендуемое |

| `type`| String | Тип данных (`float`, `integer`, `boolean`, `string`). Помогает избежать ошибок при обработке. | Рекомендуемое |

| `meta`| Object | Объект с дополнительными метаданными, которые могут быть полезны для сценариев (например, имя комнаты, ID пользователя, и т.д.). | Опциональное |

Пример 1: Событие от датчика температуры

Сырой сигнал: топик `/devices/wb-msw-v3_25/temperature`, payload `24.8`.

Стандартизированное событие (payload в топике `/events/office/floor1/room101/climate/temperature`):

{

"ts": "2023-10-27T12:40:15.589Z",

"val": 24.8,

"src": "/devices/wb-msw-v3_25/temperature",

"unit": "°C",

"type": "float",

"meta": {

"room_name": "Переговорная 'Озеро'"

}

}

Этот объект самодостаточен. Любой сценарий, получивший его, немедленно знает:

Пример 2: Событие от датчика движения (геркона на двери)

Сырой сигнал: топик `/devices/wb-mdm3_50/Input 1`, payload `1`.

Стандартизированное событие (payload в топике `/events/office/main_entrance/security/door_opened`):

{

"ts": "2023-10-27T12:42:01.012Z",

"val": true,

"src": "/devices/wb-mdm3_50/Input 1",

"type": "boolean",

"meta": {

"device_type": "геркон"

}

}

Обратите внимание, как мы преобразовали `1` в более понятное логическое `true`. Это упрощает написание сценариев: вместо `if (msg.payload.val == 1)` можно писать `if (msg.payload.val)`.

---

Практика в Node-RED: преобразование сигнала в событие

Теперь создадим поток в Node-RED, который реализует наш контракт. Он будет слушать сырые сигналы от оборудования, преобразовывать их в стандартизированные события и публиковать в "чистый" топик.

Сценарий:

Наш контроллер получает данные от датчика температуры, подключенного через модуль расширения. Драйвер `wb-mqtt-serial` публикует температуру в виде строки в топик `/devices/wb-ms_v2_28/temperature`.

Задача:

Создать поток, который:

  • Подписывается на топик `/devices/wb-ms_v2_28/temperature`.
  • Преобразует входящее сырое значение в JSON-объект согласно нашему контракту.
  • Публикует новое, структурированное событие в топик `/events/lab/room1/climate/temperature`.
  • Структура потока (Flow):
    [MQTT In]────────>[Function]────────>[MQTT Out]
    

    └─────────────>[Debug: Raw]

    [Debug: Event]─────┘ (подключен к выходу Function)

    Шаг 1: Настройка узла `MQTT In` Шаг 2: Настройка узла `Function` (ядро преобразования)

    Это самый важный узел. Он содержит JavaScript-код, который выполняет преобразование.

    // Сохраняем исходный топик для поля 'src'
    

    const sourceTopic = msg.topic;

    // Извлекаем сырое значение. MQTT-драйверы часто присылают

    // значения в виде строки, поэтому преобразуем его в число.

    // parseFloat является безопасным способом преобразования.

    const rawValue = parseFloat(msg.payload);

    // Проверяем, удалось ли преобразование. Если нет - прерываем поток.

    // Это базовая валидация данных.

    if (isNaN(rawValue)) {

    node.warn("Не удалось преобразовать payload в число: " + msg.payload);

    return null; // Останавливаем дальнейшую обработку сообщения

    }

    // 1. Создаем новый объект msg.payload согласно нашему контракту.

    const eventPayload = {

    // 'ts': Временная метка в формате ISO 8601 (UTC)

    ts: new Date().toISOString(),

    // 'val': Числовое значение температуры

    val: rawValue,

    // 'src': Исходный топик для отладки

    src: sourceTopic,

    // 'unit' и 'type': Добавляем важные метаданные

    unit: "°C",

    type: "float"

    };

    // 2. Заменяем старый msg.payload на наш новый объект события.

    msg.payload = eventPayload;

    // 3. Устанавливаем новый топик для публикации события.

    msg.topic = "/events/lab/room1/climate/temperature";

    // Возвращаем измененный объект msg для передачи следующему узлу.

    return msg;

    > ⚠️ Внимание: Всегда проверяйте и приводите типы данных. Значение, пришедшее как строка (`'25.5'`), должно быть преобразовано в число (`25.5`) с помощью функций `parseFloat()` или `parseInt()`. Иначе логика сценариев, ожидающая числовые операции (например, `> 25`), может работать некорректно или вызывать ошибки.

    Шаг 3: Настройка узла `MQTT Out` Шаг 4: Настройка узлов `Debug`
  • Debug: Raw: Подключите к выходу `MQTT In`. Установите вывод на `полный объект сообщения`. Это позволит вам видеть исходный, сырой сигнал.
  • Debug: Event: Подключите к выходу `Function`. Установите вывод на `полный объект сообщения`. Это покажет вам результат работы — готовый объект события.
  • Разверните поток (Deploy). Теперь, когда в топик `/devices/wb-ms_v2_28/temperature` придет сообщение (например, `24.5`), вы увидите в панели отладки два вывода:

    ---

    Заключение: ценность контракта и следующие шаги

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

    Ключевые выводы:

    Инвестиции времени в проектирование и внедрение контрактов сообщений окупаются многократно на этапе эксплуатации и расширения системы. Вы получаете предсказуемую среду, где все компоненты "говорят" на одном языке.

    Что дальше?

    Теперь, когда у нас есть поток стандартизированных, понятных событий, мы готовы использовать их для создания реальной автоматизации.

    🔗 Следующий урок: COURSE-01-M03-L01 "Базовые сценарии: реакция на события", где мы научимся создавать логику, которая реагирует на наши структурированные события для управления освещением, климатом и другими системами.