Руководство по созданию драйверов для Dynamic Driver

Введение

Dynamic Driver позволяет создавать собственные драйверы для управления различными устройствами.

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

Редактор драйверов (UI)

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

Редактор драйверов находится в левой панели U-Logic Как открыть редактор драйверов

Редактор состоит из трёх зон:

  1. Верхняя панель, в которой есть селектор режима работы редактора Загрузить из списка или Создать новый.

    Режим

  2. Центральная область — редактор драйвера

  3. Нижняя панель:

    • Скачать проект - Сохраняет проект и все драйвера на ваш компьютер.

    • Загрузить проект - Загружает проект, с вашего компьютера.

    • Сохранить - Сохраняет изменения драйвера.

    • Удалить - Удаляет драйвер который открыт в редакторе.

Нижняя панель

Как добавить драйвер

  1. В редакторе драйверов выберите режим «Создать новый»

  2. Укажите имя (файла) драйвера.

  3. Отредактируйте шаблон драйвера.

  4. Нажмите «Сохранить».

Создание драйвера

Как отредактировать драйвер

  1. В редакторе драйверов выберите режим «Загрузить из списка»

  2. Выберите драйвер из списка драйверов

  3. Внесите изменения на нужных вкладках

  4. Нажмите «Сохранить»

  5. Нажмите «Деплой», чтобы изменения стали доступны узлам

Создание драйвера

Как подгружается драйвер и где он виден

  1. После добавления/обновления драйвера через интерфейс редактора драйверов нажмите «Сохранить».

  2. Список доступных драйверов обновляется после сохранения. Драйвер становится виден в настройках узла Device Command в поле «Driver».

Выбор драйвера в узле

Узлы и их назначение

В Dynamic-Driver используется 4 ключевых узла для работы с устройством через драйвер:

  1. Device Connection — отвечает за включение драйвера. Device Connection является конфигурационным узлом и не дсотупен в палитре узлов

  2. Transport — отвечает за сетевое подключение к устройству. Transport является конфигурационным узлом и не дсотупен в палитре узлов

  3. Device Command — отправляет команды, определённые драйвером

  4. Device Response Listener — принимает и маршрутизирует ответы, распознанные драйвером

Узел: Device Connection

Назначение: Подключает драйвер и связывает драйвер с Трнаспортом (TCP/UDP/HTTP и др.).

Основные настройки:

  • Имя конфигурационного узла.

  • Драйвер. Здесь необходимо выбрать нужный драйвер

  • Транспорт (Transport): TCP | UDP | HTTP/HTTPS | TELNET | SSH

  • (TCP/UDP/HTTP…)-Config. здесь необходимо создать или выбрать существующий транспорт.

  • Keep‑Alive — включает периодический вызов метода драйвера KeepAlive().

  • Интервал Keep‑Alive — период (секунды) между вызовами KeepAlive(). Поле доступно только при включённом Keep‑Alive.

TCP-Config

Узел: Transport(TCP-Config)

Назначение: Выполняет подключение к устройству передает команды из драйвера и отправляет драйверу полученные от устройства данные.

Основные настройки:

  • Название конфигурационного узла.

  • Хост. IP адрес устройства

  • Порт устройства

  • Переподключение. Таймаут между попытками подключения к устройству

  • Возвращать:

    • Байты(Buffer), для устройств которые работают в HEX формате

    • Строка, для устройств которые работаю в формате ASCII

  • Автоподключение. Транспорт будет пытаться автоматически подключится, при каждом обрыве соединения и при старте программы. По умолчанию включенно.

  • Debug. Включает отладочные сообщения транспорта.

TCP-Config

Узел: Transport(UDP-Config)

Назначение: Выполняет подключение к устройству передает команды из драйвера и отправляет драйверу полученные от устройства данные.

Основные настройки:

  • Название конфигурационного узла.

  • Внешний порт. Порт устройства

  • Локальный порт. Порт который контроллер будет слушать.

  • Тип соединения. UNICAST, BROADCAST, MULTICAST,

  • Формат данных:

    • Строка, для устройств которые работаю в формате ASCII

    • Base64

    • HEX

  • Автоподключение. Транспорт будет пытаться автоматически подключится, при каждом обрыве соединения и при старте программы. По умолчанию включенно.

  • Debug. Включает отладочные сообщения транспорта.

UDP-Config

Узел: Transport(HTTP-Config)

Назначение: Выполняет HTTP(S)-подключение к устройству/шлюзу API, отправляет запросы согласно командам драйвера и передаёт ответы драйверу.

