ГлавнаяАкадемияИсполнительные устройства: интерлоки, таймауты → Шаблон 'Команда -> Подтверждение (Ack) -> Таймаут'

Шаблон 'Команда -> Подтверждение (Ack) -> Таймаут'

Урок 1 · Исполнительные устройства: интерлоки, таймауты · 30 мин · theory

Введение: От 'отправил и забыли' к гарантированной доставке

🔗 Связанный материал: Для полного понимания проблемы, убедитесь, что вы изучили урок COURSE-05-M04-L01 'Проблема 'отправили и забыли''.

Как мы подробно рассмотрели в предыдущем уроке, подход "отправил и забыли" (Fire and Forget) является фундаментальным источником нестабильности и рассинхронизации состояния в любой системе автоматизации. Отправка команды без ожидания обратной связи превращает нашу систему в "черный ящик", где мы можем лишь надеяться, что команда была получена и исполнена. На практике это приводит к ситуациям, когда интерфейс пользователя показывает, что свет включен, а в реальности реле не сработало из-за кратковременного сбоя на шине данных или перезагрузки исполнительного устройства.

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

Для реализации гарантированной доставки мы будем использовать один из важнейших паттернов в автоматизации — шаблон "Команда -> Подтверждение (Ack) -> Таймаут". Этот шаблон является основой для построения отказоустойчивых взаимодействий между контроллером и исполнительными устройствами.

