Использование Asterisk для приема данных от охранных систем

в 11:32, , рубрики: asterisk, информационная безопасность, охрана, программирование микроконтроллеров, телефония

Несколько лет назад мы перевели охранное предприятие, в котором я тогда работал, с обычной «проводной» телефонии на IP на базе Asterisk. Это была отдельная история, со своими пробами, ошибками, эпическими фэйлами и непрерывным познанием нового. С тех пор в части голосовой связи уже все отлажено, работает без сбоев и в достаточной степени устраивает всех заинтересованных лиц.

До последнего времени на проводных линиях работало только пультовое оборудование, в автоматическом режиме принимающее события с охраняемых объектов и передающее их для обработки диспетчерам. И вот, наконец, настал тот час, когда были побеждены собственная лень и административный голем, и функции этих железок тоже были переданы телефонному серверу.

Исходные данные и для чего это было затеяно

  • в офис приходит SIP-транк с ограничением емкости в 15 каналов, оператором связи нам выделено 10 номеров;
  • в шкафу оператора стоит «железный» VoIP-шлюз, от FXS-портов которого проложены линии до нашего оборудования;
  • собственно «оборудование» — это две железки от разных производителей, умеющие принимать от объектовых систем охраны сообщения в формате Contact ID и передавать их в программу-рабочее место диспетчера.

Contact ID

Contact ID — протокол, разработанный в 1999 г. группой компаний Ademco для передачи информации от охранных систем по телефонным сетям общего пользования, и являющийся стандартом де-факто для разработчиков таких систем по всему миру. Данные передаются в виде DTMF-последовательностей с проверкой контрольной суммы для каждой посылки и подтверждением от принимающей стороны. Полную спецификацию можно официально купить на сайте разработчиков, но гугл выдает её бесплатно в первых же ссылках.

Минусы имевшегося решения:

  • лишняя цепочка преобразований VoIP-шлюз → аналоговая линия → детектор приемника, которая совсем не добавляет качества входящему сигналу, который зачастую и так сильно страдает от плохих телефонных линий на объектах;
  • невозможность приема данных с нескольких объектов одновременно, так как при передаче информации FXS-порт, естественно, занят и второй вызов по нему не пройдет (а передача информации с отдельно взятого объекта в отдельных случаях может занимать минуты);
  • невозможность определения входящих номеров — шлюз-то теоретически их выдавать может, а вот оборудование определять не умеет;
  • отсутствие адекватных логов и записи звонков и вследствие этого определенные трудности с диагностикой и настройкой «проблемных» объектов, с которыми периодически теряется связь;
  • ощутимая в масштабах этой организации стоимость пультового оборудования, осложняемая необходимостью держать резерв на случай выхода приемника из строя.

Как и что настраивалось

В дистрибутиве Asterisk еще с 2004 года присутствует модуль app_alarmreceiver, который призван эмулировать пультовый приемник. Вызывается он как обычная команда dialplan'а, отвечает на входящий звонок, обрабатывает события и складывает их в текстовый файл/файлы по указанному в настройках пути, после чего может вызвать для обработки этих файлов произвольную системную команду. С чем пришлось столкнуться при настройке:

Для начала — при приеме данных в лог начали пачками валиться сообщения от channel.c вида:

[Mar 23 22:58:35] DTMF[636][C-00000009] channel.c: DTMF begin '0' received on SIP/inbound-0000000e
[Mar 23 22:58:35] DTMF[636][C-00000009] channel.c: DTMF begin ignored '0' on SIP/inbound-0000000e
[Mar 23 22:58:35] DTMF[636][C-00000009] channel.c: DTMF end '0' received on SIP/inbound-0000000e, duration 51 ms
[Mar 23 22:58:36] DTMF[636][C-00000009] channel.c: DTMF end emulation of '4' queued on SIP/inbound-0000000e
[Mar 23 22:58:36] DTMF[636][C-00000009] channel.c: DTMF end '0' received on SIP/inbound-0000000e, duration 51 ms
[Mar 23 22:58:36] DTMF[636][C-00000009] channel.c: DTMF begin emulation of '0' with duration 80 queued on SIP/inbound-0000000e
[Mar 23 22:58:36] DTMF[636][C-00000009] channel.c: DTMF begin '1' received on SIP/inbound-0000000e
[Mar 23 22:58:36] DTMF[636][C-00000009] channel.c: DTMF begin ignored '1' on SIP/inbound-0000000e
[Mar 23 22:58:36] DTMF[636][C-00000009] channel.c: DTMF end '1' received on SIP/inbound-0000000e, duration 51 ms
[Mar 23 22:58:36] DTMF[636][C-00000009] channel.c: DTMF end emulation of '0' queued on SIP/inbound-0000000e
[Mar 23 22:58:36] DTMF[636][C-00000009] channel.c: DTMF end '1' received on SIP/inbound-0000000e, duration 51 ms

