Files
2026-10-11 01:27:15 +03:00

600 lines
34 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Настройка Modbus
> Источник: Wiki LightHub (lazyhome.ru), страницы «настройка\_modbus» и «режим\_шлюза\_ip\_modbus». Снапшот от 2026-10-02.
## Функциональность драйвера Modbus v2
LightHub можно настроить на работу в качестве шлюза с фактически любым устройством, поддерживающим Modbus RTU и работающем в режиме Slave
В отличии от v1, драйвер можно настроить на работу в качестве шлюза с фактически любым устройством, поддерживающим Modbus RTU и работающем в режиме Slave
Поддерживается как чтение так и запись в modbus устройства
## Раздел "modbus" конфигурации
В корневом разделе modbus конфигурации описывается библиотека используемых типов устройств.
Для каждого устройства в библиотеки возможно задать такие параметры как скорость обмена (раздел baud) /параметры четности (раздел serial), назначение и тип регистров (раздел par), а также, интервал и параметры опроса этих регистров (раздел poll)
### раздел par
Данный раздел содержит информацию о назначении каждого опрашиваемого или управляемого регистра (или групп регистров) используемого устройства. Раздел состоит из JSON объектов следующего формата:
```json
"t_heat":{"reg":8,"map":[0,1024,0,1024],"type":"i16"},
```
В примере выше:
- "t\_heat" - имя параметра устройства.
- "reg" - регистр (или стартовый HOLDING REGISTER) параметра
- "ir" - INPUT REGISTER или "reg"- HOLDING REGISTER или "coil": COIL или "din" - DISCRETE INPUT
- "map" - правила преобразования (TBD)
- "type":"i16" - указывает, что значение - 16-ти битное число со знаком
Возможные варианты типов:
- i16 - знаковый 16-ти битный регистр (по умолчанию)
- i32 - знаковый 32-ти битный регистр (первый регистр - младшие биты, следующий - старшие)
- u16 - беззнаковый 16-ти битный регистр
- u32 - беззнаковый 32-ти битный регистр (первый регистр - младшие биты, следующий - старшие)
- i8h - старшие 8 бит регистра (при записи значения, другие 8 бит будут заполнены значением 0xFF)
- i8l - младшие 8 бит регистра (при записи значения, другие 8 бит будут заполнены значением 0xFF)
- x10 - дробное число с точностью до 0.1 полученное путем деления значения 16 битного регистра со знаком на 10 (часто используется в разных устройствах для кодирования температуры, например, значение 124 в регистре соответствует 12,4)
- 100 - дробное число с точностью до 0.01 полученное путем деления значения 16 битного регистра со знаком на 100
### раздел poll
задает интервал и параметры опроса регистров устройства
Причем, в целях оптимизации обмена, можно опрашивать устройство командой чтения группы регистров.
Пример:
```json
"poll":{"regs":[[40000,40014],[30000,30002],40099],"delay":10000}
```
Обозначает, что с интервалом 10 сек будут произведены следующие три опроса:
- Команда чтения группы из 15-ти регистров с 40000 по 40014
- Команда чтения группы из 3-x регистров с 30000 по 30002
- Команда чтения одиночного регистра 40099
Для каждого из успешно прочитанных регистров, проводится поиск соответствия в разделе par и ir
Если есть запись, соответствующая данному регистру, прочитанные значения преобразовываются (см описание map) и направляются в MQTT и/или локальным объектам контроллера
Отправка происходит, если с момента последнего опроса значение регистра изменилось
кроме regs допустимы irs и coils
Или вот пример для сенсора Sensair S8
Опрашиваем INPUT REGISTERS 0-3,21,25-30 и HOLDING REGISTERS 0,1,31
```json
"sensair":{"baud":9600,
"poll":{"irs":[[0,3],21,[25,30]],"regs":[0,1,31],"delay":1000},
"par":{
"co2":{"ir":3},
"meterStat":{"ir":0},
"alarmStat":{"ir":1},
"outStat":{"ir":0},
"pwm":{"ir":21},
"typeid":{"ir":25,"type":"u32"},
"mapver":{"ir":27},
"fwver":{"ir":28},
"sensorid":{"ir":29,"type":"u32"},
"ack":{"reg":0},
"command":{"reg":1},
"abc":{"reg":31}
}
```
А вот пример подключения Modbus адаптера кондиционера Haier
```json
"modbus":
{
"haier":{"baud":9600,
"poll":{"regs":[[0,3]],"irs":[[0,1]],"coils":[0],"delay":5000},
"par":{
"pwr":{"coil":0,"map":{"cmd":[1,["OFF",0]],"val":null, "def":"acmode"},"id":1},
"acmode":{"reg":1,"map":{"cmd":[["FAN_ONLY",4],["HEAT",2],["COOL",1],["DRY",3],["AUTO",5]],"val":null},"id":1},
"$temp":{"ir":0},
"$fault":{"ir":1},
"set":{"reg":0,"id":2},
"fan":{"reg":2,"id":7,"map":{"cmd":[["OFF",0],["LOW",1],["HIGH",3],["MEDIUM",2],["AUTO",4]]}},
"lock":{"reg":3,"map":{"cmd":[["OFF",1],["ON",4]]}}
}
}
},
"items": {
"ac_2":[14,[128,
"haier",
{
"pwr":{"emit":"home/s_out/ac_2/cmd","@V":null},
"$temp":{"emit":"home/s_out/ac_2/$temp","@S":null},
"$fault":{"emit":"home/s_out/ac_2/$fault"},
"set":{"emit":"home/s_out/ac_2/set"},
"acmode":{"emit":"home/s_out/ac_2/$mode","@V":null},
"fan":{"emit":"home/s_out/ac_2/fan","@V":null},
"lock":{"emit":"home/s_out/ac_2/lock","@V":null}
}
]
]
}
}
```
### Разделы baud и serial
Пример:
```json
"baud":9600,
"serial":"8E1"
```
Соответственно, скорость обмена (по умолчанию, 9600) и параметр четности порта
Параметр четности - одно из значений ниже. По умолчанию 8N1
"8E1","8N1","8E2", "8N2", "8O1", "8O2", "8M1", "8S1", "7E1", "7N1", "7E2", "7N2", "7O1", "7O2", "7M1", "7S1"
## Раздел "items" конфигурации
В данном разделе задаются конкретные modbus устройства по шаблону, определенному в разделе modbus
```json
"items": {
"dimer1":[14,[1,
"dim4",
{
"ch1":{"emit":"aaa1,"item":"localdimmer1"},
"ch2":{"emit":"abc/aaa2"},
"ch3":{"item":"localdimmer3"},
"ch4":[{"emit":"aaa4,"item":"localdimmer4"},{"emit":"aaa44,"item":"localdimmer44"}]
}
]
],
```
В данном примере, создается item (объект) dimer1 с типом 14 (modbus v2) адресом modbus=1, на основе библиотечного устройства "dim4"
Устройство опрашивается согласно настройкам в библиотеке. А вот результаты опроса используются согласно настройкам в items.
В данном примере,
- "ch1" передается в MQTT топик "myhome/s\_out/aaa1" плюс локальному устройству "localdimmer1"
- "ch2" передается в MQTT топик "abc/aaa2" (если в названии топика есть / - он не дополняется слева статусным префиксом)
- "ch3" передается локальному устройству "localdimmer3"
- "ch4" передается в MQTT топик "myhome/s\_out/aaa4" и "myhome/s\_out/aaa44", локальному устройству "localdimmer4" и "localdimmer44" (использован массив из двух JSON объектов)
Строки ch1 - ch4 в данном примере, определенные в третем параметре массива конфигурации, далее будем называть "именованными параметрами modbus item"
Имена этих параметров используются для доступа на запись в modbus
Для данного примера, чтобы записать значение в первый канал modbus димера можно использовать mqtt топик:
```
myhome/in/dimer1/ch1/set
```
(Здесь и далее для простоты предполагаем что mqtt префикс по-умолчанию myhome и обращение через broadcast имя устройства in)
"ch1" используется как subItem устройства (ставится между именем item и суффиксом)
таким образом обеспечивается индивидуальный доступ к каждому описанному в настройках регистру modbus устройства из mqtt/httpApi
NB. Не надо именовать данные параметры именами стандартных суффиксов (set, cmd, fan и пр) (см. полный перечень стандартных суффиксов)
Также, возможно управлять не отдельными subItem - ами modbus устройства, а устройством в целом, по аналогии с тем, как это делается для всех типичных item-ов LightHub. То есть, задавать один общий параметр устройства через суффикс /set, команды через суффикс /cmd и пр (см. полный перечень стандартных суффиксов)
Для этого в разделе шаблона устройства, в описании конкретного параметра modbus можно задать связь параметра с конкретным суффиксом при помощи значения "id"=N, где N - цифровой идентификатор стандартного суффикса (см. полный перечень стандартных суффиксов)
Далее, соберем фрагменты в более полный пример:
```json
"modbus":
{
"airset":{
"baud":9600,
"serial":"8N1",
"poll":{"regs":[1,[8,24]],"delay":1000},
"par":{
"t_heat":{"reg":8,"map":[0,1024,0,1024],"type":"i16"},
"mode":{"reg":24}
}
},
"dim4":{
"poll":{"regs":[[0,4]],"delay":10000},
"par":{
"ch1":{"reg":0},
"ch2":{"reg":1},
"ch3":{"reg":2},
"ch4":{"reg":3}
}
},
"sensair":{"baud":9600,
"poll":{"irs":[[0,3],21,[25,30]],"regs":[0,1,31],"delay":1000},
"par":{
"co2":{"ir":3},
"meterStat":{"ir":0},
"alarmStat":{"ir":1},
"outStat":{"ir":0},
"pwm":{"ir":21},
"typeid":{"ir":25,"type":"u32"},
"mapver":{"ir":27},
"fwver":{"ir":28},
"sensorid":{"ir":29,"type":"u32"},
"ack":{"reg":0},
"command":{"reg":1},
"abc":{"reg":31}
}
}
},
"panel":{
"poll":{"regs":[[40000,40014],[30000,30002]],"delay":10000},
"par":{
"fan" :{"reg":40000},
"mode" :{"reg":40001},
"set" :{"reg":40002},
"pwr" :{"reg":40003},
"alm01":{"reg":40004},
"alm17":{"reg":40005},
"alm33":{"reg":40006},
"sethum" :{"reg":40007},
"setvoc" :{"reg":40008},
"temp" :{"reg":30000},
"hum" :{"reg":30001},
"voc" :{"reg":30002},
"ch_temp" :{"reg":40009},
"ext_temp" :{"reg":40010},
"out_temp" :{"reg":40011},
"water_temp" :{"reg":40012},
"ch_hum" :{"reg":40013},
"heat_pwr":{"reg":40014}
}
}
},
"items": {
"airset1":[14,[10,"airset",{"t_heat":{"emit":"aaa"}}]],
"panel1":[14,[2,"panel"]],
"dimer1":[14,[1,"dim4",
{
"ch1":{"emit":"aaa1,"item":"localdimmer1"},
"ch2":{"emit":"abc/aaa2"},
"ch3":{"item":"localdimmer3"},
"ch4":[{"emit":"aaa4,"item":"localdimmer4"},{"emit":"aaa44,"item":"localdimmer44"}]
}
]
]
},
"sensair1":[14,[254,
"sensair",
{"co2":{"emit":"co2"}}
]
]
}
}
```
## Подавление повторных значений
В процессе опроса устройств, драйвер запоминает последнее считанное из регистра modbus значение во внутреннюю переменную @S внутри именованного параметра item
Если значение не изменилось с последнего считывания - операция по отправки значения в modbus или локальному обьекту не производится
Данное поведение можно отменить. Если данную переменную определить в конфиге и ее тип будет отличен от целого значения, последнее опрошенное значение не будет сохраняться, что приведет к отправке результата каждого опроса в mqtt или локальному обьекту
Пример - панель управления климатом Zentec. Измерения влажности передаются каждый раз:
```json
"modbus":
{
"panel":{
"serial":"8E1",
"poll":{"regs":[[40000,40014],[30000,30002]],"delay":10000},
"par":{
"fan" :{"reg":40000,"map":[1,[0,7,0,100]],"id":11},
"mode" :{"reg":40001,"mapcmd":[2,[[1,"FAN_ONLY"],[2,"HEAT"],[4,"COOL"],[8,"AUTO"]]]},
"settemp" :{"reg":40002,"id":12},
"pwr" :{"reg":40003,"mapcmd":[2,[[0,2],[1,1]]]},
"alm01":{"reg":40004},
"alm17":{"reg":40005},
"alm33":{"reg":40006},
"sethum" :{"reg":40007,"id":13},
"setvoc" :{"reg":40008,"id":14},
"roomtemp" :{"reg":30000,"type":"x10"},
"hum" :{"reg":30001},
"voc" :{"reg":30002},
"ch_temp" :{"reg":40009,"type":"x10","id":15},
"ext_temp" :{"reg":40010,"type":"x10","id":16},
"out_temp" :{"reg":40011,"type":"x10","id":17},
"water_temp" :{"reg":40012,"type":"x10","id":18},
"ch_hum" :{"reg":40013},
"heat_pwr":{"reg":40014}
}
}
},
items:{
"p_zal3":[14,[2,"panel",
{
"fan" :{"emit":"house/air/vents/zal3/fan"},
"mode":{"emit":"house/air/vents/zal3/mode"},
"pwr" :{"emit":"house/air/vents/zal3/pwr"},
"sethum":{"emit":"house/air/humzal3/set"},
"hum" :{"emit":"house/air/humzal3/val","@S":null},
"setvoc":{"emit":"house/air/voczal3/set"},
"voc" :{"emit":"house/air/voczal3/val"},
"settemp" :{"item":"tzal3/set"},
"roomtemp" :{"emit":"house/fl2/term_bedr/1/val"},
"ch_temp":{},
"ch_hum":{},
"ext_temp":{},
"out_temp":{},
"water_temp":{},
"heat_pwr":{}
}
]]
}
```
Также, по-умолчанию, контроллер выполняет какие-то действия, только если регистр Модбас устройства поменялся не в результате записи в него со стороны контроллера(такая логика сделана, чтобы избежать зацикливания нотификаций)
Например, если в регистре число 3 а мы записали в него 5 - никаких действий при опросе не произойдет. А вот если значение в регистре изменилось в результате каких-то внутренних процессов устройства (например, пользователь изменил значение температуры на панеле) - это значение будет обработано.
Данную фильтрацию можно отменить, если в настройке параметра добавить переменную @V типа, отличного от целого.
Например так:
```json
"fan" :{"emit":"house/air/vents/zal3/fan","@V":null},
```
В этом случае, после того, как мы что-то записали в регистр, при следующем же опросе устройства, это будет обнаружено (если значение изменилось) и будут предприняты настроенные действия (например, значение будет отправлено по MQTT и отобразится в мобильном приложении Home Assistant)
~~И еще один фильтр: при попытке записать в регистр значение, не отличающееся от последнего полученного при опросе, такая попытка будет проигнорирована. Такое поведение отключить нельзя.~~
## Запись в Modbus устройство
Параметры Модбас устройства можно записывать аналогично, со всеми остальными items контроллера - с использованием MQTT, прямым обращением из других items контроллера, через http API
Рассмотрим на примере вентустановки:
Скорость можно поменять, обращаясь к "суб-топику" fanspeed
//vent/fanspeed/set// (для прямого обращения по api или как к item-у)
Для обращения через MQTT топик будет выглядеть как myhome/devicename/vent/fanspeed/set
Внимание, для названий субтопиков следует избегать стандартных имен суффиксов (cmd,set,hsv,rgb,fan,mode,hue,sat,temp,val,del,raw)
Параметры fanmin и fanmax доступны только на запись (контроллер не будет отслеживать изменение их значений)
также, есть возможность "прикрепить" параметры к стандартным суффиксам item-а (set,cmd,fan и пр), указав числовой параметр id, соответствующий данному стандартному суффиксу в описании этого параметра.
Перечень суффиксов:
```
CMD 1
SET 2
HSV 5
RGB 6
FAN 7
MODE 8
HUE 9
SAT 10
TEMP 11
VAL 12
DELAYED 13
RAW 14
```
в примере ниже, это значит, что альтернативный описанному метод поменять скорость:
//vent/fan//
А при записи в vent/cmd это значение установится как в регистр 41 (параметр on) так и в регистр 43 (параметр pmode) так как у них указан одинаковый id=1, соответствующий суффиксу /cmd
```json
"modbus":
{
"airset":{
"baud":9600,
"serial":"8E1",
"poll":{"regs":[[0,60],1008],"delay":2000},
"par":{
"rtemp":{"reg":5,"type":"100", "mode":"r"},
"fanmin":{"reg":50,"type":"u8l"},
"fanmax":{"reg":51,"type":"u8l"},
"on":{"reg":41,"type":"u8l","id":1},
"fanspeed":{"reg":42,"type":"u8l","id":7},
"pmode":{"reg":43,"type":"u8l","id":1,"map":{}},
"tset":{"reg":44,"type":"u8l","id":2},
"numspeed":{"reg":60,"type":"u8l"},
"fault":{"reg":1008}
}
}
},
"items": {
"vent":[14,[247,"airset",
{
"tset":{"emit":"house/s_out/vent/tset/set","@V":null},
"pmode":{"emit":"house/s_out/vent/pmode/set","@V":null},
"fanspeed":{"emit":"house/s_out/vent/fanspeed/set","@V":null},
"on":{"emit":"house/s_out/vent/on/set","@V":null},
"rtemp":{"emit":"house/s_out/vent/rtemp"},
"fanmin":{},
"fanmax":{}
}
]],
}
```
## Mapping значений и команд контроллера в регистры Modbus
Более подробно настройка преобразований описана в отдельном разделе [использование преобразований map](../core/analog_inputs_and_map.md)
Далее, разобраны примеры применения
Рассмотрим на примере вентустановки
Допустим, регистр 41 отвечает за включение установки. Соответственно, в него надо записать "1" для всех рабочих режимов:
- AUTO
- ON
- HEAT
- FAN\_ONLY
Для команды OFF записываем 0
Это реализуется строкой
```json
"on":{"reg":41,"type":"u8l","id":1,"map":{"cmd":[["OFF",0],["ON",1],["AUTO",1],["HEAT",1],["FAN_ONLY",1]]}}
```
наличие "id"=1 позволяет не только напрямую управлять данным регистром при помощи именованного параметра "on" (топика air/on/set) но и привязать его к командному суффиксу канала
Соответственно, доступно управление по командному топику air/cmd
В нашей вентустановке за переключение режима работы (нагрев-вентиляция-авто) отвечает отдельный регистр 43
настроим отображение режима работы на требуемые значения данного регистра и, также, привяжем это отображение к командному топику ("id":1)
```json
"pmode":{"reg":43,"type":"u8l","id":1,"map":{"cmd":[["AUTO",3],["HEAT",2],["FAN_ONLY",1]]}},
```
теперь при переключении режимов в регистр 43 будет записываться 3 для Авторежима, 2 - для режима нагрева, 1 - для режима вентиляции
Еще лайфхак:
```json
"humpwr":{"reg":39998,"map":{"cmd":[null,["ON",1],["OFF",0]],"val":null}},
```
В примере выше, в значения регистра будет маппироваться только команда, поступающая на humpwr. Команда ON в 1, команда OFF в 0
первый null перед массивом соответствия команда -> значение по умолчанию, которое будет использоваться если не удастся подобрать команду. (null тут можно не указывать)
Если не подобрали - пытаемся преобразовать по значению
Если не использовать "val":null - то значения, отправляемые в humpwr/set будут передаваться в регистр без преобразований. Использование null обеспечивает игнорирование значений (не команд)
(описание не завершено)
```json
"modbus":
{
"airset":{
"baud":9600,
"serial":"8E1",
"poll":{"regs":[[0,60],1008],"delay":2000},
"par":{
"on":{"reg":41,"type":"u8l","id":1,"map":{"cmd":[["OFF",0],["ON",1],["AUTO",1],["HEAT",1],["FAN_ONLY",1]]}},
"fanspeed":{"reg":42,"type":"u8l","id":7,"map":{"cmd":"fan"}},
"offbyfan":{"reg":41,"type":"u8l","id":7,"map":{"cmd":[1,["OFF",0]]}},
"pmode":{"reg":43,"type":"u8l","id":1,"map":{"cmd":[["AUTO",3],["HEAT",2],["FAN_ONLY",1]]}},
"tset":{"reg":44,"type":"u8l","id":2},
"di":{"reg":47,"type":"u8l"},
"dp":{"reg":48,"type":"u8l"},
"dt":{"reg":49,"type":"u8l"},
"fault":{"reg":1008}
}
}
}
},
"items":
{
"vent":[14,[247,"airset",
{
"tset":{"emit":"house/s_out/vent/set","@V":null},
"pmode":{"emit":"house/s_out/vent/cmd","@V":null},
"fanspeed":{"emit":"house/s_out/vent/fan","@V":null},
"on":{"emit":"house/s_out/vent/oncmd","@V":null},
"ruheat":{"emit":"house/s_out/vent/ruheat"},
"ruslow":{"emit":"house/s_out/vent/rupump"},
"ramode":{"emit":"house/s_out/vent/ramode"},
"rfanspd":{"emit":"house/s_out/vent/rfanspd"},
"rfanlvl":{"emit":"house/s_out/vent/rfanlvl"},
"rpumpon":{"emit":"house/s_out/vent/rpumpon"},
"rtemp":{"emit":"house/s_out/vent/rtemp"},
"rtempset":{"emit":"house/s_out/vent/rtempset"},
"fault":{"emit":"house/s_out/vent/fault"},
"tautoheat":{},
"tautocool":{},
"di":{},
"dp":{},
"dt":{},
"fanmin":{},
"fanmax":{},
"tmreheat":{},
"tairfreeze":{},
"twaterstart":{},
"twatershut":{},
"twateridle":{},
"fanauto":{"emit":"house/s_out/vent/fanauto/set","@V":null},
"numspeed":{},
"offbyfan":{}
}
]]
}
```
## Режим шлюза IP-Modbus
На контроллере может быть активизирован режим доступа к шине MODBUS через TCP/IP
Данный режим может быть полезен для удаленного управления/перепрошивки устройств, которыми управляет контроллер в режиме Modbus-Master
Доступ обеспечивается как через протокол UDP так и через TCP с использованием стандартного порта 502
Для активации такого шлюза, надо перекомпилировать прошивку со следующими опциями (см [опции компиляции](https://www.lazyhome.ru/dokuwiki/doku.php?id=опции_компиляции))
```
-D IPMODBUS
```
Будет использован порт Modbus v1 и его настройки. Изменить настройки можно следующими директивами:
```
-D MODBUS_SERIAL_PARAM=SERIAL_8N1
-D MODBUS_SERIAL_BAUD=9600
```
В примере выше даны настройки по-умолчанию
## Сверка с кодом (2026-10-02)
Сверено с `lighthub/modules/out_modbus.cpp`, `lighthub/modules/out_modbus.h`, `lighthub/ipmodbus.cpp`, `lighthub/ipmodbus.h`, `lighthub/options.h`, `lighthub/item.h`.
- Ключи шаблона устройства, подтвержденные в коде (`out_Modbus::getConfig`, out\_modbus.cpp:139-249): `baud` (по умолчанию 9600), `serial` (по умолчанию `SERIAL_8N1`), `poll` с подклавишами `regs`, `irs`, `coils`, `dins` и `delay` (по умолчанию 1000 мс — в wiki значение по умолчанию не указано), `par`.
- Ключи параметра в `par` (out\_modbus.cpp:433-467, 1084-1085, 203-204): `reg`, `ir`, `coil`, `din`, `type`, `map`, `id`, `prefetch` (boolean). Ключ `prefetch` поддерживается кодом (при записи предварительно опрашивается регистр), но в wiki не описан.
- Поддерживаемые типы (таблица `regSize_P`, out\_modbus.cpp:47-60): `i16`, `i32`, `u16`, `u32`, `i8h`, `i8l`, `u8h`, `u8l`, `x10`, `100` — только эти типы. Список в wiki неполный: отсутствуют `u8h` и `u8l`.
- Ключ `mapcmd` (пример панели Zentec, раздел «Подавление повторных значений») в коде не найден — поддерживается только `map`; вероятно, опечатка/устаревшее имя.
- Ключ `"mode":"r"` в примере параметра `rtemp` в out\_modbus.cpp не обрабатывается — вероятно, унаследован от драйвера Modbus v1.
- В коде есть ключ `sendchanges` (out\_modbus.cpp:1085), не упомянутый в wiki.
- Перечень числовых идентификаторов суффиксов в wiki (HUE 9, SAT 10, TEMP 11, VAL 12, DELAYED 13, RAW 14) **не совпадает** с текущим кодом (item.h:27-39): `CMD 1, SET 2, VAL 3, DELAYED 4, HSV 5, RGB 6, FAN 7, MODE 8, CTRL 9, HUE 10, SAT 11, TEMP 12, RAW 13`. Примеры с `id` 11-18 (Zentec, вентустановка) соответствуют старой нумерации; при использовании на актуальной прошивке значения `id` нужно пересматривать.
- Описание типа `i8h`/`i8l` («при записи другие 8 бит будут заполнены 0xFF») не подтверждается кодом: запись идет целиком в 16-битный регистр смещенным байтом (`writeSingleRegister`, out\_modbus.cpp:897-905), остальные биты обнуляются.
- Механика `@S`/`@V` (подавление повторных значений и «записали сами — не обрабатываем») подтверждена: `createLastMeasured`/`getLastMeasured`, сравнение с `@V` («Ignored - equal with setted val», out\_modbus.cpp:676-701).
- Шлюз IP-Modbus: компиляционная опция `IPMODBUS` (ipmodbus.h:1), порты TCP и UDP — оба 502 (ipmodbus.h:26-27), значения по умолчанию `MODBUS_SERIAL_BAUD 9600` и `MODBUS_SERIAL_PARAM SERIAL_8N1` (options.h:154-159) — совпадает с wiki.