====== Функциональность драйвера Modbus v2 ====== LightHub можно настроить на работу в качестве шлюза с фактически любым устройством, поддерживающим Modbus RTU и работающем в режиме Slave В отличии от v1, драйвер можно настроить на работу в качестве шлюза с фактически любым устройством, поддерживающим Modbus RTU и работающем в режиме Slave Поддерживается как чтение так и запись в modbus устройства ===== Раздел "modbus" конфигурации ===== В корневом разделе modbus конфигурации описывается библиотека используемых типов устройств. Для каждого устройства в библиотеки возможно задать такие параметры как скорость обмена (раздел baud) /параметры четности (раздел serial), назначение и тип регистров (раздел par), а также, интервал и параметры опроса этих регистров (раздел poll) ==== раздел par ==== Данный раздел содержит информацию о назначении каждого опрашиваемого или управляемого регистра (или групп регистров) используемого устройства. Раздел состоит из 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 ==== задает интервал и параметры опроса регистров устройства Причем, в целях оптимизации обмена, можно опрашивать устройство командой чтения группы регистров. Пример: "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 "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 "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 ==== Пример: "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 "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 - цифровой идентификатор стандартного суффикса (см. полный перечень стандартных суффиксов) Далее, соберем фрагменты в более полный пример: "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. Измерения влажности передаются каждый раз: "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":"edem/air/vents/zal3/fan"}, "mode":{"emit":"edem/air/vents/zal3/mode"}, "pwr" :{"emit":"edem/air/vents/zal3/pwr"}, "sethum":{"emit":"edem/air/humzal3/set"}, "hum" :{"emit":"edem/air/humzal3/val","@S":null}, "setvoc":{"emit":"edem/air/voczal3/set"}, "voc" :{"emit":"edem/air/voczal3/val"}, "settemp" :{"item":"tzal3/set"}, "roomtemp" :{"emit":"edem/fl2/term_bedr/1/val"}, "ch_temp":{}, "ch_hum":{}, "ext_temp":{}, "out_temp":{}, "water_temp":{}, "heat_pwr":{} } ]] } Также, по-умолчанию, контроллер выполняет какие-то действия, только если регистр Модбас устройства поменялся не в результате записи в него со стороны контроллера(такая логика сделана, чтобы избежать зацикливания нотификаций) Например, если в регистре число 3 а мы записали в него 5 - никаких действий при опросе не произойдет. А вот если значение в регистре изменилось в результате каких-то внутренних процессов устройства (например, пользователь изменил значение температуры на панеле) - это значение будет обработано. Данную фильтрацию можно отменить, если в настройке параметра добавить переменную @V типа, отличного от целого. Например так: "fan" :{"emit":"edem/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 "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":"edem/s_out/vent/tset/set","@V":null}, "pmode":{"emit":"edem/s_out/vent/pmode/set","@V":null}, "fanspeed":{"emit":"edem/s_out/vent/fanspeed/set","@V":null}, "on":{"emit":"edem/s_out/vent/on/set","@V":null}, "rtemp":{"emit":"edem/s_out/vent/rtemp"}, "fanmin":{}, "fanmax":{} } ]], } ===== Mapping значений и команд контроллера в регистры Modbus ===== Рассмотрим на примере вентустановки Допустим, регистр 41 отвечает за включение установки. Соответственно, в него надо записать "1" для всех рабочих режимов: * AUTO * ON * HEAT * FAN_ONLY Для команды OFF записываем 0 Это реализуется строкой "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) "pmode":{"reg":43,"type":"u8l","id":1,"map":{"cmd":[["AUTO",3],["HEAT",2],["FAN_ONLY",1]]}}, теперь при переключении режимов в регистр 43 будет записываться 3 для Авторежима, 2 - для режима нагрева, 1 - для режима вентиляции Еще лайфхак: "humpwr":{"reg":39998,"map":{"cmd":[null,["ON",1],["OFF",0]],"val":null}}, В примере выше, в значения регистра будет маппироваться только команда, поступающая на humpwr. Команда ON в 1, команда OFF в 0 первый null перед массивом соответствия команда -> значение по умолчанию, которое будет использоваться если не удастся подобрать команду. (null тут можно не указывать) Если не подобрали - пытаемся преобразовать по значению Если не использовать "val":null - то значения, отправляемые в humpwr/set будут передаваться в регистр без преобразований. Использование null обеспечивает игнорирование значений (не команд) (описание не завершено) "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":"edem/s_out/vent/set","@V":null}, "pmode":{"emit":"edem/s_out/vent/cmd","@V":null}, "fanspeed":{"emit":"edem/s_out/vent/fan","@V":null}, "on":{"emit":"edem/s_out/vent/oncmd","@V":null}, "ruheat":{"emit":"edem/s_out/vent/ruheat"}, "ruslow":{"emit":"edem/s_out/vent/rupump"}, "ramode":{"emit":"edem/s_out/vent/ramode"}, "rfanspd":{"emit":"edem/s_out/vent/rfanspd"}, "rfanlvl":{"emit":"edem/s_out/vent/rfanlvl"}, "rpumpon":{"emit":"edem/s_out/vent/rpumpon"}, "rtemp":{"emit":"edem/s_out/vent/rtemp"}, "rtempset":{"emit":"edem/s_out/vent/rtempset"}, "fault":{"emit":"edem/s_out/vent/fault"}, "tautoheat":{}, "tautocool":{}, "di":{}, "dp":{}, "dt":{}, "fanmin":{}, "fanmax":{}, "tmreheat":{}, "tairfreeze":{}, "twaterstart":{}, "twatershut":{}, "twateridle":{}, "fanauto":{"emit":"edem/s_out/vent/fanauto/set","@V":null}, "numspeed":{}, "offbyfan":{} } ]] }