Требуется сделать систему экстренного голосового оповещения в случае аварий и прочих происшествий. Схема работы следующая:
Ответственное лицо звонит на определенный номер телефона, набирает код и попадает в голосовое меню системы оповещения. Там ему предлагается записать сообщение, прослушать его, сохранить и система должна совершать звонки одновременно используя 5 телефонных линий на заранее заданные в файле номера телефонов. Причем в случае, если не взяли трубку — перезванивать оповещаемому. По окончании оповещения на указанную почту должен приходить файл с номерами телефонов на которые не смогли дозвониться вообще.
Если интересно как это работает прошу под кат.
Для совершения звонков в Астериске есть call файлы. При помещении файла в директорию /var/spool/asterisk/outgoing он автоматически совершает звонок. Давайте для начала разберемся из чего состоят call файлы и что у них внутри.
Внутри каждого файла может быть несколько переменных:
Channel: <channel>
— Указывает канал для исходящего вызова
CallerID: Name <number>
— Соответственно имя, от кого будет исходить вызов
MaxRetries: <number>
— Вот как раз очень нужный параметр количества попыток дозвона, если установить 0, то будет считаться что это 1 попытка.
RetryTime: <number>
— Время между попытками неудачного вызова, задается в секундах, противоречивый на практике параметр, если поставить мало — то вероятность дозвониться падает, а если много — то сильно может увеличить общее время обзвона (при большом количестве номеров). Приходится выбирать золотую середину.
WaitTime: <number>
— Время в секундах сколько будет звонить телефон у каждого абонента из списка телефонов. Думаю в районе 60 секунд вполне удобно, больше смысла нету, а меньше в учреждениях могут не успеть дойти до телефона.
Account:
— Должно использоваться для установки поля “account code”для записи в CDR, но я не использовал
Context: <context-name>
— Контекст который будет использоваться для совершения дальнейших действий(когда прошел дозвон)
Extension: <ext>
— Название Extension от которого будет совершаться звонок.
Priority: <priority>
— Номер приоритета для Extension, с которого нужно начать выполнение.
Set:
— Установка переменных канала для использования их в логике обработки вызова на заданный екстеншен.
Application:
— Имя приложения Asterisk, которое необходимо выполнить. Если используется приложение — то не будет использованы context, extension и priority.
Data:
— Параметры для запускаемого приложения. Тоже не использовал.
Archive: Yes/No
– Переносить или нет .call файл в поддиректорию «outgoing_done» с установленным значением поля «Status: значение», где значение может быть: Completed, Expired или Failed. Тоже не использовал.
Основные поля в файле посмотрели, теперь я приведу как выглядит файл у меня:
Channel: Local/11%1%@from-internal/n
CallerID: <5102>
MaxRetries: 4
RetryTime: 60
WaitTime: 60
Context: startmessage
Extension: 5102
Priority: 1
Теперь по порядку. В переменной канала используется Local для того чтобы все эти звонки присутствовали в CDR логах(не забываем про необходимость анализировать обзвон — для формирования списка недоступных телефонов). Имя звонящего, потом установлено 5 попыток дозвона через каждые 60 секунд, и звонить номер будет тоже 60 секунд. После того как Астериск дозвонится абоненту — будет использоваться самописный контекст startmessage. Звонить будет extension 5102, приоритет 1.
Перед номером телефона который будет вставлен в файл вставляется 11, это сделано для удобства маршрутизации звонка через нужный транк. То есть в FreePBX создается Outbound route в котором из номера вырезается вначале 11 и ставим выход через нужный транк. Парсер который в конце обзвона формирует список файлов «кому не дозвонились» тоже вырезает эти две единицы.
Раз уж мы коснулись контекстов можно заглянуть в /etc/asterisk/extensions_custom.conf (стоит Elastix)
Вначале стоит строчка exten => 9876,1,Goto(testcontext,s,1)
, которая закидывает при звонке на 9876 на наш контекст, в конце статьи опишу для чего.
Далее у меня прописано 2 контекста. Первый отвечает за запись сообщения, и начало обзвона. Второй за действия, которые будет совершать система, после дозвона абоненту.
Первый контекст:
[testcontext]
exten => s,1,Answer
exten => s,n,Wait(2)
exten => s,n,Playback(/var/lib/asterisk/sounds/custom/privet-zapis)
exten => s,n,Record(obzvon-message.wav)
exten => s,n,Playback(/var/lib/asterisk/sounds/obzvon-message)
exten => s,n,Playback(/var/lib/asterisk/sounds/custom/zapusk)
exten => s,n,WaitExten(10)
exten => s,n,Hangup()
exten => 999,1,System(echo "" > /var/log/asterisk/cdr-custom/Simple.csv)
exten => 999,n,System(/mnt/script/parser.bash /mnt/CallCenter/telefony.txt /mnt/script/main.call)
exten => 999,n,System(/mnt/script/startcall.bash)
exten => 0,1, Hangup()
Описываю построчно — снимаем трубку, ждём 2 секунды, воспроизводим приветствие из файла /var/lib/asterisk/sounds/custom/privet-zapis , записываем сообщение obzvon-message.wav, сразу после записи мы воспроизводим его в трубку и запускаем звуковой файл с дальнейшими инструкциями /var/lib/asterisk/sounds/custom/zapusk. На данном моменте предлагается ввести пароль для запуска системы оповещения и ждём 10 скунд для ввода пароля. Если будет набрано 999, то запускаем скрипты запуска, если нет — вешаем трубку.
После того как набрали 999, первым делом Астериск чистит содержимое файла с кастомным логом /var/log/asterisk/cdr-custom/Simple.csv (Позже рассмотрим как он сделан), потом запускает скрипт /mnt/script/parser.bash с двумя аргументами, первый это /mnt/CallCenter/telefony.txt — путь к файлу телефонов(по одному телефону на строку в нужном формате), второй это /mnt/script/main.call — файл с шаблоном call файла. Шаблон call файла приведен выше. Далее запускается скрипт /mnt/script/startcall.bash.
Хочу отметить что блокнотом windows лучше этот файл с телефонами не редактирова, т.к. он вставляет символы из за которых нарушается работоспособность, я пользовался Notepad ++, когда делал это из под Windows.
Приведу свой файл для формирования CDR — /etc/asterisk/cdr_custom.conf:
[mappings]
Simple.csv => ${CSV_QUOTE(${CDR(clid)})},${CSV_QUOTE(${CDR(src)})},${CSV_QUOTE(${CDR(dst)})},${CSV_QUOTE(${CDR(dcontext)})},${CSV_QUOTE(${CDR(channel)})},$$
Теперь разберемся что находится в скриптах.
Вот содержимое файла /mnt/script/parser.bash:
#!/bin/bash
#первый аргумент - файл с телефонами, второй файл шаблона
FILE=`cat $1 | sort -u`
for I in $FILE
do
if [ -n "$I" ]
then
sed -e 's/%1%/'$I'/g' < $2 >> /mnt/script/tmp/$I.call
fi
done;
Из файла с телефонами вырезается первая строка и вставляется в call файл вместо %1%, создающийся в папке /mnt/script/tmp/ с именем «номер телефона».call.
Содержимое скрипта /mnt/script/startcall.bash:
#!/bin/bash
# Папка в которой лежат свеже-сформированные call файлы
OutDir="/mnt/script/tmp/"
# Папка в которую будут помещаться call файлы
inDir="/var/spool/asterisk/outgoing/"
# Количество call файлов одновременно находящихся в папке (равняется количеству используемых для звонка линий)
CountIn=5;
movefile ()
# Считаем количество файлов в inDir
{
CounFileInDir=$(find $inDir -name "*.call" | wc -l);
# Если количество файлов в inDir - меньше значения CountIn, то перемещаем файл в эту директорию.
if (("$CounFileInDir" < "$CountIn")); then
mv $I $inDir
# Иначе ждём 30 секунд и повторяем функцию заново.
else
sleep 30;
movefile;
fi
}
# В переменную помещаются имена всех файлов *.call в директории OutDir.
FILE=`find $OutDir -name "*.call"`
# Пробегаем по всем значениям в переменной FILE, если файл существует, то выполняем movefile.
for I in $FILE
do
if [ -n "$I" ]
then
movefile;
fi
done;
# По окончании работы скрипта - запускаем следующий скрипт.
/mnt/script/result.bash
Содержимое скрипта /mnt/script/result.bash:
#!/bin/bash
# Файл со списком телефонов
telefoni=/mnt/CallCenter/telefony.txt
# Лог файл из которого будем парсить.
logfile=/var/log/asterisk/cdr-custom/Simple.csv;
# Создаём временный файл.
cp $telefoni telefoni.tmp
# В переменную FILE заносим отсортированный временный файл только с уникальными значениями.
FILE=`cat telefoni.tmp | sort -u | uniq`
# Пробегаем по всем значениям переменной FILE , если номер телефона существует - то в переменной logfile ищутся все значения содержащие ANSWERED.
# startmessage, ищется значение текущего номера телефона, если все значения в строке сошлись то из строки берется номер
#телефона, в котором спереди вырезаются 11 (использующиеся для маршрутизации вызова) и всё что находится после @ сортируется по уникальным записям и передается во временный файл temp
for I in $FILE
do
if [ -n "$I" ]
then
rm temp.temp
cat $logfile | grep "ANSWERED" | grep "startmessage" | grep $I | awk 'BEGIN{FS=","}{print $5}' | sed -e 's/.*/11//g' -e 's/@.*//g' | uniq >> temp$
fi
done;
# Переменная FILE1 получает значение файла temp.temp, сортирует и оставляет только уникальные записи.
FILE1=`cat temp.temp | sort -u | uniq`
# Формируем список кому не дозвонились.
for J in $FILE1
do
if [ -n "$J" ]
then
cat telefoni.tmp | grep -v $J > telefoni1.tmp
mv telefoni1.tmp telefoni.tmp
fi
done;
rm temp.temp;
Отправляем список телефонов кому не дозвонились на почтовый ящик
cat telefoni.tmp | mail -s Ne_Dozvon testmail@example.com
Осталась самая малость — сделать входящую маршрутизацию для звонка. Заходим в Inbound routes и создаем маршрут с CID номером ответственного за запуск системы человека. Тоесть когда он позвонит на любой номер — то попадет на систему обзвона, если не он, то пойдёт по стандартному маршруту. Можно также задать DID чтобы прикрепить к какому то конкретному номеру телефона фирмы. Теперь в destination надо указать Misc Destinations который мы сейчас создадим. Заходим в Misc Destinations, создаём новый и вписываем 9876 в строку dial. (Помните мы добавляли строчку в extensions_custom.conf? )
Вот собственно мы и добились нужного результата, вариантов использования конечно масса, переделывать тоже можно как угодно, но эту статью я решил сделать в виде заметки для себя же, чтобы не забыть что и как работает. То что многое криво и возможно не совсем правильно я знаю, посему конструктивную критику только поддерживаю.
Автор: MotjaX