Можно провести аналогию с заключением юридического контракта:

  • Команда (Command): Контроллер отправляет исполнительному устройству "предложение" (оферту) выполнить действие. Например, "включи реле номер 5".
  • Подтверждение (Acknowledgement, Ack): Устройство, успешно получив и приняв команду к исполнению, отправляет обратно "принятие" (акцепт). Например, "команда на включение реле 5 получена и выполнена".
  • Таймаут (Timeout): Если контроллер не получает "акцепт" в течение оговоренного времени, он считает, что "предложение" не было принято. Контракт не состоялся. Этот факт фиксируется, и запускается процедура обработки сбоя.
  • Внедрение этого шаблона переводит нашу систему из разряда "наивных" в разряд профессиональных, где каждое управляющее воздействие контролируется и его результат проверяется.

    ---

    Компоненты шаблона: Команда, Подтверждение (Ack) и Таймаут

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

    'Команда' (Command)

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

    Пример правильного объекта `msg` для отправки команды:

    {
    

    "topic": "hi/office/zone1/light_group_3/set",

    "payload": {

    "value": true,

    "source": "SCN-LIGHT-012",

    "ts": 1678886400000,

    "correlationId": "cmd-a84bf4-1"

    }

    }

    > 💡 Подсказка: Ключ к надежности — корреляция. На уровне MQTT принято добавлять уникальный ID в конец топика или использовать специальное поле в payload, например, 'reqId' или `correlationId`, как в примере выше. Это позволяет однозначно сопоставить ответ с исходным запросом в асинхронной среде, где одновременно могут обрабатываться десятки команд.

    'Подтверждение' (Acknowledgement, Ack)

    Подтверждение — это ответное сообщение, которое сигнализирует об успешном завершении операции. Оно может быть двух видов:
  • Подтверждение доставки: Устройство сообщает, что команда получена, но еще не обязательно выполнена. Это характерно для асинхронных операций.
  • Подтверждение выполнения: Устройство сообщает, что команда не только получена, но и успешно выполнена (например, реле замкнулось, и устройство это проверило). Это наиболее предпочтительный тип подтверждения.
  • Подтверждение также должно следовать "Контракту сообщения". Часто оно приходит по другому MQTT топику, например, с суффиксом `/status`.

    Пример сообщения-подтверждения в ответ на команду выше:

    {
    

    "topic": "hi/office/zone1/light_group_3/status",

    "payload": {

    "value": true,

    "source": "relay_module_xyz",

    "ts": 1678886405123,

    "correlationId": "cmd-a84bf4-1"

    }

    }

    Обратите внимание на `correlationId`. Наличие этого поля позволяет нам на 100% быть уверенными, что это подтверждение именно на нашу команду, а не случайное сообщение о статусе.

    'Таймаут' (Timeout)

    Таймаут — это наш защитный механизм, "сторожевой пес", который срабатывает, если подтверждение (Ack) не было получено в течение заранее определенного периода времени. Выбор времени таймаута критически важен:

    Типичные значения таймаута на платформе HI:

    Когда таймаут срабатывает, система должна выполнить четкий набор действий:

  • Записать ошибку в журнал (Audit Log): В базу данных MySQL на контроллере или системный лог записывается информация о сбое: какая команда, для какого устройства, в какое время произошел таймаут.
  • Отправить уведомление: Для критически важных систем администратору отправляется уведомление (MQTT, Telegram) о невозможности доставить команду.
  • Изменить состояние интерфейса: В пользовательском интерфейсе элемент управления должен вернуться в исходное состояние и/или отобразить индикатор ошибки.
  • Перейти к стратегии повтора (Retry): Система может попытаться отправить команду еще раз. Этот механизм мы подробно рассмотрим в следующем уроке.
  • ---

    Практическая реализация в Node-RED

    Теперь давайте соберем этот шаблон в виде рабочего потока в Node-RED. Центральным элементом для реализации таймаута является узел `trigger`.

    Пошаговая инструкция

    Представим, что мы отправляем команду по MQTT и ждем подтверждения в другом топике.

    Схема потока:
    // Инициация команды
    

    [Inject] --(cmd)--> [Function: Add CorrID] --+

    |

    // Основной механизм Ack/Timeout |

    v

    +------------------------------------------------------+

    | trigger |

    (cmd)--------->| In -> Out1 (отправка команды) |-----> [MQTT Out: .../set]

    | |

    | |

    (ack)--------->| Reset -> (ничего) |

    | |

    | (по истечении таймера) -> Out2 (сработка таймаута) |-----> [Function: Log Timeout] -> [Debug: TIMEOUT]

    +------------------------------------------------------+

    // Получение и фильтрация подтверждения

    [MQTT In: .../status] --(ack?)--> [Switch: Check CorrID] --(valid ack)--> (идет на Reset узла trigger)

  • Создайте узлы: Разместите на холсте узлы `inject`, `function`, `trigger`, `switch`, `mqtt out`, `mqtt in` и два узла `debug`.
  • Инициатор команды (`inject` + `function`):
  • * `inject`: Настройте его для отправки любого сообщения для запуска потока.

    * `function` ("Add CorrID"): Этот узел формирует нашу команду и добавляет уникальный ID для корреляции.

        // Генерируем уникальный ID для этой транзакции

    msg.correlationId = "cmd-" + Date.now() + "-" + Math.random().toString(36).substr(2, 9);

    // Формируем команду по контракту

    msg.payload = {

    value: true,

    source: "flow:COURSE-05-M04-L02",

    ts: Date.now()

    };

    // Сохраняем исходную команду для обработки таймаута

    flow.set("last_command", msg);

    return msg;

  • Центральный узел `trigger`:
  • * Send: `the original message payload` (в нашем случае мы пропустим через него весь `msg`).

    * then wait for: `5` `seconds`.

    * then send: `nothing` (это важно! Мы будем использовать второй выход).

    * Handling: `extend delay if new message arrives`.

    * Second output: установите отправку сообщения. Например, `{"payload": "TIMEOUT"}`.

  • Обработка ответа (`mqtt in` + `switch`):
  • * `mqtt in`: Подпишите его на топик статуса, например `hi/device/+/status`.

    * `switch` ("Check CorrID"): Этот узел — наш фильтр. Он должен проверять, является ли пришедшее сообщение тем самым подтверждением, которое мы ждем.

    * Property: `msg.correlationId`

    * Rule: `==` `flow.last_command.correlationId`

    * Outputs: 1

  • Соединение логики:
  • * `inject` -> `function` -> `trigger`.

    * Первый выход `trigger` -> `mqtt out` (топик `hi/device/test/set`).

    * Второй выход `trigger` (таймаут) -> `debug` ("TIMEOUT").

    * `mqtt in` -> `switch`.

    * Выход `switch` -> вход `reset` узла `trigger`.

    * Также можно добавить выход из `switch` в узел `debug` ("ACK Received") для наглядности.

    > ⚠️ Внимание: Узел 'trigger' сбрасывается любым входящим сообщением. Крайне важно с помощью узла 'switch' фильтровать сообщения, чтобы на сброс 'trigger' попадали только релевантные подтверждения (Ack), а не посторонний "шум" в системе. Без фильтра по `correlationId` любой другой статус в системе может ошибочно сбросить наш таймер.

    Теперь вы можете протестировать три сценария:

    ---

    Пример: Управление Modbus-реле с контролем исполнения

    Адаптируем наш шаблон для реальной задачи: управление реле на Modbus-модуле, подключенном к контроллеру HI по шине RS-485. Здесь подтверждением будет служить сам факт успешного выполнения Modbus-запроса.

    Задача: Включить реле №3 (адрес Coil `2`) на Modbus-устройстве с `Unit ID = 15`. Мы должны быть уверены, что команда дошла до устройства. Схема потока:
    [Inject] -> [Function: Prepare Cmd] -> [Trigger] --(out1)--> [Modbus-Write] --(success)--> (идет на reset триггера)
    

    |

    +----(out2)----> [Function: Log Timeout]

  • Подготовка команды (`inject` + `function`):
  • * Узел `inject` просто запускает поток.

    * Узел `function` "Prepare Cmd" формирует сообщение для узла `modbus-write`.

        msg.payload = {

    'value': true,

    'fc': 5, // FC 5: Force Single Coil

    'unitid': 15,

    'address': 2,

    'quantity': 1

    };

    return msg;

  • Настройка узла `trigger`:
  • * Настраиваем его точно так же, как в предыдущем примере, с таймаутом, например, в 2 секунды (стандартно для Modbus RTU).

  • Узел `modbus-write`:
  • * Этот узел выполняет запись в Modbus-устройство.

    * У него есть два выхода. Первый — для успешного выполнения, второй — для ошибок. Нам важен первый.

  • Реализация обратной связи (Ack):
  • * Самый важный момент: первый (верхний) выход узла `modbus-write` соединяется со входом `reset` узла `trigger`.

    * Что это дает? Если `modbus-write` успешно отправил команду и получил корректный ответ от устройства (сам протокол Modbus подразумевает ответ на команду записи), он выдаст сообщение на свой первый выход. Это сообщение немедленно сбросит наш таймер в `trigger`. Это и есть наше подтверждение (Ack).

  • Обработка таймаута:
  • * Если устройство `unitid=15` отключено от шины, кабель RS-485 поврежден или устройство "зависло", узел `modbus-write` не сможет выполнить команду. Он либо выдаст ошибку на второй выход, либо просто не выдаст ничего в течение своего внутреннего таймаута.

    * В любом из этих случаев сообщение на сброс в `trigger` не поступит.

    * Через 2 секунды сработает второй выход узла `trigger`, сигнализируя о таймауте. Это сообщение мы направляем в наш централизованный поток обработки ошибок для логирования и отправки уведомлений.

    > ℹ️ Информация: Для протоколов типа KNX или DALI, где подтверждения являются стандартной частью телеграмм (флаги Ack в KNX, ответы в DALI), логика остается той же, но меняется узел, от которого мы ждем подтверждения. Вместо `modbus-write` это будет `knx-out` или специализированный DALI-узел. Главное — найти выход, сигнализирующий об успешном выполнении, и направить его на сброс `trigger`.

    ---

    Резюме и следующие шаги

    Сегодня мы сделали важнейший шаг от простых скриптов к созданию профессиональных и отказоустойчивых систем управления. Мы досконально разобрали шаблон "Команда -> Подтверждение (Ack) -> Таймаут", который является краеугольным камнем надежной доставки команд.

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

    Но что делать, когда таймаут все-таки сработал? Просто зафиксировать ошибку — это лишь половина дела. В большинстве случаев система должна предпринять попытку восстановить работоспособность, например, отправив команду повторно. Эта стратегия называется "Повтор" (Retry).

    🔗 Связанный материал: После освоения этого урока, переходите к уроку COURSE-05-M04-L03, чтобы научиться строить еще более надежные системы с помощью механизма повторных попыток (Retry).

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