Стояла задача научиться управлять шлагбаумом через RS-485. Шлагбаум питерской конторы АПС-СПБ с китайской автоматикой управления. Можно управлять сухими контактами и через gsm модуль, который поддерживает управление через приложение (по факту замыкает тот же сухой контакт). Но как известно, это не наш метод!

Для начала была запрошена информация у производителя, на что был получен файл для диагностики автоматики, который работает под Windows. Поигравшись с открытие-закрытием шлагбаума перешел к изучению трафика, проходящего через порт. В результате чего нашел, что для открытия и закрытия программа отправляет команду 1 в coil регистры 0 и 1. Что ж, уже хорошо, уже можно отправлять команду на открытие или закрытие. Но нам же нужны статусы! С помощью снифера так же удалось найти, что программа «общается» с автоматикой по следующим адресам:
-
Holding 0 примерно 30 регистров, что соответствует настройке параметров работы шлагбаума, которые так же дублируются на самой автоматике физически, т.е. их можно выставив «понажимав» кнопочки на самой автоматике.
-
Holding 53248 примерно 25 регистров. Тут передавались разные состояния и параметры, какой за что отвечает можно было только догадываться, либо сверять эти значения с теми, что выдавало приложение.
Сначала хотел сделать через найденые параметры Hall и Trans. Понятия не имею, что они значат, но в приложении отображались, и как сказал выше, нашел их адреса снифиром. Данные параметры приходили на адреса 53252 (Hall) и 53253 (Trans) и при открытом состоянии были 2/13 Hall/Trans, а при закрытом 6/1184 соответственно.
Дальше настала очередь «засунуть» все это добро в Wiren Board. Подключив автоматику ко 2му порту начал изучать запросы ответы с помощью утилиты modbus client. Далее, заметки на полях:
modbus_client --debug -mrtu -pnone -s2 /dev/ttyRS485-2 -a1 -t0x03 -r53248 -c 25 // чтение параметров начинается отсюда 25 регистров modbus_client --debug -mrtu -pnone -s2 /dev/ttyRS485-2 -a1 -t0x03 -r53252 // параметр Hall в приложении. если ответ 2 ОТКРЫТ. Если 6 - ЗАКРЫТ modbus_client --debug -mrtu -pnone -s2 /dev/ttyRS485-2 -a1 -t0x03 -r53253 // параметр Trans в приложении. если ответ 13 ОТКРЫТ. Если 1184 - ЗАКРЫТ modbus_client --debug -mrtu -pnone -s2 /dev/ttyRS485-2 -a1 -t0x05 -r0x00 0x01 // открытие шлагбаума! modbus_client --debug -mrtu -pnone -s2 /dev/ttyRS485-2 -a1 -t0x05 -r0x01 0x01 // закрытие шлагбаума!
Но тут пришла хорошая новость, производитель шлагбаумов, по моей просьбе запросил описание протокола у производителя автоматики, и те наконец ответили. Таким образом у меня появилось еще два файла — RS485 interface protocol.pdf и 操作说明 RS485.docx. Второй был прогнан через яндекс переводчик, и получился — 操作说明 RS485 (1).docx. Попытавшись проникнуться дзеном понять что, же имели ввиду китайцы, пришел к выводу, что все необходимые состояния передаются в 1 адресе, и адрес этот должен быть 14-м. Не спрашивайте как, но я нашел ЭТОТ 14 адрес (там целая детективная история). В общем, 53268 — это именно тот адрес. который содержит в себе состояния шлагбаума. Раскладывается достаточно просто, если понимать процесс. Полученное значение, это бинарное состояние значений зашифрованное в HEX. Сам я не сварщик, но с подсказки старших товарищей накидал себе такую картинку, по ней уже смог понять, что состояния получены верные.

