Моя борьба с автоматикой шлагбаума SPbarrier

от автора

Стояла задача научиться управлять шлагбаумом через 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/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *