IP ATC Asterisk — это мощный комбайн в области IP-телефонии. А web-интерфейс FreePBX, созданный для Asterisk, значительно упрощает настройку и снижает порог вхождения в систему.
Если вы можете придумать какую-либо задачу, связанную с IP-телефонией, то почти наверняка это можно реализовать в Asterisk. Но будьте уверены, что от вас потребуется упорство и выдержка.
Перед нами встала задача настроить e-mail уведомления о пропущенных вызовах. Точнее говоря, оповещать через e-mail о тех случаях, когда входящий вызов перешёл в очередь, но никто (из агентов) так и не ответил на этот входящий вызов.
На удивление мы не обнаружили штатных средств для решения этой задачи во FreePBX. О том, как мы решили эту задачу, расскажу под катом.
Предисловие
Перед решением задачи «в лоб» мы конечно поискали информацию в интернете, но решения под ключ не нашли (возможно плохо искали, но что поделаешь… ).
Навыков работы непосредственно в Asterisk не так много, как хотелось бы, поэтому решение, предлагаемое здесь, не было до конца осмыслено и было отброшено.
Понравилось решение, предложенное здесь, хоть оно и не заработало. Отсюда подчеркнули, что работать в Asterisk нужно в контексте очередей [ext-queues]. И так как мы работаем во Freepbx, то работать нужно в файле конфигурации «extensions_override_freepbx.conf». Обратили внимание на то, что «ловить пропущенные вызовы» удобно перед событием hangupcall (окончание вызова).
Прочитав обсуждение здесь, появилась идея о том, что нужно фильтровать в CDR переменную «Disposition» по всем агентам в очереди. А после прочтения этой информации сформировались вполне конкретные шаги по решению поставленной задачи.
Что у нас есть:
Есть FreePBX 13.0.197, который использует Asterisk 13.12.1. Версия ОС SHMZ release 6.6 (Final). Дистрибутив базируется на CentOS.
В Asterisk настроен IVR (голосовое меню) раскидывающий входящие вызовы на разные Queues (очереди). Каждой очереди назначены Agents (агенты), т. е. операторы.
Теория
Что происходит в Asterisk
Когда на Asterisk поступает входящий вызов, этот вызов попадает на IVR. Звонящий делает выбор, нажав определенную цифру на телефоне, и попадает в определенную очередь. После этого всем свободным агентам очереди одновременно поступает звонок.
Для того чтобы лучше понять, что происходит в этот момент и что происходит дальше, обратимся к Report CDR (Рис.1).
Рис.1
Когда входящий вызов попал в очередь, у всех агентов значение переменной «Disposition» стало равным «NO ANSWER», если агенты в этот момент были не заняты. Переменная «Disposition» могла принять и другие значения (см. https://asterisk-pbx.ru/wiki/asterisk/cf/cdr), кроме значения «ANSWERED». А в тот момент, когда один из агентов отвечает на входящий вызов, значение переменной «Disposition» этого агента становится равной «ANSWERED».
Из Report CDR можно заметить, что когда вызов перешел в очередь (в колонке App значение становится равным «Queue»), то все события фигурируют с одинаковым «uniqueid» (колонка System).
Коротко о CDR
Важно понимать что такое CDR, и в какой именно момент в CDR заносятся данные, которые мы наблюдаем в Report CDR. CDR, относительно операционной системы — это база данных, в которую Asterisk записывает детализированный отчет вызовов (см. https://asterisk-pbx.ru/wiki/asterisk/cf/cdr). В нашем случае это база под именем asteriskcdrdb, которая находится в mysql. Опытным путем мы установили, что данные о вызове с определенным «uniqueid» заносятся в asteriskcdrdb не сразу после возникновения какого-либо события, а после события hangupcall (окончание вызова).
Принцип работы созданного решения
Так как у нас познаний в bash больше, чем познаний в Asterisk, то основная идея получилась следующей. Перед событием hangupcall вызвать bash-скрипт. В этот скрипт передать 3 параметра. Первый параметр «uniqueid», для фильтрации данных, получаемых из CDR. Второй параметр «CALLERID(num)» (номер звонившего), чтобы знать кому перезвонить. Третий параметр «NODEST» (номер очереди), в которую поступил звонок, для того, чтобы знать по какому вопросу был звонок, и кому отправить e-mail уведомление о пропущенном вызове.
Bash-скрипт должен подключиться к базе asteriskcdrdb в mysql и взять все значения переменной «Disposition» с определенным «uniqueid». Из полученных данных нужно исключить значения: «NO ANSWER», «BUSY», «FAILED», «UNKNOWN». В результате останутся либо «ANSWERED» — на входящий вызов ответили, либо вообще ни чего — пропущенный вызов.
Далее, если вызов оказался пропущенным, то скрипт должен отправить e-mail уведомление.
Забегая вперед отмечу важный момент. Asterisk выполняет команды последовательно, дожидаясь их выполнения (что в общем-то логично). А вызывать bash-скрипт мы будем до того, как выполнится команда hangupcall. Таким образом в момент непосредственного выполнения скрипта, в CDR еще не будет внесена информация об искомом нами «uniqueid». Для решения этой проблемы bash-скрипт мы будем вызывать с параметром «&», чтобы Asterisk сразу перешел к выполнению следующего шага, т. е. hangupcall. А внутри bash-скрипта, в самом начале, мы установим небольшую задержку по времени, чтобы дать время для Asterisk внести данные с интересующим нас «uniqueid» в CDR.
Практика
Перед тем как перейти к настройке Asterisk и созданию bash-скрипта, нужно настроить отправку e-mail уведомлений. Для этого мы будем использовать утилиту postfix.
Настройка postfix
У нас есть почтовый домен «lucky.ru», расположенный в Яндексе. Мы настроим postfix в режим smtp-клиента и будем отправлять письма с аккаунта asterisk@lucky.ru.
За основу взято решение отсюда: https://www.dmosk.ru/miniinstruktions.php?mini=postfix-over-yandex.
Сначала установим/обновим/проверим наличие пакетов:
yum install postfix
yum install mailx
yum install cyrus-sasl cyrus-sasl-lib cyrus-sasl-plain
cp /etc/postfix/main.cf /etc/postfix/main.cf.sav
nano /etc/postfix/main.cf
#####################
relayhost =
smtp_sasl_auth_enable = yes
smtp_sasl_password_maps = hash:/etc/postfix/private/sasl_passwd
smtp_sasl_security_options = noanonymous
smtp_sasl_type = cyrus
smtp_sasl_mechanism_filter = login
smtp_sender_dependent_authentication = yes
sender_dependent_relayhost_maps = hash:/etc/postfix/private/sender_relay
smtp_generic_maps = hash:/etc/postfix/generic
smtp_tls_CAfile = /etc/postfix/ca.pem
smtp_use_tls = yes
smtputf8_autodetect_classes = all
#####################
mkdir /etc/postfix/private
nano /etc/postfix/private/sender_relay
#####################
@lucky.ru smtp.yandex.ru
#####################
nano /etc/postfix/private/sasl_passwd
#####################
asterisk@lucky.ru asterisk@lucky.ru:password_asterisk
#####################
nano /etc/postfix/generic
#####################
root asterisk@lucky.ru
root@localhost asterisk@lucky.ru
root@localhost.localdomain asterisk@lucky.ru
root@freepbx asterisk@lucky.ru
root@freepbx.localdomain asterisk@lucky.ru
root@asterisk asterisk@lucky.ru
root@asterisk.localdomain asterisk@lucky.ru
asterisk asterisk@lucky.ru
asterisk@localhost asterisk@lucky.ru
asterisk@localhost.localdomain asterisk@lucky.ru
asterisk@freepbx asterisk@lucky.ru
asterisk@freepbx.localdomain asterisk@lucky.ru
asterisk@asterisk asterisk@lucky.ru
asterisk@asterisk.localdomain asterisk@lucky.ru
root@localdomain.localdomain asterisk@lucky.ru
#####################
cat /etc/hosts
#####################
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4 asterisk.localdomain
127.0.0.1 localhost.localdomain localhost
::1 asterisk localhost localhost6
#####################
cat /etc/hostname
#####################
asterisk
#####################
postmap /etc/postfix/generic && postmap /etc/postfix/private/{sasl_passwd,sender_relay}
openssl s_client -starttls smtp -crlf -connect smtp.yandex.ru:25 > /etc/postfix/ca.pem
nano /etc/postfix/ca.pem
#####################
-----BEGIN CERTIFICATE-----
MIIGazCCBVOgAwIBAgIQcUU9mJXW4OUs5Gf0JfLtsjANBgkqhkiG9w0BAQsFADBf
...
nRG0DfdqYIuPGApFORYe
-----END CERTIFICATE-----
#####################
service postfix restart
echo "Это тело письма" | mail -s "Это тема" admin@lucky.ru
mkdir /home/asterisk/scripts
touch /home/asterisk/scripts/noanswer.sh
chmod +x /home/asterisk/scripts/noanswer.sh
chmod 777 /home/asterisk/scripts/noanswer.sh
nano /home/asterisk/scripts/noanswer.sh
#####################
#!/bin/bash
sleep 7
res_sql="SELECT disposition FROM cdr WHERE uniqueid = '$1'"
answer=`mysql -u freepbxuser -pPassword_freepbxuser -D asteriskcdrdb -B -N -e "$res_sql" | grep -E -v "NO ANSWER|BUSY|FAILED|UNKNOWN" | head -n 1`
error_kod=0
if [ "$answer" != "ANSWERED" ]
then
case $3 in
68800)
address="big_boss@lucky.ru"
subject="по важному вопросу"
;;
63100)
address="debian@lucky.ru"
subject="по вопросам linux debian"
;;
63200)
address="windows@lucky.ru"
subject="по вопросам windows"
;;
63300)
address="freebsd@lucky.ru"
subject="по вопросам freebsd"
;;
63400)
address="ubuntu@lucky.ru"
subject="по вопросам linux ubuntu"
;;
63500)
address="centos@lucky.ru"
subject="по вопросам linux centos"
;;
*)
address="admin@lucky.ru"
error_kod=1
;;
esac
case $error_kod in
0)
echo "Пропущен вызов от абонента $2, звонившего $subject." | mail -s "Пропущен вызов от $2" $address
echo "Пропущен вызов для $address от абонента $2, звонившего $subject. uid=$1" | mail -s "Пропущен вызов от $2" admin@lucky.ru
;;
1)
echo "Пропущен вызов от $2. Очередь неизвестна. uid=$1" | mail -s "Пропущен вызов от $2" admin@lucky.ru
;;
esac
fi
#####################
«res_sql="SELECT disposition FROM cdr WHERE uniqueid = '$1'"»:
cat /etc/amportal.conf | grep AMPDBUSER
cat /etc/amportal.conf | grep AMPDBPASS
cat /etc/asterisk/extensions.conf | grep extensions_override_freepbx.conf
#####################
#include extensions_override_freepbx.conf
#####################
nano /etc/asterisk/extensions_override_freepbx.conf
#####################
[ext-queues]
exten => h,1,System(/home/asterisk/scripts/noanswer.sh ${CDR(uniqueid)} ${CALLERID(num)} ${NODEST} &)
exten => h,2,Macro(hangupcall,)
#####################
/usr/sbin/asterisk -rx "core restart now"
К сожалению, не доступен сервер mySQL