Решил поделиться собственным опытом в некоторых особенностях работы Dialplan'а.
Данная заметка не претендует на научные открытия, но как короткий справочный лист может кому-нибудь и пригодиться.
Исходные данные:
Сервер с Asterisk 1.8, без Web-интерфейса, настроенный как телефонный шлюз-маршрутизатор.
Конфигурация задаётся редактированием конфигурационных файлов в каталоге /etc/asterisk/
Необходимо воспроизвести сообщение вызываемому абоненту, а затем уведомить вызывающего о готовности его слушать.
Кому интересно, добро пожаловать под кат.
При входящем звонке вызывающий абонент прослушивает приветствие и сообщение IVR (это настраивается на большом Call-центре с Web-интерфейсом, похожим на стандартный FreePBX) в том числе, что «сообщение записывается». Возникла потребность данное же сообщение воспроизвести при звонке от операторов Call-центра к абонентам. В Web-интерфейсе нет возможности добавить подобную функцию, пришлось импровизировать.
В настоящий момент сервер с функциями голосового шлюза и воспроизводит сообщение вызываемым абонентам, если вызывающие номера удовлетворяют заданному условию, а так же, уведомляет звонящего, что можно говорить.
Кусочек диалплана, отвечающего за сообщение:
[call-centr-context]
etxen => _X.,n,Macro(SayAllCallsRec)
[macro-SayAllCallsRec]
exten => s,1,NoOp(${CALLERID(num})
exten => s,n,GotoIf($["${CALLERID(num)}" = "123456789"]?say)
; Other numbers here. NO FULL if-then-else!
exten => s,n(notsay),NoOp()
exten => s,n,Dial(SIP/${MACRO_EXTEN}@TrunkOut,50)
exten => s,n,GoTo(end)
exten => s,n(say),NoOp()
exten => s,n,Set(LIMIT_PLAYAUDIO_CALLER=yes)
exten => s,n,Set(LIMIT_PLAYAUDIO_CALLEE=no)
exten => s,n,Set(LIMIT_CONNECT_FILE=/var/lib/asterisk/sounds/beep)
exten => s,n,Dial(SIP/${MACRO_EXTEN}@TrunkOut,50,L(9999999)A(/var/lib/asterisk/sounds/AllCallsRec))
exten => s,n,Goto(end)
exten => s,n(end),NoOp()
exten => s,n,HangUp()
Данный участок диалплана рабочий и оттестирован. Можно смело удалить все строки, содержащие NoOp, т.к. они выполняют только функции записи в лог консоли.
Теперь пройдёмся по некоторым участкам.
В контексте, которому принадлежит транк Call-центра, общий экстеншн «Для любого номера» (_X.) отправляет вызов в макрос, где происходит:
— Проверка на «белый» список.
— Поднятие трубки,
— Воспроизведение КПВ,
— Сообщение вызываемому участнику,
— Сообщение вызывающему.
К сожалению, описанный ещё в 2007-м году баг (или фича) (пруф) с неодновременным сообщением файла «LIMIT_CONNECT_FILE» всё ещё существует в версии 1.8, а обновлять сервер нет возможности.
Таким образом, если пытаться уведомлять обоих участников вызова, используя флаги LIMIT_PLAYAUDIO_CALLER=yes, LIMIT_PLAYAUDIO_CALLEE=yes, то сообщение будет воспроизведено последовательно. Сначала — абоненту Б, затем — абоненту А, а у другого абонента в этот момент будет тишина.
Теперь разберём диалплан. Начнём с команды вызова, как самой длинной.
Dial(SIP/${MACRO_EXTEN}@TrunkOut — Вызов будет направлен с номерм, переданным в макрос на транк «TrunkOut»
,50 — Ждать ответа будем до 50 секунд
,L(9999999) — лимит времени соединения в милисекундах (не требуется, но иного способа сообщить вызываюшему, что вызываемый прослушал «приветствие» не нашлось.
A(путь-к-файлу) — сообщение вызываемому
exten => s,n,Set(LIMIT_PLAYAUDIO_CALLER=yes) — Воспроизводить вызывающему
exten => s,n,Set(LIMIT_PLAYAUDIO_CALLEE=no) — Воспроизводить вызываемому
exten => s,n,Set(LIMIT_CONNECT_FILE=/var/lib/asterisk/sounds/beep) — Путь к файлу, который нужно будет воспроизвести.
Заданные чуть выше переменные определяют, кому будет воспроизведён звук «beep». В нашем случае, тому, кто звонит ИЗ Call-центра.
И, собственно, как выглядит сам вызов по шагам:
Абонент Call-центра набирает номер. Вызов приходит на шлюз Asterisk. Звонок падает в макрос. Номер А сравнивается со списком. В случае несовпадения, вызов идёт без дополнительных ключей. В случае совпадения, звонку добавляются ключи.
Абонент Б снимает трубку, прослушивает сообщение (Ключ A()). В этот момент у абонента А будет тишина (после длиных гудков). По окочании сообщения, абонент А услышит звук из файла «beep.gsm» (должен быть в каталоге /var/lib/asterisk/sounds/). По какой причине именно в формате gsm — не ясно. И только после этого, каналы будут соединены.
Так же, можно заменить тишину для абонента А на гудки КПВ. Для этого нужно изменить строки:
exten => s,n(say),NoOp()
exten => s,n,Answer()
exten => s,n,Playtones(420)
exten => s,n,Set(LIMIT_PLAYAUDIO_CALLER=yes)
exten => s,n,Set(LIMIT_PLAYAUDIO_CALLEE=no)
exten => s,n,Set(LIMIT_CONNECT_FILE=/var/lib/asterisk/sounds/beep)
exten => s,n,Dial(SIP/${MACRO_EXTEN}@TrunkOut,50,L(9999999)A(/var/lib/asterisk/sounds/AllCallsRec))
Добавлены Answer() — собирается канал, и Playtones() — воспроизводится КПВ. В таком случае, вызывающий получит генерируемые Asterisk-шлюзом сигналы КПВ.
К сожалению, в этом случае RTP-сообщения перед соединением от вышестоящих серверов будут заменены гудками. Например, сообщение без ответа «Абонент не может быть вызван» от сотовых и им подобные, которые фактически, не приводят к созданию канала (ответ 200 — OK) будут проигнорированы, и вызывающий получит звук вида (длинные гудки, длинные гудки, короткие гудки...), но зато не будет паузы тишины.
Так же, можно добавить к Dial() ключ r — генерация системного КПВ, но то будет выбираться именно из системных звуков и будет (чаще всего) сильно отличаться от классического звука контроля посылки вызова.
На этом всё. Надеюсь, заметка поможет кому-то ещё сэкономить день-другой, пытаясь заставить шлюз выполнять необходимую последовательность действий.
Комментарии (8)
Emily_Rose
16.07.2015 13:30+1Еще один трюк покажу, может знаете, но все же:
Здеся, когда кого-то набираем, указиваем опцию: «M(callanswered)», это макро которое тригернеться српазу перед тем когда делаеться бридж:
[from-inside] exten => _X.,1,Noop(DIALING OUT) same => n,Set(__CALLEE=${MACRO_EXTEN}) same => n,Dial(SIP/${MACRO_EXTEN}@TrunkOut,50,M(callanswered))
Здесь, мы делаем ориджинейт, одна нога пойдет в bargein, который врежеться в звонок, вторая нога, проиграет файл:
[macro-callanswered] exten => s,1,Noop(${CALLEE} answered the call) same => n,Originate(Local/${CALLEE}@bargein,app,Playback,/var/lib/asterisk/sounds/AllCallsRec)
Здесь просто врезаемся в звонок:
[bargein] exten => _X.,1,Noop(PLAY MUSIC THROUGH CHANSPY) same => n,Answer() same => n,ChanSpy(SIP/${EXTEN},BEq) same => n,Hangup()
Таким образом сразу после соединения, оба услышат одно и тоже сообщение. Это на половину псевдокод, так что нуждаеться в правках для вашей ситуации.AlanDrakes Автор
16.07.2015 16:51Однако, действительно не знал такого трюка. Да и выглядит он чуть более громоздко, но позже постараюсь проверить его работу.
У меня выход из Dial не происходит, а переход в макрос в собственно, команде Dial() приводил к отбою вызывающего абонента. Плюс, информация по собственно, переброске вызова в макрос несколько противоречива — каналы фактически хоть и соединены, но канал вызывающего попадает в контекст с приоритетом N, а канал вызываемого — N+1. Но у меня воспроизвести не удалось, или мешали другие звонки.
Позже постараюсь проверить и дополнить статью. Спасибо =)Emily_Rose
16.07.2015 17:43Опция: «M(callanswered)», запускает макро отдельно, так сказать в отдельном треде. и не влияет например на продолжение исполнения «дайлплана» в контексте from-inside, но в нем буду доступны «inherited variables», с канала который это тригернул.
ComodoHacker
16.07.2015 14:23+1Вот бы еще такой скрипт, который уведомляет абонента, звонящего в call-центр, что оператор соизволил оторваться от вконтакта и ответить на звонок. :-\ Обычно это происходит минут через 20-30.
И чтобы он работал без наличия Asterisk, просто на смартфоне.roginvs
17.07.2015 13:45Это называется en.wikipedia.org/wiki/Virtual_queue, теоретически удобная штука, хотя почему-то ни разу не встречал на практике
IgorG
21.07.2015 12:13Мы реализовывали такое для Vicidial и для астериска реализовать можно при отключении пользователя оставив канал в queue и при соединении с оператором подключить к вызову звонившего.
komiller
Оч интересно, по больше таких реализаций. Спасибо за статью не могу голосовать пока нет кармы ))
AlanDrakes Автор
У меня аналогичная ситуация.
На то, чтобы собрать такой материал ушло без малого 2 дня. Не скажу, что постоянно копался на форумах — тогда бы собрал всё быстрее. Приходилось экспериментировать с рабочим сервером, в том числе.