Как писать шаблоны для сторонних Modbus-устройств
Введение
Рекомендуем сперва поискать ваше устройств в Таблице поддерживаемых устройств — вдруг оно уже там есть. Если устройства в списке нет, но оно поддерживает протокол Modbus, то его можно подключить к контроллеру Wiren Board.
Существует два вида протокола Modbus:
- Modbus RTU — устройства соединяются по шине RS-485.
- Modbus TCP — устройства соединяются по локальной сети через Wi-Fi или Ethernet.
Независимо от вида протокола, алгоритм добавления поддержки устройства в наш контроллер будет такой:
- Ищете документацию на ваше устройство с описанием его modbus-регистров.
- Составляете шаблон для нашего драйвера wb-mqtt-serial.
- Копируете созданный шаблон на контроллер в папку
- Выбираете в веб-интерфейсе контроллера свой шаблон и указываете адрес.
Некоторые производители Modbus-устройств не придерживаются стандартов протокола, что может сказаться на работе всей шины. Поэтому рекомендуем проверять работу новых устройств на отдельной шине и только после того, как добьётесь стабильной работы, подключать к ней другие Modbus-устройства.
Подготовка
Устройство с Modbus RTU:
- Откройте документацию на устройство и найдите описание modbus-регистров и параметров подключения (Baud rate, Data bits, Parity, Stop bits, Slave ID).
- Подключите устройство к контроллеру по шине RS-485.
- Проверьте связь с устройством и правильность подключения:
- Остановите драйвер wb-mqtt-serial или иное ПО, которое опрашивает устройство.
- Подключитесь к устройству с помощью утилиты modbus_client и считайте данные из любого известного вам регистра.
Устройство Modbus TCP:
- Откройте документацию на устройство и найдите описание modbus-регистров и настроек подключения (адрес, порт).
- Утилита modbus_client в релизах до 2304 содержит ошибку, из-за которой она не может работать по протоколу Modbus TCP. Если успользуете устаревший релиз, то подключите устройство к компьютеру через Ethernet.
- Попробуйте считать из устройства значение одного известного вам регистра.
Если вы смогли получить содержимое регистра — вы всё делаете верно и можете продолжать.
Создание шаблона
Рассказать драйверу wb-mqtt-serial, который в контроллере работает с Modbus-устройствами можно двумя способами:
- Добавить регистры устройства прямо в веб-интерфейсе контроллера. Этот способ удобен для быстрой проверки работы.
- Создать шаблон, который описывает регистры устройства, их тип и другие параметры. Этот способ удобен для масштабирования: просто копируете шаблон на другой контроллер и в нём появляется поддержка вашего устройства.
Здесь мы рассмотрим создание простого шаблона. Упрощённо шаблон устройства выглядит так:
{
"device_type": "my-relay", // тип устройства — уникальный идентификатор
"title": "My Relay", // отображаемое название
"group": "g-relay", // группа, в которой будет отображаться шаблон. Список групп в документации
"device": {
"name": "MY-RELAY", // имя устройства, используется в MQTT
"id": "my-relay",
"groups": [ ], // группы параметров и каналов
"channels": [ ], // каналы, доступно в скриптах и на вкладке Устройства
"parameters": [ ], // параметры, можно менять в настройках устройства
"translations": { } // переводы
}
}
Полное описание смотрите в документации драйвера wb-mqtt-serial на Github.
Допустим, у нас есть одноканальное Modbus-реле, у которого таблица регистров, показанная ниже.
Адрес | Тип | Название | Назначение |
---|---|---|---|
0 | Discrete Input | Input 1 | Состояние входа устройства |
1 | Input Register | Input 1 Counter | Значение счётчика входов |
3 | Coil | Relay 1 | Состояние выхода и управление им |
10 | Holding | Input Mode | Выбор режима взаимодействия входов с выходами |
В таком случае шаблон будет выглядеть так:
{
"device_type": "my-relay",
"title": "My Relay",
"group": "g-relay",
"device": {
"name": "MY-RELAY",
"id": "my-relay",
"groups": [
{
"title": "Channels",
"id": "channels",
"order": 0
},
{
"title": "Settings",
"id": "settings",
"order": 1
}
],
"channels": [
{
"name": "Input 1",
"reg_type": "discrete",
"address": 0,
"type": "switch",
"group": "channels"
},
{
"name": "Input 1 Counter",
"reg_type": "input",
"address": 1,
"type": "value",
"group": "channels"
},
{
"name": "Relay 1",
"reg_type": "coil",
"address": 3,
"type": "switch",
"group": "channels"
}
],
"parameters": [
{
"id": "input1",
"title": "Input Mode",
"reg_type": "input",
"address": 10,
"format": "s8",
"enum": [
1,
2
],
"enum_titles": [
"Switch Relay",
"Not used"
],
"default": 1
}
],
"translations": {
"ru": {
"Channels": "Каналы",
"Settings": "Настройки",
"Input 1": "Вход 1",
"Input 1 Counter": "Вход 1 счетчик",
"Relay 1": "Реле 1",
"Input Mode": "Режим входа"
}
}
}
}
Загрузка шаблона на контроллер
Когда шаблон готов, его надо загрузить на контроллер:
- Сохраните шаблон в файл, например,
my-relay.json
и загрузите его на контроллер в папку/etc/wb-mqtt-serial.conf.d/templates
по инструкции. - Проверьте шаблон на синтаксические ошибки командой:
# wb-mqtt-serial -g <3>ERROR: [serial config] Failed to parse /etc/wb-mqtt-serial.conf.d/templates/my-best-template.json Failed to parse JSON /etc/wb-mqtt-serial.conf.d/templates/my-best-template.json:* Line 12, Column 5 Missing ',' or '}' in object declaration
- в примере в шаблоне my-best-template.json в строке 12, символ 5 ожидается
,
or}
, а находится что-то другое.
- Если с шаблоном всё в порядке, то перейдите в настройки драйвера и выберите ваш шаблон.
Отклонения от стандарта и что с ними делать
Оптимизация запросов драйвером
Стандартом Modbus RTU предусмотрен обязательный интервал тишины в 3.5 символа между фреймами данных (под символом подразумевается посылка, состоящая из стартового бита, битов данных, бита четности и стоп-битов).
Для ускорения опроса устройств Wiren Board мы соблюдаем этот интервал только перед первым запросом к следующему в цикле опроса устройству (параметр frame_timeout_ms в шаблонах устройств).
Поэтому, чтобы соответствовать требованиям протокола Modbus-RTU, нужно для сторонних устройств задавать параметр guard_interval_us. Этот параметр задает задержку перед записью каждого запроса в порт.
Нужное значение рассчитывается по формуле:
guard_interval_us = (3.5*11*10^6)/(скорость в бит/с).
Например, для скорости 9600 бит/с guard_interval_us = (3.5*11*10^6)/9600 = 4000 мкс
. При проблемах с подключением стороннего устройства для теста это значение можно увеличить (например до 100000 мкс), так как сторонние устройства иногда работают не совсем корректно.
Если каналы устройства периодически мигают красным
Со сторонними устройствами довольно частая ситуация, когда вы сделали шаблон, данные идут, но в логах сыпятся ошибки, а каналы устройства в веб-интерфейсе контроллера окрашивают красным.
Основная причина этому — устройство слишком медленно обрабатывает запросы нашего драйвера. Для начала мы рекомендуем подключить такие устройства на отдельную шину, чтобы они не тормозили работу нормальных устройств, а потом использовать рекомендации ниже.
Чтобы починить, попробуйте увеличить параметр guard_interval_us
вплоть до тысяч единиц, например, 5000. Если работа стабилизируется, потихоньку уменьшайте это значение до тех пор, пока ошибки не появятся вновь. Предыдущее значение, когда всё работало хорошо и будет вашим значением в шаблоне.
Ещё есть параметр response_timeout_ms
— это максимальное время ответа устройства в миллисекундах, по умолчанию 500 мс. С ним тоже можно аккуратно поэкспериментировать.
Не выставляйте без нужды огромных значений в этих параметрах — это замедлит опрос устройств на порту, куда подключено проблемное устройство. Подробнее о том, как работают эти параметры, смотрите в Диаграмме таймаутов цикла опроса.
Оба параметра пишутся в секцию device шаблона:
"device": {
"name": "BAC-6000ELNW",
"id": "bac-6000elnw",
"response_timeout_ms": 100,
"guard_interval_us": 5000,
...
}
Разные регистры для чтения состояния и управления
Иногда в сторонних устройствах встречаются особенности:
- В некоторые регистры можно только писать информацию, но нельзя считывать.
- Один и тот же параметр может читаться по одному адресу, а записываться по другому.
Для того, чтобы драйвер работал с такими устройствами без ошибок, мы добавили параметр write_address.
Писать можно, читать нельзя
Допустим, в нашем устройстве есть такой регистр только для записи.
Адрес | Тип | Название | Назначение |
---|---|---|---|
20 | Coil | Relay 1 Switch | Управление выходом. Только для записи. |
В этом случае канал надо описывать так:
{
"name": "Relay 1 Switch",
"write_address": "20",
"reg_type": "coil",
"type": "pushbutton",
"format": "u16",
"group": "channels",
"on_value": 1 // определяет, что записывать в регистр при нажатии
}
Писать в один регистр, читать из другого
Допустим, в нашем устройстве команда записывается в один регистр, а читается из другого. Этот метод можно использовать только, если у обоих регистров один тип. Если типы разные — создавайте два разных канала: на чтение и на запись.
Адрес | Тип | Название | Назначение |
---|---|---|---|
20 | Coil | Relay 1 Switch | Управление выходом. Только для записи. |
21 | Coil | Relay 1 State | Состояние выхода. Только для чтения. |
В этом случае канал надо описывать так: у нас будет один параметр в контроллере, но при обмене данными запись будет производиться один регистр, а чтение — из другого:
{
"name": "Relay 1",
"write_address": "20", // адрес, куда мы записываем команды
"address": "21", // адрес, по которому мы читаем состояние
"reg_type": "coil",
"type": "switch",
"format": "u16",
"group": "channels"
}
Произвольные значения в регистрах с бинарной логикой
Иногда бывает так, что по смыслу регистр должен представляться в веб-интерфейсе переключателем ВКЛ/ВЫКЛ, но допустимые значения у него не 1/0.
В этом случае вы описываете обычный канал с типом switch и указываете значения on_value
и off_value
, например:
{
"name": "Status",
"reg_type": "holding",
"address": "0",
"type": "switch",
"format": "u16",
"on_value": "0x00a5", // ВКЛ
"off_value": "0x005a" // ВЫКЛ
}
Теперь драйвер будет автоматически при чтении конвертировать указанные значения в положение переключателя, а при изменении положения переключателя — записывать указанные значения.