Стояла задача научиться управлять шлагбаумом через 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, можно голосом сказать "Сири, открой шлагбаум" и не искать иконку приложения. Так же видно в каком сейчас положении шлагбаум и если необходимо - закрыть его.
Автор: Anzic23