Выяснилось, что хотя стандарт DTMF поддерживает «цифры» длительностью от 40 мс, по умолчанию в Asterisk задано 80 мс, и все посылки меньшей длительности эмулируются до этого значения. В Contact ID длительность цифры определена как 50-60 мс. Благо по просьбам общественности с 2012 года соответствующий #DEFINE в channel.c продублировали параметром mindtmfduration в asterisk.conf, и после установки его равным 50, этот вопрос решился.

Второй проблемой оказалось то, каким образом полученные данные сохраняются и передаются дальше. По умолчанию весь сеанс передачи с одного объекта пишется в файл вида:

[metadata]
PROTOCOL=ADEMCO_CONTACT_ID
CALLINGFROM=ххххххххххх
CALLERNAME=<unknown>
TIMESTAMP=Mon Mar 23, 2015 @ 22:59:17 PDT

[events]
6238181401000042
623818340100004C

А затем для его обработки вызывается команда, указанная в параметре eventcmd файла alarmreceiver.conf. Меня это не устраивало по двум причинам:

Во-первых, при этом события поступят диспетчеру на обработку только после завершения сеанса связи. В случае, если на объекте действительно происходит что-то противоправное и датчики сигнализации срабатывают подряд один за другим, каждая такая сработка и последующее восстановление будут генерировать новые события, и завершение сеанса связи (и как следствие, отправка бойцов по тревоге) произойдет только после того, как на объекте больше не останется никого постороннего (и потенциально — ничего ценного).

Во-вторых, сами по себе события Contact ID не предусматривают какой-либо временной метки, и события появляются у диспетчера и пишутся в БД пультовой программы по мере поступления. При приеме событий «одной большой пачкой» в БД у них всех окажется одинаковый timestamp, что в дальнейшем может вызвать непонимание при общении с владельцами объекта и сложности с восстановлением хронологии реальных событий.

Казалось бы именно для предотвращения таких ситуаций сделан параметр logindividualevents, при котором alarmreceiver создает отдельный файл для каждого события. Но и тут не обошлось без ложки дегтя — файлы-то он отдельные создает, но eventcmd вызывает все равно только один раз при завершении сеанса. В результате от штатного механизма обработки отказались и добавили в incron правило IN_CLOSE_WRITE для папки с файлами событий — теперь они стали поступать на обработку немедленно после приема.

Третье — в метаданных файлов событий указано, с какого номера поступил входящий звонок, но не указано, на какой из наших номеров он пришел. А у нас из-за некоторых организационных особенностей, работает несколько независимых диспетчерских программ со своими БД и своими охраняемыми объектами для каждой. Причем данные с разных объектов приходят на разные входящие номера. Пришлось поправить app_alarmreceiver.c и добавить туда получение DNID из ast_channel и выдачу его вместе с остальными метаданными.

Обработка и передача дальше

Тут особых проблем не возникло, за исключением того, что диспетчерская программа весьма проприетарная и со сторонним оборудованием работать не умеет по принципиальным соображениям. Зато она умеет принимать данные от своего оборудования по UDP, и обработка свелась к простому bash-скрипту, который парсит файлы событий, созданные Asterisk'ом, формирует пакеты «от своего оборудования» и передает их на соответствующий диспетчерский ПК, в зависимости от DNID:

#!/bin/bash

dialednum=""
exec < $1
while read s
do
 if [ "${s:0:10}" = "DIALEDNUM=" ]
 then
  dialednum=${s:10}
 fi
 if [ "$s" == "[events]" ]
 then
  break
 fi
done

while read s
do
 if [ "$s" != "" ]
 then
  r=<здесь формируется hex-строка, имитирующая данные от «родного» для пультовой программы железа>
  if [ "$dialednum" == "xxxxxx" ]
  then
   echo $r | xxd -r -p > /dev/udp/192.168.1.xxx/3322
  fi
  if [ "$dialednum" == "yyyyyy" ]
  then
   echo $r | xxd -r -p > /dev/udp/192.168.1.yyy/3322
  fi
  break
 fi
done
rm -f $1

Профиты

Были устранены все «минусы», перечисленные в начале статьи. Дополнительно после вдумчивого курения логов, решилась проблема с одним объектом, который до этого периодически «затыкался». Оказалось, что установленное там древнее оборудование передает контрольную сумму «не совсем» в соответствии со стандартом, и наш честный и правильный аппаратный приемник отказывался это переваривать. В новом варианте все заработало после небольшого костыля в процедуре проверки контрольной суммы в app_alarmreceiver.c.

P.S. Применив и немного дополнив содержимое этой статьи, можно сделать из имеющейся охранной сигнализации и Asterisk свой собственный приемник с расшифровкой кодов событий в текст и последующей отправкой их себе, любимому посредством e-mail/SMS/любым другим способом. Причем поскольку подавляющее большинство объектового оборудования поддерживает передачу событий по нескольким номерам одновременно, это можно даже совместить с охраной в полиции/ЧОПе, и использовать такую систему для мониторинга объекта и контроля работы охраны. Если кому-нибудь это будет интересно, охотно поделюсь опытом.

Автор: harlong

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js