[Из песочницы] Использование 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=
[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/любым другим способом. Причем поскольку подавляющее большинство объектового оборудования поддерживает передачу событий по нескольким номерам одновременно, это можно даже совместить с охраной в полиции/ЧОПе, и использовать такую систему для мониторинга объекта и контроля работы охраны. Если кому-нибудь это будет интересно, охотно поделюсь опытом.