Как стать автором
Обновить

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

Уровень сложностиСредний
Время на прочтение12 мин
Количество просмотров318

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

Теги:
Хабы:
0
Комментарии0

Публикации

Ближайшие события