Несколько лет назад мы перевели охранное предприятие, в котором я тогда работал, с обычной «проводной» телефонии на IP на базе Asterisk. Это была отдельная история, со своими пробами, ошибками, эпическими фэйлами и непрерывным познанием нового. С тех пор в части голосовой связи уже все отлажено, работает без сбоев и в достаточной степени устраивает всех заинтересованных лиц.
До последнего времени на проводных линиях работало только пультовое оборудование, в автоматическом режиме принимающее события с охраняемых объектов и передающее их для обработки диспетчерам. И вот, наконец, настал тот час, когда были побеждены собственная лень и административный голем, и функции этих железок тоже были переданы телефонному серверу.
Минусы имевшегося решения:
В дистрибутиве Asterisk еще с 2004 года присутствует модуль app_alarmreceiver, который призван эмулировать пультовый приемник. Вызывается он как обычная команда dialplan'а, отвечает на входящий звонок, обрабатывает события и складывает их в текстовый файл/файлы по указанному в настройках пути, после чего может вызвать для обработки этих файлов произвольную системную команду. С чем пришлось столкнуться при настройке:
Для начала — при приеме данных в лог начали пачками валиться сообщения от channel.c вида:
Выяснилось, что хотя стандарт DTMF поддерживает «цифры» длительностью от 40 мс, по умолчанию в Asterisk задано 80 мс, и все посылки меньшей длительности эмулируются до этого значения. В Contact ID длительность цифры определена как 50-60 мс. Благо по просьбам общественности с 2012 года соответствующий #DEFINE в channel.c продублировали параметром mindtmfduration в asterisk.conf, и после установки его равным 50, этот вопрос решился.
Второй проблемой оказалось то, каким образом полученные данные сохраняются и передаются дальше. По умолчанию весь сеанс передачи с одного объекта пишется в файл вида:
А затем для его обработки вызывается команда, указанная в параметре eventcmd файла alarmreceiver.conf. Меня это не устраивало по двум причинам:
Во-первых, при этом события поступят диспетчеру на обработку только после завершения сеанса связи. В случае, если на объекте действительно происходит что-то противоправное и датчики сигнализации срабатывают подряд один за другим, каждая такая сработка и последующее восстановление будут генерировать новые события, и завершение сеанса связи (и как следствие, отправка бойцов по тревоге) произойдет только после того, как на объекте больше не останется никого постороннего (и потенциально — ничего ценного).
Во-вторых, сами по себе события Contact ID не предусматривают какой-либо временной метки, и события появляются у диспетчера и пишутся в БД пультовой программы по мере поступления. При приеме событий «одной большой пачкой» в БД у них всех окажется одинаковый timestamp, что в дальнейшем может вызвать непонимание при общении с владельцами объекта и сложности с восстановлением хронологии реальных событий.
Казалось бы именно для предотвращения таких ситуаций сделан параметр logindividualevents, при котором alarmreceiver создает отдельный файл для каждого события. Но и тут не обошлось без ложки дегтя — файлы-то он отдельные создает, но eventcmd вызывает все равно только один раз при завершении сеанса. В результате от штатного механизма обработки отказались и добавили в incron правило IN_CLOSE_WRITE для папки с файлами событий — теперь они стали поступать на обработку немедленно после приема.
Третье — в метаданных файлов событий указано, с какого номера поступил входящий звонок, но не указано, на какой из наших номеров он пришел. А у нас из-за некоторых организационных особенностей, работает несколько независимых диспетчерских программ со своими БД и своими охраняемыми объектами для каждой. Причем данные с разных объектов приходят на разные входящие номера. Пришлось поправить app_alarmreceiver.c и добавить туда получение DNID из ast_channel и выдачу его вместе с остальными метаданными.
Тут особых проблем не возникло, за исключением того, что диспетчерская программа весьма проприетарная и со сторонним оборудованием работать не умеет по принципиальным соображениям. Зато она умеет принимать данные от своего оборудования по UDP, и обработка свелась к простому bash-скрипту, который парсит файлы событий, созданные Asterisk'ом, формирует пакеты «от своего оборудования» и передает их на соответствующий диспетчерский ПК, в зависимости от DNID:
Были устранены все «минусы», перечисленные в начале статьи. Дополнительно после вдумчивого курения логов, решилась проблема с одним объектом, который до этого периодически «затыкался». Оказалось, что установленное там древнее оборудование передает контрольную сумму «не совсем» в соответствии со стандартом, и наш честный и правильный аппаратный приемник отказывался это переваривать. В новом варианте все заработало после небольшого костыля в процедуре проверки контрольной суммы в app_alarmreceiver.c.
P.S. Применив и немного дополнив содержимое этой статьи, можно сделать из имеющейся охранной сигнализации и Asterisk свой собственный приемник с расшифровкой кодов событий в текст и последующей отправкой их себе, любимому посредством e-mail/SMS/любым другим способом. Причем поскольку подавляющее большинство объектового оборудования поддерживает передачу событий по нескольким номерам одновременно, это можно даже совместить с охраной в полиции/ЧОПе, и использовать такую систему для мониторинга объекта и контроля работы охраны. Если кому-нибудь это будет интересно, охотно поделюсь опытом.
До последнего времени на проводных линиях работало только пультовое оборудование, в автоматическом режиме принимающее события с охраняемых объектов и передающее их для обработки диспетчерам. И вот, наконец, настал тот час, когда были побеждены собственная лень и административный голем, и функции этих железок тоже были переданы телефонному серверу.
Исходные данные и для чего это было затеяно
- в офис приходит 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/любым другим способом. Причем поскольку подавляющее большинство объектового оборудования поддерживает передачу событий по нескольким номерам одновременно, это можно даже совместить с охраной в полиции/ЧОПе, и использовать такую систему для мониторинга объекта и контроля работы охраны. Если кому-нибудь это будет интересно, охотно поделюсь опытом.