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