Всем привет!
Решил поделиться собственным опытом в некоторых особенностях работы 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)


  1. komiller
    16.07.2015 03:32
    +1

    Оч интересно, по больше таких реализаций. Спасибо за статью не могу голосовать пока нет кармы ))


    1. AlanDrakes Автор
      16.07.2015 05:16

      У меня аналогичная ситуация.
      На то, чтобы собрать такой материал ушло без малого 2 дня. Не скажу, что постоянно копался на форумах — тогда бы собрал всё быстрее. Приходилось экспериментировать с рабочим сервером, в том числе.


  1. 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()
    

    Таким образом сразу после соединения, оба услышат одно и тоже сообщение. Это на половину псевдокод, так что нуждаеться в правках для вашей ситуации.


    1. AlanDrakes Автор
      16.07.2015 16:51

      Однако, действительно не знал такого трюка. Да и выглядит он чуть более громоздко, но позже постараюсь проверить его работу.
      У меня выход из Dial не происходит, а переход в макрос в собственно, команде Dial() приводил к отбою вызывающего абонента. Плюс, информация по собственно, переброске вызова в макрос несколько противоречива — каналы фактически хоть и соединены, но канал вызывающего попадает в контекст с приоритетом N, а канал вызываемого — N+1. Но у меня воспроизвести не удалось, или мешали другие звонки.

      Позже постараюсь проверить и дополнить статью. Спасибо =)


      1. Emily_Rose
        16.07.2015 17:43

        Опция: «M(callanswered)», запускает макро отдельно, так сказать в отдельном треде. и не влияет например на продолжение исполнения «дайлплана» в контексте from-inside, но в нем буду доступны «inherited variables», с канала который это тригернул.


  1. ComodoHacker
    16.07.2015 14:23
    +1

    Вот бы еще такой скрипт, который уведомляет абонента, звонящего в call-центр, что оператор соизволил оторваться от вконтакта и ответить на звонок. :-\ Обычно это происходит минут через 20-30.

    И чтобы он работал без наличия Asterisk, просто на смартфоне.


    1. roginvs
      17.07.2015 13:45

      Это называется en.wikipedia.org/wiki/Virtual_queue, теоретически удобная штука, хотя почему-то ни разу не встречал на практике


      1. IgorG
        21.07.2015 12:13

        Мы реализовывали такое для Vicidial и для астериска реализовать можно при отключении пользователя оставив канал в queue и при соединении с оператором подключить к вызову звонившего.