Дальше я просто создал примитивное устройство в wiren board по шаблону, в которое вывел интересующие меня параметры и состояния. Шаблон — config-SPbarrier-03L.json
{ "device_type": "SPbarrier", "device": { "name": "SPbarrier-03L", "id": "spb-03l", "max_read_registers": 60, "response_timeout_ms": 200, "frame_timeout_ms": 36, "channels": [ { "name": "K1", "reg_type": "coil", "address": 0, // Команда на открытие "type": "switch" }, { "name": "K2", "reg_type": "coil", "address": 1, // Команда на закрытие "type": "switch" }, { "name": "K3", "reg_type": "coil", "address": 2, // Команда на остановку "type": "switch" }, { "name": "K4", "reg_type": "coil", "address": 3, // Команда на самотестирование "type": "switch" }, { "name": "Param 0", "reg_type": "holding", "address": 0, // Скорость открытия 25-95, шаг 1 "type": "value" }, { "name": "Param 1", "reg_type": "holding", "address": 1, // Скорость закрытия 25-95, шаг 1 "type": "value" }, { "name": "Param 2", "reg_type": "holding", "address": 2, // "type": "value" }, { "name": "Param 3", "reg_type": "holding", "address": 3, // "type": "value" }, { "name": "Param 4", "reg_type": "holding", "address": 4, // "type": "value" }, { "name": "Param 5", "reg_type": "holding", "address": 5, // "type": "value" }, { "name": "Param 6", "reg_type": "holding", "address": 6, // "type": "value" }, { "name": "Param 7", "reg_type": "holding", "address": 7, // "type": "value" }, { "name": "Param 8", "reg_type": "holding", "address": 8, // "type": "value" }, { "name": "Param 9", "reg_type": "holding", "address": 9, // Задержка перед автоматическим закрытием, 0-90 секунд, шаг 1. 0 - не будет закрываться автоматически "type": "value" }, { "name": "Param 10", "reg_type": "holding", "address": 10, // "type": "value" }, { "name": "Param 11", "reg_type": "holding", "address": 11, // "type": "value" }, { "name": "Param 12", "reg_type": "holding", "address": 12, // "type": "value" }, { "name": "Param 13", "reg_type": "holding", "address": 13, // "type": "value" }, { "name": "Param 14", "reg_type": "holding", "address": 14, // "type": "value" }, { "name": "Param 15", "reg_type": "holding", "address": 15, // "type": "value" }, { "name": "Param 16", "reg_type": "holding", "address": 16, // RS-485 адрес, от 1 до 32 "type": "value" }, { "name": "Param 17", "reg_type": "holding", "address": 17, // Скорость RS-485 порта. 0 - 9600, 1 - 19200, 2 - 38400. Изменения вступают в силу после перезагрузке по питанию "type": "value" }, { "name": "Param 18", "reg_type": "holding", "address": 18, // "type": "value" }, { "name": "Param 19", "reg_type": "holding", "address": 19, // "type": "value" }, { "name": "Param 20", "reg_type": "holding", "address": 20, // "type": "value" }, { "name": "Param 21", "reg_type": "holding", "address": 21, // "type": "value" }, { "name": "Param 22", "reg_type": "holding", "address": 22, // "type": "value" }, { "name": "Param 23", "reg_type": "holding", "address": 23, // "type": "value" }, { "name": "Param 24", "reg_type": "holding", "address": 24, // "type": "value" }, { "name": "Param 25", "reg_type": "holding", "address": 25, // "type": "value" }, { "name": "Param 26", "reg_type": "holding", "address": 26, // "type": "value" }, { "name": "Param 27", "reg_type": "holding", "address": 27, // "type": "value" }, { "name": "Param 28", "reg_type": "holding", "address": 28, // "type": "value" }, { "name": "Param 29", "reg_type": "holding", "address": 29, // "type": "value" }, { "name": "Param 30", "reg_type": "holding", "address": 30, // "type": "value" }, { "name": "Param 31", "reg_type": "holding", "address": 31, // "type": "value" }, { "name": "Param 32", "reg_type": "holding", "address": 32, // "type": "value" }, { "name": "Param 33", "reg_type": "holding", "address": 33, // "type": "value" }, { "name": "Param 34", "reg_type": "holding", "address": 34, // "type": "value" }, { "name": "Param 35", "reg_type": "holding", "address": 35, // "type": "value" }, { "name": "Param 36", "reg_type": "holding", "address": 36, // № версии "type": "value" }, { "name": "Input 1", "reg_type": "holding", "address": 53252, // "type": "value" }, { "name": "Input 2", "reg_type": "holding", "address": 53253, // "type": "value" }, { "name": "Input 3", "reg_type": "holding", "address": 53262, // "type": "value" }, { "name": "Status 0", "type": "switch", "reg_type": "holding", "address": "53268:0:1", // нулевой бит (первый справа, с младшего бита) маски в регистре 53268 (регистр работы, рабочее состояние, в движении сейчас или нет. 1 - в движении, 0 - в покое) "format": "u16" }, { "name": "Status 1", "type": "switch", "reg_type": "holding", "address": "53268:1:1", // первый бит (второй справа, с младшего бита) маски в регистре 53268 (направление движения. 1 - вниз, закрывается. 0 - вверх, открывается) "format": "u16" }, { "name": "Status 2", "type": "switch", "reg_type": "holding", "address": "53268:2:1", // питание "format": "u16" }, { "name": "Status 3", "type": "switch", "reg_type": "holding", "address": "53268:3:1", // пусто "format": "u16" }, { "name": "Status 4", "type": "switch", "reg_type": "holding", "address": "53268:4:1", // нормальное питание - перевод с китайского "format": "u16" }, { "name": "Status 5", "type": "switch", "reg_type": "holding", "address": "53268:5:1", // псамотестирование "format": "u16" }, { "name": "Status 6", "type": "switch", "reg_type": "holding", "address": "53268:6:1", // самотестирование ошибка "format": "u16" }, { "name": "Status 7", "type": "switch", "reg_type": "holding", "address": "53268:7:1", // пусто "format": "u16" }, { "name": "Status 8", "type": "switch", "reg_type": "holding", "address": "53268:8:1", // пусто "format": "u16" }, { "name": "Status 9", "type": "switch", "reg_type": "holding", "address": "53268:9:1", // шлагбаум в нижнем положении "format": "u16" }, { "name": "Status 10", "type": "switch", "reg_type": "holding", "address": "53268:10:1", // шлагбаум в верхнем положении "format": "u16" }, { "name": "Status 11", "type": "switch", "reg_type": "holding", "address": "53268:11:1", // зафиксирован, не двигается "format": "u16" }, { "name": "Status 12", "type": "switch", "reg_type": "holding", "address": "53268:12:1", // зеленый свет горит "format": "u16" }, { "name": "Status 13", "type": "switch", "reg_type": "holding", "address": "53268:13:1", // красный свет горит "format": "u16" }, { "name": "Status 14", "type": "switch", "reg_type": "holding", "address": "53268:14:1", // пусто "format": "u16" }, { "name": "Status 15", "type": "switch", "reg_type": "holding", "address": "53268:15:1", // пусто "format": "u16" } ] } }
В идеале хотелось добавить это устройство в Sprut.Hub, и потом уже привязаться к необходимым статусам и командам виртуальным устройством «Гаражные ворота», но так и не смог написать шаблон :(. Поэтому пришлось привлекать тяжелую артиллерию — IOBROKER.
В IOBROKER создал несколько объектов под эту задачу, и набросал blockly в котором заложена логика работы «Гаражных ворот». Если коротко описать логику, то получается так:
-
Подписываемся на изменения TargetDoorState, поменялась и равна 0 — дергаем команду открыть, равна 1 — команду закрыть
-
подписываемся на Status 11 (шлагбаум в покое) если этот статус навен 0, то шлагбаум двигается и нужно понять куда? Status 1 равен 1 — закрывается, ставим CurrentDoorState на 3. Равен 0 — открывается, ставим CurrentDoorState на 2
-
Если Status 11 равен 1, значит шлагбаум в покое, значит нужно выяснить в каком именно «покое»? Status 9 = 1 — закрыт. Status 10 = 1 — открыт. CurrentDoorState на 1 и 0 соответственно.
<xml xmlns="https://developers.google.com/blockly/xml"> <block type="on" id="2~fUG!q;)AV!BlB:75~^" x="87" y="112"> <field name="OID">0_userdata.0.office.Шлагбаум.TargetDoorState</field> <field name="CONDITION">ne</field> <field name="ACK_CONDITION"></field> <statement name="STATEMENT"> <block type="controls_if" id="goMrbn(TOB~qbql{4w_Y"> <mutation elseif="1"></mutation> <value name="IF0"> <block type="logic_compare" id="!C(#0F#[?Ovr�^jdp/"> <field name="OP">EQ</field> <value name="A"> <block type="on_source" id="-uMn;5$gG)b)#B9LTri*"> <field name="ATTR">state.val</field> </block> </value> <value name="B"> <block type="math_number" id="uij)^.$@Y=$UU|foD8iq"> <field name="NUM">0</field> </block> </value> </block> </value> <statement name="DO0"> <block type="control" id="[?In}s2mIU4*JY?V~}K["> <mutation xmlns="http://www.w3.org/1999/xhtml" delay_input="false"></mutation> <field name="OID">mqtt.5.devices.spb-03l_1.controls.K1.on</field> <field name="WITH_DELAY">FALSE</field> <value name="VALUE"> <block type="math_number" id="6n)7u;eI9)h]^8nUE;GO"> <field name="NUM">1</field> </block> </value> </block> </statement> <value name="IF1"> <block type="logic_compare" id="?`%YhelMFC$jzRby~5bn"> <field name="OP">EQ</field> <value name="A"> <block type="on_source" id="9o Na~fIRi{UkpBg7A |"> <field name="ATTR">state.val</field> </block> </value> <value name="B"> <block type="math_number" id="mHjT0~0MnpXld~D!yc$["> <field name="NUM">1</field> </block> </value> </block> </value> <statement name="DO1"> <block type="control" id="u)Jt{q^Gn]Sjjf_[Pe`z"> <mutation xmlns="http://www.w3.org/1999/xhtml" delay_input="false"></mutation> <field name="OID">mqtt.5.devices.spb-03l_1.controls.K2.on</field> <field name="WITH_DELAY">FALSE</field> <value name="VALUE"> <block type="math_number" id="J8l%[^qJ8`OtMg)CG.^4"> <field name="NUM">1</field> </block> </value> </block> </statement> </block> </statement> </block> <block type="on" id="z?J^wZmkcwh7M`dH gs/" x="63" y="488"> <field name="OID">mqtt.5.devices.spb-03l_1.controls.Status_11</field> <field name="CONDITION">ne</field> <field name="ACK_CONDITION"></field> <statement name="STATEMENT"> <block type="controls_if" id="H3CZvm5!3):BWq)hV?,2"> <mutation elseif="1"></mutation> <value name="IF0"> <block type="logic_compare" id="VhUg6q1Ajn]tfIbR[0I]"> <field name="OP">EQ</field> <value name="A"> <block type="on_source" id="be,@HHLgo7/zdlf}O[-s"> <field name="ATTR">state.val</field> </block> </value> <value name="B"> <block type="math_number" id="pO!n-q]?@M9og#iK[pgm"> <field name="NUM">0</field> </block> </value> </block> </value> <statement name="DO0"> <block type="controls_if" id="3fqd1p$fao4G6C`g4?nz"> <mutation elseif="1"></mutation> <value name="IF0"> <block type="logic_compare" id="NvL#*|BP$5w_wf,ZS%UR"> <field name="OP">EQ</field> <value name="A"> <block type="get_value" id="ws!-jM-6J`/g:oeoV$o/"> <field name="ATTR">val</field> <field name="OID">mqtt.5.devices.spb-03l_1.controls.Status_1</field> </block> </value> <value name="B"> <block type="math_number" id="~q|Sb@~]F.RG,fIn]F0^"> <field name="NUM">1</field> </block> </value> </block> </value> <statement name="DO0"> <block type="control" id="d*j|hB/M??KSX ?e0,qS"> <mutation xmlns="http://www.w3.org/1999/xhtml" delay_input="false"></mutation> <field name="OID">0_userdata.0.office.Шлагбаум.CurrentDoorState</field> <field name="WITH_DELAY">FALSE</field> <value name="VALUE"> <block type="math_number" id="m@o-@h8!`Q%!3RMN{eT-"> <field name="NUM">3</field> </block> </value> </block> </statement> <value name="IF1"> <block type="logic_compare" id="thx**_4j!7(;-iEWjKq5"> <field name="OP">EQ</field> <value name="A"> <block type="get_value" id="M~9ab(3N`HUP{hk8jVMN"> <field name="ATTR">val</field> <field name="OID">mqtt.5.devices.spb-03l_1.controls.Status_1</field> </block> </value> <value name="B"> <block type="math_number" id="crL#$VJ`%3~.%a`IQNf~"> <field name="NUM">0</field> </block> </value> </block> </value> <statement name="DO1"> <block type="control" id="?Yu#2W=IPCa{Su.,?oM$"> <mutation xmlns="http://www.w3.org/1999/xhtml" delay_input="false"></mutation> <field name="OID">0_userdata.0.office.Шлагбаум.CurrentDoorState</field> <field name="WITH_DELAY">FALSE</field> <value name="VALUE"> <block type="math_number" id="xO0cG[U48oq!-R;$Pm@["> <field name="NUM">2</field> </block> </value> </block> </statement> </block> </statement> <value name="IF1"> <block type="logic_compare" id="@@qiO6fjNmQ=Sg2[h^0:"> <field name="OP">EQ</field> <value name="A"> <block type="on_source" id="7jqPWha@6 ,]m)B/gzZP"> <field name="ATTR">state.val</field> </block> </value> <value name="B"> <block type="math_number" id="t`FF(tL#(*E:Qls*mtJm"> <field name="NUM">1</field> </block> </value> </block> </value> <statement name="DO1"> <block type="controls_if" id="?idx:TZsyOU{SSm?|oeX"> <mutation elseif="1"></mutation> <value name="IF0"> <block type="logic_compare" id="od|M4pE?j;wesMMNusu!"> <field name="OP">EQ</field> <value name="A"> <block type="get_value" id="bOAgB.CGK?NV(5~buP!u"> <field name="ATTR">val</field> <field name="OID">mqtt.5.devices.spb-03l_1.controls.Status_9</field> </block> </value> <value name="B"> <block type="math_number" id="UIm@$Xb_v`7`$My]z%=I"> <field name="NUM">1</field> </block> </value> </block> </value> <statement name="DO0"> <block type="control" id="^MeTB^;r=a8:?Sn897`d"> <mutation xmlns="http://www.w3.org/1999/xhtml" delay_input="false"></mutation> <field name="OID">0_userdata.0.office.Шлагбаум.CurrentDoorState</field> <field name="WITH_DELAY">FALSE</field> <value name="VALUE"> <block type="math_number" id="Kum02$|-|#AH}bvpS5[l"> <field name="NUM">1</field> </block> </value> </block> </statement> <value name="IF1"> <block type="logic_compare" id="ZJ)Jqq3lUS[%]A(Z$"> <field name="OP">EQ</field> <value name="A"> <block type="get_value" id=",bwKW9vyK.}}gA1vZSju"> <field name="ATTR">val</field> <field name="OID">mqtt.5.devices.spb-03l_1.controls.Status_10</field> </block> </value> <value name="B"> <block type="math_number" id="Pb3Q@=.,(8O6CIJjq1Cn"> <field name="NUM">1</field> </block> </value> </block> </value> <statement name="DO1"> <block type="control" id="{05^}R7Kt-]~yb-g,hyi"> <mutation xmlns="http://www.w3.org/1999/xhtml" delay_input="false"></mutation> <field name="OID">0_userdata.0.office.Шлагбаум.CurrentDoorState</field> <field name="WITH_DELAY">FALSE</field> <value name="VALUE"> <block type="math_number" id="1z!/,%G]Ng{A1V4|dw(a"> <field name="NUM">0</field> </block> </value> </block> </statement> </block> </statement> </block> </statement> </block> </xml>
На этом собственно все! Шлагбаум отлично управляется с Homekit, можно голосом сказать «Сири, открой шлагбаум» и не искать иконку приложения. Так же видно в каком сейчас положении шлагбаум и если необходимо — закрыть его.
ссылка на оригинал статьи https://habr.com/ru/articles/905026/
Добавить комментарий