Основные настройки:

  • Название конфигурационного узла (Имя)

  • Сервер (URL): базовый адрес, например http://192.168.1.1 (поддерживает подстановки из env, flow, global, msg)

  • Метод: GET, POST, PUT, DELETE, HEAD, Установить через (взять из сообщения)

  • Полезная нагрузка (для GET): Игнорировать | Добавить в запрос | Добавить в тело

  • Использовать TLS: выбор конфигурации TLS; параметр «Проверять SSL сертификаты» (самоподписанные ⇄ разрешить)

  • Аутентификация: Базовая | Digest | Bearer; поля Имя пользователя/Пароль или Токен Bearer

  • Сохранять соединение (keep‑alive)

  • Отправлять ошибки (передавать ошибки далее по потоку)

  • Небезопасный HTTP парсер (разрешает нестандартные ответы)

  • Следовать переадресациям (HTTP 301/302)

  • Тайм‑аут запроса, мс (переопределяет глобальный)

  • Возвращать: UTF‑8 | Бинарные данные | JSON

  • Заголовки: настраиваемый список с типами значений (other, msg. и пресеты)

  • Поля URL/логин/пароль поддерживают типизированный ввод (строка, msg., flow., global., env.)

Рекомендации:

  • Указывайте протокол в URL (http:// или https://)

  • Для самоподписанных сертификатов либо подключайте корректную TLS‑конфигурацию, либо временно отключайте проверку

  • Для JSON‑API установите «Возвращать: JSON», чтобы автоматически получить объект

Переопределение параметров из драйвера/сообщения:

  • url — полный URL (перекрывает server+path)

  • path — относительный путь, добавляется к server

  • method — HTTP‑метод (GET/POST/…)

  • headers — объект заголовков запроса

  • cookies — объект куки

  • payload — тело запроса (строка/Buffer/объект)

  • requestTimeout — тайм‑аут, мс

  • followRedirectstrue/false

  • rejectUnauthorizedtrue/false (TLS)

  • Для GET: если в конфиге выбран режим «Полезная нагрузка», объект payload добавится в query или тело

HTTP-Config

Узел: Transport(Telnet-Config)

Назначение: устанавливает Telnet‑сеанс с устройством, выполняет вход по логину/паролю и передаёт команды/ответы между драйвером и устройством.

Основные настройки:

  • Имя — отображаемое имя конфигурационного узла

  • Хост — IP/домен Telnet‑сервера (typed input: str, msg, flow, global, env)

  • Порт — обычно 23 (typed input: num, str, msg, flow, global, env)

  • Логин — имя пользователя (typed input)

  • Пароль — пароль (typed input)

  • Выражение для логина — regex для ввода логина, по умолчанию [^\S\s]*login[: ]* или [^\S\s]*login[: ]*

  • Выражение для пароля — regex для ввода пароля, по умолчанию [^\S\s]*password[: ]*

  • Выражение для shell prompt — regex окончания вывода, по умолчанию [^\S\s]*[$#>]

  • Переподключение — автоматически восстанавливать соединение при разрыве (флаг)

  • Интервал (мс) — задержка между попытками переподключения (по умолчанию 5000)

  • Авто‑подключение — подключаться при старте (флаг)

  • Debug — выводить отладочные сообщения (флаг)

  • Поля «Хост», «Порт», «Логин», «Пароль» поддерживают источники msg/flow/global/env через typed input

  • Поля «Выражение для логина/пароля/shell» вводятся как regex‑шаблоны

  • Имя узла формируется как telnet@<host>:<port>, если не задано «Имя»

Переопределение из драйвера/контекста:

  • При выборе соответствующего источника значения «Хост», «Порт», «Логин», «Пароль» могут считываться из msg.*, flow.*, global.* или env.*

  • Флаги «Переподключение», «Авто‑подключение», «Debug» задаются в конфигурации узла

Примечание

  • Тщательно подберите регулярные выражения для авторизации — от них зависит корректность входа и парсинга вывода

Telnet-Config

Узел: Transport(SSH-Config)

Назначение: устанавливает защищённый SSH‑сеанс с устройством/хостом и передаёт команды/ответы между драйвером и устройством через интерактивный shell. Поддерживает аутентификацию по паролю или по приватному ключу.

Основные настройки:

  • Имя — отображаемое имя конфигурационного узла

  • Хост — IP/домен SSH‑сервера (typed input: str, msg, flow, global, env)

  • Порт — обычно 22 (typed input: num, msg, flow, global, env)

  • Логин — имя пользователя (typed input)

  • Пароль — пароль пользователя (typed input; не используется, если указан ключ)

  • Ключ — путь к приватному ключу (если указан, используется вместо пароля)

  • Переподключение — автоматически восстанавливать соединение при разрыве (флаг)

  • Интервал (мс) — задержка между попытками переподключения (по умолчанию 5000)

  • Авто‑подключение — подключаться при старте (флаг)

  • Debug — выводить отладочные сообщения (флаг)

Переопределение из драйвера/контекста:

  • При выборе соответствующего источника значения «Хост», «Порт», «Логин», «Пароль» могут считываться из msg.*, flow.*, global.* или env.*

  • Флаги «Переподключение», «Авто‑подключение», «Debug» задаются в конфигурации узла

  • Путь к ключу задаётся в конфигурации; передача через msg не рекомендуется по соображениям безопасности

SSH-Config

Статусы узла:

  • Серый — отключён, Красный — ошибка, Зелёный — подключено

  • Текстовый статус под узлом: Connecting… / Connected / Disconnected / Error

Советы по настройке:

  • Для TCP‑клиента: укажите IP/порт устройства, проверьте доступность порта брандмауэром

  • Для Serial: проверьте параметры порта (baud rate, parity, data/stop bits)

  • Для нестабильных линий включите реконнект и увеличьте тайм‑ауты

Рисунок: Форма настроек узла Device Connection — поля Transport, Host/IP, Port, Encoding, Keep Alive

Узел: Device Command

Назначение: отправка команд, описанных в драйвере, с заполнением параметров из UI или из сообщения.

Ключевые поля:

  • Driver — выбор драйвера (из метаданных драйвера)

  • Command — список команд, определённых драйвером (динамически меняется при выборе Driver)

  • Parameters — набор полей для ввода значений (тип/обязательность/диапазоны/enum — берутся из описания команды)

  • Источник параметров: из UI или из входного сообщения (msg.parameters, msg.payload)

  • Ожидать ответ — если включено, узел будет ждать распознанный драйвером ответ

  • Timeout — время ожидания ответа (если включено «Ожидать ответ»)

  • Повторы/Троттлинг (если доступны) — ограничение частоты отправки, политика повторов

Сообщения:

  • Вход: msg.parameters (объект параметров), msg.command (имя команды), msg.connection (bool — подключить/отключить), и/или msg.payload (сквозной)

  • Выход 1: статус соединения (connected | disconnected | error) и события транспорта

  • Выход 2: сквозной msg.payload без изменений

Типовые сценарии:

  • Заполнение из UI — удобно для статических команд

  • Динамика из msg.parameters — удобно при вычислении значений в рантайме

Переопределения через сообщение:

  • msg.command — переопределяет выбранную в узле команду

  • msg.parameters — дополняет/замещает параметры команды из узла (приоритет у msg)

  • msg.connectiontrue подключиться | false отключиться (в этом случае команда не отправляется)

Валидация параметров (по метаданным драйвера):

  • Обязательность: параметры с required: true не могут быть пустыми

  • Типы: number (приведение + проверка min/max), boolean (строки "true"/"1" → true), enum (значение должно входить в список)

  • Невалидные значения игнорируются и заменяются сохранёнными в узле (если есть)

Статусы узла:

  • Серый «IDLE» — узел создан, ожидание действий

  • Жёлтый «Подключение…» — установление соединения

  • Зелёный «Подключено» — соединение установлено

  • Красный «Отключено» — соединение разорвано

  • «Статус: <код> - <текст>» — транспортный ответ (HTTP‑коды и т.п.)

  • Красный «Не настроено соединение» — не выбран device-connection в настройках узла

  • Красный «Ошибка: …» — ошибка формирования/отправки команды Device-Command

Узел: Device Response Listener

Назначение: приём и маршрутизация ответов, которые распознал драйвер согласно настроенным в нём шаблонам.

Ключевые настройки/поведение:

  • Соответствие выходов: type → номер выхода

  • Выход по умолчанию: ответы без сопоставления типа направляются на 1‑й выход

Структура выходного сообщения:

  • msg.payload — распарсенный объект ответа

  • msg.type — тип ответа (например, status, error, volumeStatus и т.п.)

  • msg.originalMsg — дополнительная информация/сырой пакет (если публикуется драйвером)

Настройки:

  • Соединение — тот же device-connection, что использует Device Command

  • Выходы — количество физических выходов узла (1–20)

  • Типы ответов — список типов из драйвера с назначением на выход:

    • галочка «включить тип» и выпадающий «номер выхода» (0..N‑1)

    • счётчики: «Выбрано ответов», «Настроено выходов»/«из N»

    • предупреждение о конфликтах, когда несколько типов направлены на один и тот же выход

Вход узла:

  • Поддерживается управление соединением через msg.connection (true подключиться / false отключиться)

Маршрутизация:

  • Используется payload.type. Если тип назначен в карте — сообщение уходит на соответствующий выход

  • Если тип не назначен — сообщение отправляется на 1‑й выход (индекс 0)

  • На событие формируется массив из N элементов, где только один выбранный выход получает msg, остальные — null

Статусы узла:

  • Серый «IDLE» при инициализации

  • Зелёный «Подключено» — при событии подключения

  • Красный «Отключено» — при событии отключения

  • Красный «Ошибка» — при ошибке; текст берётся из события

Пример выходного сообщения:

{
  "payload": {
    "type": "MatrixTie",
    "input": 1,
    "output": 2
  },
}

Device-Command

Схема зависимости узлов Dynamic-Driver

        sequenceDiagram
 
  participant DCMD as Device Command
  participant DCON as device-connection
  participant TR as Transport

  DCMD->>DCON: Создание Конфигурации Драйвер + Транспорт
  Note over DCMD: Подключение Драйвера и Транспорта
  Note over DCON: Выбор драйвера и установка транспорта
  DCON->>TR: Создание транспорта и настройка транспорта (TCP/UDP/HTTP/Telnet/SSH)
  Note over TR: Настройка транспорта
  DCON->>DCMD: Создание Конфигурации Драйвер + Транспорт

    

Схема работы узлов Dynamic-Driver

        sequenceDiagram

  participant UI as Сообщение/триггер
  participant DCMD as Device Command
  participant DCON as device-connection (экземпляр Драйвера)
  participant TR as Transport
  participant DEV as Устройство
  participant RLIST as Response Listener

  UI->>DCMD: msg.command / msg.parameters / msg.connection
  DCMD->>DCON: Команда + параметры
  Note over DCON: Внутри: driver.format(command, params)
  DCON->>TR: payload (TCP/UDP/HTTP/Telnet/SSH)
  TR->>DEV: Запрос
  DEV-->>TR: Ответ
  TR-->>DCON: Сырые данные
  Note over DCON: Внутри: driver.parse(raw) → { type, ... }
  DCON-->>RLIST: Распознанный ответ по type
  DCON-->>DCMD: Статусы подключения / коды
    

Шаги настройки (матрица действий)

Шаг

Действие

Где

Результат

1

Создайте/отредактируйте драйвер и сохраните

Редактор драйверов

Драйвер доступен для выбора

2

Добавьте Device Command/Response Listener (точка входа), создайте/выберите Connection

Палитра узлов

Открыт Device Command; из него создаётся device-connection. Отправка команд или парсинг ответов станет возможным после шагов 3–4

3

Внутри Device Command/Response Listener создайте device-connection и выберите Драйвер

Внутри Device Command или Response Listener

device-connection создан и привязан к драйверу; переход к настройке транспорта

4

Внутри device-connection создайте и привяжите Transport Config (TCP/UDP/HTTP/Telnet/SSH)

device-connection

Транспорт создан и привязан; Device Command готов отправлять команды

5

Настройте Device Command, укажите Connection, выберите команду и задайте параметры

Палитра узлов

Отправка команд

6

Добавьте Response Listener, укажите Connection, настройте карту выходов

Палитра узлов

Маршрутизация ответов по типам

7

Деплой и тест

Кнопка Deploy

Команды/ответы идут по потоку

Подготовка к разработке

Необходимые инструменты

  1. Базовые знания JavaScript и Node.js

  2. Документация на протокол взаимодействия с вашим устройством

Структура драйвера

Драйвер представляет собой модуль который включает в себя модуль BaseDriver. BaseDriver предоставляет необходимую функциональность для взаимодействия с системой и устройством.

Основные элементы драйвера

  • Метаданные - описательная информация о драйвере

  • Команды - доступные для выполнения команды и их параметры

  • Обработчики ответов - шаблоны для распознавания и обработки ответов устройства

  • Методы форматирования команд - преобразование высокоуровневых команд в формат устройства

  • Метод инициализации - настройка драйвера при подключении

Пошаговая разработка драйвера

Шаг 1: Создание файла драйвера

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

const BaseDriver = require('base-driver');

/**
 * Драйвер для устройства MyDevice
 */
class MyDeviceDriver extends BaseDriver {
  
}

module.exports = MyDeviceDriver;

Шаг 3: Добавление метаданных драйвера

Метаданные помогают системе идентифицировать драйвер и отображать информацию о нем в интерфейсе. Добавьте следующий статический объект в класс драйвера:

static metadata = {
  name: 'MyDevice',              // Имя драйвера
  manufacturer: 'MyCompany',     // Производитель устройства
  version: '1.0.0',              // Версия драйвера
  description: 'Драйвер для устройства MyDevice от MyCompany' // Описание
};

Шаг 4: Определение команд

Команды определяют действия, которые можно выполнить с устройством. Каждая команда описывается в объекте static commands. Для каждого параметра можно указать:

  • name — имя поля.

  • typestring | number | boolean.

  • description — пояснение.

  • required — если true, параметр обязателен. Узел «Device Command» проверит его наличие и подсветит поле в редакторе.

  • Дополнительно: min/max (числа), enum (список допустимых значений).

static commands = {
  setPower: {
    description: 'Включение/выключение устройства',
    parameters: [
      {
        name: 'value',
        type: 'boolean',
        description: 'Состояние питания (true=включено, false=выключено)',
        required: true
      }
    ]
  },
  setVolume: {
    description: 'Установка громкости',
    parameters: [
      {
        name: 'level',
        type: 'number',
        description: 'Уровень громкости (0-100)',
        required: true,
        min: 0,
        max: 100
      }
    ]
  },
  setInput: {
    description: 'Выбор входного источника',
    parameters: [
      {
        name: 'source',
        type: 'string',
        description: 'Имя источника (HDMI1, HDMI2, USB и т.д.)',
        required: true,
        enum: ['HDMI1', 'HDMI2', 'USB', 'Component', 'Composite']
      }
    ]
  },
  getStatus: {
    description: 'Запрос статуса устройства',
    parameters: []
  }
};

Что такое static commands и зачем они нужны (простыми словами)

  • Это «каталог возможностей» вашего драйвера. Здесь вы перечисляете, какие действия устройство умеет выполнять, и какие данные нужны для каждого действия.

  • По этому описанию система автоматически строит узел Device Command.

  • Каждая команда — это объект с описанием и списком параметров. Каждый параметр задаёт тип (string, number, bool), обязательность (required:true) и ограничения (min/max).

Где это отображается в интерфейсе

  • В узле Device Command:

    • поле «Команда» — выпадающий список из ключей ваших команд (setPower, setVolume, …);

    • блок «Параметры команды» — динамически сгенерированные поля:

      • number → числовое поле c min/max;

      • boolean → выбор True/False;

      • enum → выпадающий список значений;

      • string → текстовое поле;

    • над параметрами показываются «Описание».

Как это работает во время выполнения

  • Значения, введённые в UI, сохраняются как дефолты в конфигурации узла.

  • В потоке можно переопределять:

    • msg.parameters — подставить значения параметров на лету (приоритет у входного сообщения);

  • Невалидные значения игнорируются или заменяются сохранёнными дефолтами в узле.

Мини‑чеклист

  1. В static commands опишите команды и параметры (типы/ограничения/required).

  2. Реализуйте методы с теми же именами (см. «Шаг 6: Реализация методов команд»).

  3. Нажмите Сохранить/Деплой драйвера.

  4. Откройте узел Device Command: выберите соединение → драйвер → команду, заполните параметры.

  5. При необходимости переопределяйте msg.command и msg.parameters в потоке.

Результат описания в static commands команды setPower

Результат описания в static commands команды setPower

Шаг 5: Определение обработчиков ответов

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

static responses = {
  status: {
    description: 'Статус устройства',
    matcher: {
      pattern: /Status: (.+), Power: (.+), Volume: (.+), Input: (.+)/
    },
    extract: function(match) {
      return {
        status: match[1],
        power: match[2] === 'ON',
        volume: parseInt(match[3], 10),
        input: match[4]
      };
    }
  },
  powerStatus: {
    description: 'Статус питания',
    matcher: {
      pattern: /Power: (.+)/
    },
    extract: function(match) {
      return {
        power: match[1] === 'ON'
      };
    }
  },
  volumeStatus: {
    description: 'Текущая громкость',
    matcher: {
      pattern: /Volume: (\d+)/
    },
    extract: function(match) {
      return {
        volume: parseInt(match[1], 10)
      };
    }
  },
  inputStatus: {
    description: 'Текущий вход',
    matcher: {
      pattern: /Input: (.+)/
    },
    extract: function(match) {
      return {
        input: match[1]
      };
    }
  },
  error: {
    description: 'Ошибка устройства',
    matcher: {
      pattern: /Error: (.+)/
    },
    extract: function(match) {
      return {
        message: match[1]
      };
    }
  }
};

Что такое responses (простыми словами)

  • Это словарь правил распознавания ответов в драйвере: каждый ключ — имя типа (status, error, volumeStatus…), внутри — как распознать (matcher) и что извлечь (extract).

  • Когда устройство присылает сырые данные, драйвер сопоставляет их с matcher, передает в extract и возвращает объект с полем type и параметрами. type - помогает узлу Response Listener определить на какой выход отправить результат.

Где это видно в интерфейсе

  • В узле Device Response Listener в секции «Типы ответов»: чекбокс «включить» и выпадающий «Номер выхода».

  • Счётчики: «Выбрано ответов» и «Настроено выходов / из N».

  • Предупреждение о конфликтах, если несколько типов назначены на один и тот же выход.

Пошагово: что происходит во время выполнения

matcher и extract
  • Устройство прислало сырой текст:

Status: READY, Power: ON, Volume: 50, Input: HDMI1
  • В драйвере в static responses настроен тип status:

static responses = {
  status: {
    description: 'Статус устройства',
    matcher: { pattern: /Status: (.+), Power: (.+), Volume: (\d+), Input: (.+)/ },
    extract: function(match) {
      return {
        type: 'status',            // ключ для маршрутизации
        status: match[1],
        power: match[2] === 'ON',
        volume: parseInt(match[3], 10),
        input: match[4]
      };
    }
  }
};
  • Что происходит:

    • matcher находит совпадение по регулярному выражению и формирует массив match;

    • extract извлекает значения из match и возвращает объект с полем type: 'status' и параметрами;

    • Device Response Listener читает payload.type === 'status' и отправляет сообщение на назначенный для status выход.

  • Драйвер публикует сообщение вида:

{ "payload": { "type": "status", "status": "READY", "power": true, "volume": 50, "input": "HDMI1" } }
  • Response Listener берёт payload.type, находит назначенный выход и отправляет сообщение только туда;

  • Если тип не включён/не назначен — сообщение уходит на 1‑й выход.

Мини‑чеклист

  1. Выберите соединение (тот же device-connection, что у Device Command).

  2. Задайте «Выходы» (1–20).

  3. Включите нужные типы и назначьте им выход.

  4. Убедитесь, что нет конфликтов (или скорректируйте назначения).

  5. Деплой и подключите узел debug к соответствующим выходам.

Настройка типов ответов и назначение выходов в Response Listener

Настройка типов ответов и назначение выходов в Response Listener

Шаг 6: Реализация методов команд

Для каждой команды создайте метод с тем же именем, который возвращает объект с полем payload содержащим команду в формате, понятном устройству.

// Метод для команды setPower
setPower(params) {
  const { value } = params;
  // Обновляем состояние
  this.state.power = value;
  // Возвращаем объект с полем payload - форматированной командой
  return { payload: `PWR ${value ? 'ON' : 'OFF'}\r\n` };
}

// Метод для команды setVolume
setVolume(params) {
  const { level } = params;
  // Проверка диапазона
  const safeLevel = Math.max(0, Math.min(100, level));
  // Обновляем состояние
  this.state.volume = safeLevel;
  // Возвращаем объект с полем payload
  return { payload: `VOL ${safeLevel}\r\n` };
}

// Метод для команды setInput
setInput(params) {
  const { source } = params;
  // Обновляем состояние
  this.state.input = source;
  // Возвращаем объект с полем payload
  return { payload: `INPUT ${source}\r\n` };
}

// Метод для команды getStatus
getStatus() {
  // Команда без параметров
  return { payload: `STATUS\r\n` };
}

Шаг 7: Реализация метода инициализации

Метод initialize вызывается автоматически после успешного подключения к устройству. Используйте его для запроса начального состояния устройства или выполнения других действий при подключении.

initialize() {
  // Запрос начального статуса устройства
  this.sendCommand('getStatus');
  
  // Можно добавить дополнительные обработчики ответов динамически
  this.addResponseHandler(/Version: (.+)/, (match, data) => {
    // Обновляем информацию об устройстве
    this.deviceInfo.firmwareVersion = match[1];
    // Возвращаем обработанные данные
    return { 
      type: 'version',
      version: match[1]
    };
  });
  
  this.addResponseHandler(/Serial: (.+)/, (match, data) => {
    this.deviceInfo.serialNumber = match[1];
    return { 
      type: 'serial',
      serialNumber: match[1]
    };
  });
}

Шаг 8: Сборка полного драйвера

Объединим все части для создания полного драйвера:

const BaseDriver = require('base-driver');

/**
 * Драйвер для устройства MyDevice
 */
class MyDeviceDriver extends BaseDriver {
  // Метаданные драйвера
  static metadata = {
    name: 'MyDevice',
    manufacturer: 'MyCompany',
    version: '1.0.0',
    description: 'Драйвер для устройства MyDevice от MyCompany'
  };
  
  // Определение команд
  static commands = {
    setPower: {
      description: 'Включение/выключение устройства',
      parameters: [
        {
          name: 'value',
          type: 'boolean',
          description: 'Состояние питания (true=включено, false=выключено)',
          required: true
        }
      ]
    },
    setVolume: {
      description: 'Установка громкости',
      parameters: [
        {
          name: 'level',
          type: 'number',
          description: 'Уровень громкости (0-100)',
          required: true,
          min: 0,
          max: 100
        }
      ]
    },
    setInput: {
      description: 'Выбор входного источника',
      parameters: [
        {
          name: 'source',
          type: 'string',
          description: 'Имя источника (HDMI1, HDMI2, USB и т.д.)',
          required: true,
          enum: ['HDMI1', 'HDMI2', 'USB', 'Component', 'Composite']
        }
      ]
    },
    getStatus: {
      description: 'Запрос статуса устройства',
      parameters: []
    }
  };
  
  // Определение обработчиков ответов
  static responses = {
    status: {
      description: 'Статус устройства',
      matcher: {
        pattern: /Status: (.+), Power: (.+), Volume: (.+), Input: (.+)/
      },
      extract: function(match) {
        return {
          status: match[1],
          power: match[2] === 'ON',
          volume: parseInt(match[3], 10),
          input: match[4]
        };
      }
    },
    powerStatus: {
      description: 'Статус питания',
      matcher: {
        pattern: /Power: (.+)/
      },
      extract: function(match) {
        return {
          power: match[1] === 'ON'
        };
      }
    },
    volumeStatus: {
      description: 'Текущая громкость',
      matcher: {
        pattern: /Volume: (\d+)/
      },
      extract: function(match) {
        return {
          volume: parseInt(match[1], 10)
        };
      }
    },
    inputStatus: {
      description: 'Текущий вход',
      matcher: {
        pattern: /Input: (.+)/
      },
      extract: function(match) {
        return {
          input: match[1]
        };
      }
    },
    error: {
      description: 'Ошибка устройства',
      matcher: {
        pattern: /Error: (.+)/
      },
      extract: function(match) {
        return {
          message: match[1]
        };
      }
    }
  };
    
  // Инициализация при подключении
  initialize() {
    console.log('Инициализация устройства MyDevice');
    
    // Запрос начального статуса при подключении
    this.publishCommand('getStatus');
    

  }
  
  // Методы команд
  setPower(params) {
    if (this.debug) {
      console.log('Выполнение команды setPower:', params);
    }
    
    const { value } = params;
    this.state.power = value;
    return { payload: `PWR ${value ? 'ON' : 'OFF'}\r\n` };
  }
  
  setVolume(params) {
    if (this.debug) {
      console.log('Выполнение команды setVolume:', params);
    }
    
    const { level } = params;
    // Проверка диапазона
    const safeLevel = Math.max(0, Math.min(100, level));
    this.state.volume = safeLevel;
    return { payload: `VOL ${safeLevel}\r\n` };
  }
  
  setInput(params) {
    if (this.debug) {
      console.log('Выполнение команды setInput:', params);
    }
    
    const { source } = params;
    this.state.input = source;
    return { payload: `INPUT ${source}\r\n` };
  }
  
  getStatus() {
    if (this.debug) {
      console.log('Выполнение команды getStatus');
    }
    
    return { payload: `STATUS\r\n` };
  }
  
  // Обработка нестандартных ответов
  parseResponse(data) {
    try {
      // Примеры обработки нестандартных ответов
      if (data.includes('System Info:')) {
        const info = data.replace('System Info:', '').trim();
        return {
          type: 'systemInfo',
          info: info
        };
      }
      
      return null; // Возвращаем null если ответ не распознан
    } catch (error) {
      console.error('Ошибка при обработке ответа:', error);
      return {
        type: 'error',
        message: error.message,
        raw: data
      };
    }
  }
}

module.exports = MyDeviceDriver;

Использование драйвера

  1. После создания драйвера, нажмите кнопку сохранить

  2. Добавьте узел «Device Command» в рабочую область

  3. В настройках узла выберите драйвер из списка

  4. Настройте параметры подключения (тип транспорта, адрес, порт)

  5. В настройках узла «Device Command» выберите команду для выполнения

  6. Добавьте узел «Device Response Listener» для получения ответов от устройства

  7. Запустите поток и проверьте работу драйвера

Проверка регулярных выражений

Если обработчики ответов не срабатывают:

  • проверьте корректность регулярных выражений:

    // Тестирование регулярного выражения
    const regex = /Status: (.+), Power: (.+), Volume: (.+)/;
    const testData = "Status: READY, Power: ON, Volume: 50";
    const match = testData.match(regex);
    
    if (match) {
      console.log('Совпадение найдено:');
      console.log('Статус:', match[1]);
      console.log('Питание:', match[2]);
      console.log('Громкость:', match[3]);
    } else {
      console.log('Совпадение не найдено');
    }
    
  • проверьте настройки формата данных в Транспорте (String/Buffer(HEX))

Проблемы с форматированием команд

Если команды не отправляются корректно, убедитесь, что:

  1. Метод (функция) имеет то же имя, что и команда в static commands

  2. Команда корректно возвращает объект с полем payload

Расширенные возможности

Обработка сложных протоколов

Некоторые устройства используют сложные протоколы с двоичными данными или специальными форматами. В таких случаях можно использовать дополнительные методы обработки данных:

// Для бинарных данных можно использовать буферы
binaryCommand(params) {
  const { data } = params;
  // Создаем буфер с командой
  const buffer = Buffer.from([0x02, data.charCodeAt(0), 0x03]);
  return { payload: buffer };
}

// Обработка бинарных ответов
static responses = {
  binaryResponse: {
    description: 'Бинарный ответ',
    matcher: {
      pattern: /\x02(.+)\x03/
    },
    extract: function(match) {
      // Преобразование бинарных данных
      const payload = match[1];
      const value = payload.charCodeAt(0);
      return {
        value: value
      };
    }
  }
};

Множественные ответы и publishResponse

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

Когда использовать:

  1. Один ответ – верните объект из parseResponse() с помощью return.

  2. Несколько ответов сразу – верните Array объектов, каждый будет опубликован системой по очереди.

  3. Асинхронная логика – внутри parseResponse() вызывайте this.publishResponse() столько раз, сколько нужно, и затем верните return null.

// Пример: в буфере сразу два ответа разделены переводом строки
parseResponse(data) {
  const parts = data.data.split('\r\n').filter(Boolean);
  if (parts.length === 1) {
    // Простой случай – один ответ
    return this._parseSingle(parts[0]);
  }

  // Сложный случай: публикуем через publishResponse
  parts.forEach(part => {
    const parsed = this._parseSingle(part);
    // second parameter не обязателен, но полезен для дебага, в данном случае в raw записываем сырой ответ
    this.publishResponse(parsed, { raw: part });
  });
  return null;
}
  • publishResponse(payload, originalMsg) – публикует один распарсенный ответ наружу.

    • payload – объект-результат вашего парсинга (то же, что обычно возвращает parseResponse).

    • originalMsg – необязательный объект-обёртка для сырого пакета/любой вспомогательной информации. Он попадёт в узел «Device Response Listener» как msg.originalMsg. Если не нужен — опустите параметр.

Важно: если вернёте null или undefined, система считает, что драйвер уже опубликовал ответы. Если ответ передается не корректно, он всегда попадает на первый выход.

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

Инициирование команд и publishCommand

Иногда драйверу нужно самому отправить команду устройству — например, при initialize() или при изменении внутренних таймеров.В BaseDriver имеется вспомогательный метод publishCommand:

publishCommand(command, params = {})
  • command - Имя команды

  • params - Параметры команды

Когда использовать

  • Запрос начальных статусов в initialize().

  • Реакция на внутренние события драйвера без участия внешних узлов.

Пример: запрос статуса при инициализации

initialize() {
  // Вместо прямого вызова sendCommand → write используйте publishCommand
  this.publishCommand('getStatus');

  // Или несколько команд сразу
  this.publishCommand('getVolume');
  this.publishCommand('getInput');
}

Передача готового объекта

Если у вас уже есть сформированный объект (например, после вычислений), можно передать его напрямую:

this.publishCommand({ command: 'setPower', parameters: { value: true } });

Примеры вызова publishCommand

// 1) Булев параметр – включаем питание
this.publishCommand('setPower', { value: true });

// 2) То же, но сразу объектом
this.publishCommand({ command: 'setPower', parameters: { value: false } });

// 3) Числовой параметр – установить громкость
this.publishCommand('setVolume', { level: 25 });

// 4) Значение из enum – переключиться на HDMI2
this.publishCommand('setInput', { source: 'HDMI2' });

// 5) Несколько числовых параметров
this.publishCommand('multiSet', { brightness: 80, contrast: 60 });

Функция Keep‑Alive

Драйвер умеет периодически вызывать метод KeepAlive() вашего драйвера. Это удобно для поддержания соединения и регулярного опроса статусов.

Параметры в настройках узла:

  • Keep Alive — включает/выключает периодические вызовы (по умолчанию выключено).

  • Interval — интервал в секундах между вызовами KeepAlive().

Как это работает:

  1. При включённом флаге узел запускает таймер с указанным интервалом.

  2. По таймеру вызывается метод KeepAlive() текущего драйвера.

  3. Внутри KeepAlive() вы формируете одну или несколько команд через publishCommand(...).

Device-Command

Пример реализации в драйвере:

KeepAlive() {
  // Запросить актуальные статусы устройства
  this.publishCommand('getStatus');
  // Дополнительно — опросить громкость
  this.publishCommand('getVolume');
}

Подробно о параметрах команд

Ниже приведены типовые сценарии и то, как они описываются в массиве parameters.

Сценарий

Как описать в parameters

Что увидит пользователь в узле Device Command

Несколько числовых параметров

multiSet (см. пример ниже)

Два numeric-input поля.

Выпадающий список (enum)

setInput (см. пример ниже)

Dropdown-меню со значениями из enum.

Число с пределами

setVolume (см. пример ниже)

Numeric-input с валидацией min/max.

Булев параметр

setPower (см. пример ниже)

Dropdown-меню True/False.

Примеры описаний
// Несколько параметров
multiSet: {
  description: 'Яркость и контраст',
  parameters: [
    { name: 'brightness', type: 'number', min: 0, max: 100, required: true },
    { name: 'contrast',   type: 'number', min: 0, max: 100, required: true }
  ]
}

// Выпадающий список
setInput: {
  parameters: [
    { name: 'source', type: 'string', enum: ['HDMI1', 'HDMI2', 'USB'] }
  ]
}

// Число с пределами
setVolume: {
  parameters: [
    { name: 'level', type: 'number', min: 0, max: 100 }
  ]
}

// Булево
setPower: {
  parameters: [
    { name: 'value', type: 'boolean', required: true }
  ]
}

Если у параметра указано required: true, узел не позволит оставить поле пустым.

Динамическое заполнение параметров из сообщения

Пример:

// Function-node перед Device Command
msg.parameters = { brightness: 80, contrast: 55 };
return msg;

Если параметр содержит enum, убедитесь, что переданное значение входит в список.