FreePBX. Настройка Asterisk для e-mail уведомлений о пропущенных входящих вызовах в очереди +6


image
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).

image
Рис.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

Не будем затирать основной файл конфигурации postfix «/etc/postfix/main.cf», а создадим его резервную копию:

cp /etc/postfix/main.cf /etc/postfix/main.cf.sav

Редактируем файл «/etc/postfix/main.cf» и приводим его к следующему виду:

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
#####################

Не каждую строку в «/etc/postfix/main.cf» можно комментировать. Комментарии в некоторых строках не определяются парсером и передаются в обработку, а это приводит к ошибкам. Лучше отказаться от комментариев внутри этого файла. Можете поэкспериментировать с этим запустив в соседнем окне «tail -f /var/log/messages».

Отмечу строку «smtputf8_autodetect_classes = all». Эта запись включает utf-8 по умолчанию, что позволяет использовать кириллицу и в теле письма, и в теме письма без дополнительных манипуляций (См. http://www.postfix.org/SMTPUTF8_README.html).

Создадим каталог для файлов конфигураций:

mkdir /etc/postfix/private

Редактируем файл «/etc/postfix/private/sender_relay». В нем нужно указать на какой smtp-сервер нужно ссылаться при использовании нашего почтового домена:

nano /etc/postfix/private/sender_relay
#####################
@lucky.ru smtp.yandex.ru
#####################

Редактируем файл «/etc/postfix/private/sasl_passwd». В нем мы укажем e-mail адрес, который мы будем использовать для отправки писем, а так же логин и пароль от этой учетной записи (логин и пароль указываем через двоеточие):

nano /etc/postfix/private/sasl_passwd
#####################
asterisk@lucky.ru asterisk@lucky.ru:password_asterisk
#####################

Редактируем файл «/etc/postfix/generic». В нем мы пропишем правила подмены исходящего адреса (см. https://wiki.merionet.ru/ip-telephoniya/30/postfix-nastrojka-otpravki-pochty-v-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
#####################

Изначальный исходящий адрес зависит от содержимого «/etc/hosts» и «/etc/hostname», а также от имени пользователя, который будет отправлять письмо. Т. е. не смотря на то, что мы используем smtp-клиент и отправляем письма от asterisk@lucky.ru, все равно в адрес отправителя postfix изначально подставит «что-то своё» и это нужно исправить правилами из этого файла конфигурации.

Приведу содержимое своего файла «/etc/hosts»:

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
#####################

Важно, чтобы сервер имел какой-либо домен (значение после точки), потому что утилита mail «ищет» имя домена в «/etc/hosts» и если «не находит» его сразу, то продолжит это делать в течение еще нескольких минут и только потом отправит письмо. Т. е. если домен не прописан, то письмо будет уходить с задержкой в несколько минут.

Приведу содержимое своего файла «/etc/hostname»:

cat /etc/hostname
#####################
asterisk
#####################

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

postmap /etc/postfix/generic && postmap /etc/postfix/private/{sasl_passwd,sender_relay}

Далее нам необходимо скачать и разместить на сервере сертификат smtp.yandex.ru, для этого выполним следующую команду:

openssl s_client -starttls smtp -crlf -connect smtp.yandex.ru:25 > /etc/postfix/ca.pem

Но после того, как на экран выйдет техническая информация команда будет «продолжать висеть». Нажмите Ctrl+C чтобы прервать её.

Теперь вручную удалим из получившегося файла весь мусор и оставим только сертификат. Должно получиться нечто подобное:

nano /etc/postfix/ca.pem
#####################
-----BEGIN CERTIFICATE-----
MIIGazCCBVOgAwIBAgIQcUU9mJXW4OUs5Gf0JfLtsjANBgkqhkiG9w0BAQsFADBf
...
nRG0DfdqYIuPGApFORYe
-----END CERTIFICATE-----
#####################

И наконец перезапустим postfix:

service postfix restart

Отправляем тестовое письмо:

echo "Это тело письма" | mail -s "Это тема" admin@lucky.ru

admin@lucky.ru — адрес назначения

На этом настройка posfix закончена.

Пишем bash-скрипт

Создаем директорию для хранения bash-скрипта (тут кому где больше нравится):

mkdir /home/asterisk/scripts

Создаем файл bash-скрипта:

touch /home/asterisk/scripts/noanswer.sh

Выдаем файлу скрипта права на выполнение:

chmod +x /home/asterisk/scripts/noanswer.sh

Если есть сомнения в правах на файл, то на время отладки можно дать полный доступ к файлу. Но это «не безопасно».

chmod 777 /home/asterisk/scripts/noanswer.sh

Текст bash-скрипта:

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
#####################

Краткий разбор скрипта:
«sleep 7»:

Это та самая задержка по времени, о которой я писал ранее. У нас установлена задержка на 7 сек. Хотя, думаю, и одной секунды вполне хватит.

«res_sql="SELECT disposition FROM cdr WHERE uniqueid = '$1'"»:

Запрос в mysql мы вынесли в отдельную переменную для удобства.

Далее мы делаем запрос в mysql и фильтруем полученный вывод. Удаляем все варианты кроме «ANSWERED», если такой вообще есть. Если же значений «ANSWERED» несколько, то нужно оставить только одно. В конце в переменную «answer» мы получим либо «ANSWERED» либо «».
Если значение переменной «answer» не равно «ANSWERED», то это пропущенный вызов. В зависимости от номера очереди, с помощью оператора case мы зададим адрес, кому именно необходимо отправить e-mail уведомление, и что в этом сообщении написать (изменяемая часть сообщения).

Далее рассмотрен вариант, когда очередь задана в Asterisk, но не описана в скрипте. В этом случае admin@lucky.ru получит письмо, о том, что очередь не известна скрипту.

Если же очередь описана, то будет отправлено письмо по назначению и дублирующее письмо на admin@lucky.ru с указанием «uniqueid», для того, чтобы можно было отследить события по этому звонку, в случае необходимости.

На этом скрипт заканчивается.

Отмечу, что для подключения к mysql мы использовали логин и пароль, которые заранее узнали. Во FreePBX для того, чтобы узнать логин пользователя Asterisk в mysql выполните следующую команду:

cat /etc/amportal.conf | grep AMPDBUSER

А для того, чтобы узнать пароль пользователя Asterisk в mysql выполните следующую команду:

cat /etc/amportal.conf | grep AMPDBPASS

Настройка Asterisk

Мы используем FreePBX. Во FreePBX есть разные типы конфигурационных файлов (см. https://asterisk-pbx.ru/wiki/freepbx/files), некоторые из них FreePBX перезаписывает при перезагрузке, а некоторые не перезаписывает (их называют custom), так как они специально предназначены для пользователя.

Мы будем работать с файлом конфигурации «extensions_override_freepbx.conf», так как он относится к типу custom.

Для начала убедимся, что в файле «/etc/asterisk/extensions.conf» подключен файл «extensions_override_freepbx.conf». Для этого выполним следующую команду:

cat /etc/asterisk/extensions.conf | grep extensions_override_freepbx.conf
#####################
#include extensions_override_freepbx.conf
#####################

Редактируем файл «/etc/asterisk/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,)
#####################

Как я и писал ранее символ «&» в конце обязателен. Так как мы будем работать в bash-скрипте с данными CDR непосредственно из базы mysql, а эти данные заносятся в mysql только после выполнения «exten => h,2,Macro(hangupcall,)», то необходимо не ждать окончания отработки bash-скрипта, а перейти к выполнению следующего шага в Asterisk. А сам bash-скрипт должен содержать задержку по времени, перед выполнением основной своей части.

Для того, чтобы изменения в конфигурационном файле «/etc/asterisk/extensions_override_freepbx.conf» вступили в силу необходимо перезагрузить ядро Asterisk следующей командой:

/usr/sbin/asterisk -rx "core restart now"

Это нужно сделать после того как bash-скрипт будет создан.

Заключение

Наверное, это 1001-й способ «отлова пропущенных вызовов» в Asterisk. Поделитесь в комментариях как эту задачу решаете вы. И что, по вашему мнению, можно доработать/переделать/оптимизировать. Будем признательны за конструктивные идеи.




К сожалению, не доступен сервер mySQL