Asterisk Dialplan - extensions.conf

Наиболее важным для понимания Asterisk является план набора (dialplan). Все вызовы, будь-то очередь, конференция, меню автосекретаря или вызов телефона, определяются логикой и концепцией диалплана.

Диалплан создается в файле /etc/asterisk/extensions.conf .

Каналам назначаются контексты. Контексты определяют правила набора для каналов
План набора состоит из одного или нескольких контекстов. Каждый контекст это просто набор расширений (екстеншенов). Каждый екстеншен в контексте имеет уникальное имя.

Контексты используются для выполнения основных функций АТС:

  • Безопасность: Можно разрешить междугородные/международные вызовы только конкретным абонентам.
  • Маршрутизация вызовов: Маршрутизация вызовов в зависимости от номера абонента.
  • Автосекретарь: Проигрывание приветствия и приглашение ввести добавочный номер.
  • Многоуровневые голосовые меню: Голосовые меню для службы поддержки, отдела продаж и т.д.
  • Авторизация: Запрос пароля для доступа к некоторым екстеншенам.
  • Обратный вызов: Позволяет уменьшить затраты на междугородние/международные вызовы.
  • Списки доступа: Занесение в черные списки надоедливых абонентов, не давая им возможности связаться с Вами.
  • Виртуальные АТС: Вы можете создать «виртуальную АТС» в пределах Вашей основной АТС.
  • Дневной/Ночной режим работы: Вы можете изменять поведение Вашей АТС в зависимости от времени суток.
  • Макросы: Можно создавать скрипты для решения повторяющихся задач в плане набора.

В традиционных АТС екстеншен связан с интерфейсом (портом). В Asterisk екстеншен определяется как перечень приложений (applications) и их аргументов, выполняемых в определённом порядке, Порядок выполнения определяется приоритетами (priority). Когда екстеншен набран приоритеты выполняются до разъединения вызова, или перенаправления на другой екстеншен. Каждый шаг записывается следующим образом:

exten => <exten>,<priority>,<application>, [(<args>)]

Пример простого екстеншена

exten => 100,1,Wait(5)
exten => 100,2,Answer
exten => 100,3,Playback(demo-congrats)
exten => 100,n,Hangup

Этот екстеншен состоит из 4-х действий.

Первым выполняется приложение Wait c приоритетом 1 - ждать 5 секунд (время задаётся аргументом (5).
Вторым приложение Answer - поднять трубку.
Затем Playback - проиграть звуковой файл; аргумент задает имя файла (demo-congrats) в директории по умолчанию.
Последним выполняется приложение Hangup - повесить трубку. Приоритет 'n' означает next (следующий) и может использоваться вместо любого приоритета кроме 1-го.

Например:

[default]
exten => 100,1,Wait(5)
exten => 100,n,Answer
exten => 100,n,Playback(demo-congrats)
exten => 100,n,Hangup

Использование приоритета 'n' позволяет легко редактировать отдельные строки не переписывая все приоритеты.

Набор номера

Чаще всего вызывается другой интерфейс. Вызов осуществляется командой Команда Asterisk Dial.

[default]
exten => 100,1,Dial(DAHDI/1,20)
exten => 100,2,Voicemail(u100@default)
exten => 100,102,Voicemail(b100@default)

Этот пример иллюстрирует разные варианты действий в случае, если на вызов не ответили. Сначала вызывается канал DAHDI/1, если через 20 секунд никто не ответил вызов пренаправляется на VoiceMail() с объявлением «абонент не отвечает»(u100), Если же абонент занят, вызов перейдет на приоритет N+101, в нашем случае это приоритет 102.

Маршрутизация по CallerID

Пример маршрутизации по номеру вызывающего абонента.

[default]
exten => 100/1234567,1,Congestion
exten => 100,1,Dial(DAHDI/1,20)
exten => 100,2,Voicemail(u100)
exten => 100,102,Voicemail(b100)

Если вызывается екстеншен 100 вызов направляется на интерфейс DAHDI/1, кроме случая если вызов осуществляет абонент 1234567. В этом случае вызов отклоняется. На примере видно, что идентификатор вызывающего абонента задается формой '/1234567'.

Ещё один пример маршрутизации, теперь по отсутствию CallerID.

[default]
exten => 100/,1,Zapateller
exten => 100,1,Wait(0)
exten => 100,2,Dial(DAHDI/1)

В данном примере если поступает звонок без CallerID, вызов блокируется с помощью приложения Zapateller()

Вызов группы телефонов

Часто требуется чтобы вызов по не ответу перешел на другой телефон. Рассмотрим как это сделать на примере «оператор».

[operator]
exten => 0,1,Dial(DAHDI/1,15)
exten => 0,2,Dial(DAHDI/1&DAHDI/2&DAHDI/3,15)
exten => 0,3,Playback(companymailbox)
exten => 0,4,Voicemail(100)
exten => 0,5,Hangup

Вызов поступает на DAHDI/1, в случае если телефон занят или не отвечает в течении 15 секунд, звонок переходит на группу телефонов, включая и DAHDI/1. Если и на этот раз никто не поднимает трубку, вызов переходит на голосовую почту.

Для обработки и распределения множества вызовов существует специальный механизм - очередь, которая вызывается командой Queue().

Asterisk IVR

Голосовое меню как правило задается в собственном контексте.

[sales]
exten => s,1,Background(welcome-sales)
exten => 1,1,Goto(default,100,1)
exten => 2,1,Goto(default,101,1)
[mainmenu]
exten => s,1,Background(welcome-mainmenu)
exten => 1,1,Goto(sales,s,1)
exten => 2,1,Dial,DAHDI/2
exten => 9,1,Directory(default)
exten => 0,1,Dial,DAHDI/3

Объявление проигрывается на расширении 's' (смотри Asterisk Dialplan:Стандартные расширения). В объявлении предлагается набрать '1' для вызова отдела продаж (производится переход в контекст 'sales'). Набрать '2' - вызов DAHDI/2. Набор '9' - вызов каталога (смотри Asterisk app: Directory ) и '0' вызов DAHDI/3

Использование переменных

В Asterisk существуют глобальные и специфичные для каналов переменные, используемые в качестве аргументов для команд. Переменные записываются в диалплане в виде ${foo}, где 'foo' это имя переменной. Имена должны начинаться с буквы и могут состоять из любых цифр и букв, но существуют предопределенные имена, вот некоторые из них:

${CONTEXT} Текущий контекст.
${EXTEN} Текущий екстеншен.
${EXTEN:x} Текущий екстеншен с удалением первых цифр(где х кол-во удаляемых цифр)
${PRIORITY} Текущий приоритет
${CALLERID} Текущий CallerID (имя и номер)
${CALLERIDNUM} Текущий номер Caller ID
${CALLERIDNAME} Текущее имя Caller ID
${RDNIS} перенаправление DNIS

Глобальные переменные назначаются в секции [globals] диалплана. Рассмотрим следующий пример:

[globals]
MARK => DAHDI/1
GREG => DAHDI/2&SIP/telephone
WIL => DAHDI/3
JUDY => DAHDI/4
[mainmenu]
exten => 1,1,Dial(${GREG}&${MARK})
exten => 2,1,Dial(${WIL}&${JUDY})
exten => 3,1,Dial(${JUDY}&${MARK})

Организуя диалплан таким образом, можно быстро и легко переназначать физические интерфейсы для конкретных пользователей, часто используемых в контекстах.

смотри подробнее Использование переменных в плане набора Asterisk

Вложенные контексты

Один контекст может включать другие контексты, обрабатываемые в порядке перечисления. Смотри также Порядок выбора нужного екстеншена при использовании шаблонов.

include => <context>[|<hours>|<weekdays>|<monthdays>|<months>]

Где <context> - включаемый контекст
опционально:
<hours> - часы в которые действителен контекст (например рабочее время 9:00-17:00)
<weekdays> -дни недели (mon-fri)
<monthdays> - дни
<month> - месяцы

Пример:

[local]
exten => _[0-79].,1,Dial(SIP/trunk/${EXTEN})
[long]
exten => _8.,1,Dial(SIP/trunk/${EXTEN})
[local_long]
include => local
include => long
[local_only]
include => local

В этом примере контекст local_long включает два других контекста для городской и междугородней связи, а контекст 'local_only' только для городской.

Дневной / Ночной режимы. Маршрутизация по времени

Вложенные контексты можно использовать для реализации дневного, ночного и празничного режимов. Рассмотрим следующий пример:

[newyears]
exten => s,1,Playback(happy-new-years)
[daytime]
exten => s,1,Dial(DAHDI/1,20)
[nighttime]
exten => s,1,Playback(after-hours-msg)
[default]
include => newyears||||1|jan
include => daytime|9:00-17:00|mon-fri
include => nighttime

В этом примере заданы дневной, ночной и праздничный режимы прихода звонков.

Исходящие вызовы

Направление исходящей связи можно реализовать определением короткого кода доступа (например '9'), или определить полностью шаблон набираемых номеров.

[international]
ignorepat => 9
exten => _9810.,1,Dial(DAHDI/g2/${EXTEN:1})
exten => _9810.,2,Congestion
include => longdistance

[longdistance]
ignorepat => 9
exten => _98[02-9]XXXXXXXXX,1,Dial(DAHDI/g2/${EXTEN:1})
exten => _98[02-9]XXXXXXXXX,2,Congestion
include => local

[local]
ignorepat => 9
exten => _9[02-79]XXXXXX,1,Dial(DAHDI/g2/${EXTEN:1})
exten => _9[02-79]XXXXXX,2,Congestion
include => default

В этом примере рассматриваются 3 контекста с различными правами доступа к Телефонной сети Общего Пользования .

Конструкция 'ignorepat ⇒ 9 ' говорит Астериску не отключать тон готовности после набора заданной цифры.

  • Контекст [international] позволяет набрать международный номер с любым количеством цифр.
  • Контекст [longdistance] - междугородний номер до 11-ти цифр.
  • Контекст [local] - городской номер длинной до 7-ми цифр.

Переменная ${EXTEN:1} удаляет префикс:

${123456789:1} - возвращает строку 23456789
${123456789:-4} - возвращает строку 6789
${123456789:0:3} - возвращает строку 123
${123456789:2:3} - возвращает строку 345
${123456789:-4:3} - возвращает строку 678

Шаблоны Patterns

Екстеншены могут сопоставляться шаблону, вместо однозначно заданных цифр.

Шаблон должен начинаться с символа подчеркивания _ и может использовать любой из следующих символов:

  • X – любая цифра от 0-9
  • Z – любая цифра от 1-9
  • N – любая цифра от 2-9
  • [14-6] – цифры 1,4, 5 и 6
  • . – любые возможные символы.

Резервные транки и LCR (выбор направления с наименьшей стоимостью)

Весьма полезно настроить LCR (Least Coast Routing) и перенаправление в случае отказа внешней линии.

[tolllongdistance]
exten => _98XXXXXXXXXX,1,Dial(DAHDI/g2/${EXTEN:1})
exten => _98XXXXXXXXXX,2,Congestion
[low_rate_moscow]
exten => _98495XXXXXXX,1,Dial(IAX/trunk/${EXTEN:1})
exten => _98495XXXXXXX,2,Dial(DAHDI/g2/${EXTEN:1})
exten => _98495XXXXXXX,3,Congestion
[longdistance]
include => low_rate_moscow
include => tolllongdistance

В этом примере междугородние вызовы направляются на DAHDI интерфейс, но звонки в Москву направляются через более выгодного провайдера на IAX транк. В случае же недоступности IAX транка, вызовы перенаправляются через DAHDI.

Использование Макросов

Вам может потребоваться создать множество екстеншенов (расширений) очень похожих друг на друга. Чтобы упростить работу с диалпланом используются Макросы. Для создания макроса используется контекст имя которого начинается с «macro-» и далее уникальное имя макроса. Выполнение макроса начинается с ектеншена 's'. В макросах используются локальные переменные:

${MACRO_EXTEN} – Екстеншен вызываемый макросом ${MACRO_CONTEXT} – Контекст вызываемый макросом ${MACRO_PRIORITY} – активный приоритет вызываемый макросом ${MACRO_OFFSET} – если установлено вызывает смещение n + ${MACRO_OFFSET} ${ARGn} – аргумент 'n' в макросе.

[macro-oneline]
;
; Однолинейный телефон
;
; ${ARG1} – Телефон
;
exten => s,1,Dial(${ARG1},20)
exten => s,2,Voicemail(u${MACRO_EXTEN})
exten => s,3,Hangup
exten => s,102,Voicemail(b${MACRO_EXTEN})
exten => s,103,Hangup
[macro-twoline]
;
; Двухлинейный телефон
;
; ${ARG1} – Телефон (линия) 1
; ${ARG2} – Телефон (линия) 2
;
exten => s,1,Dial(${ARG1},20)
exten => s,2,Voicemail(u${MACRO_EXTEN})
exten => s,102,Dial(${ARG2},20)
exten => s,103,Voicemail(b${MACRO_EXTEN})

[default]
exten => 1000,1,Macro(oneline,DAHDI/1)
exten => 1001,1,Macro(oneline,SIP/1001)
exten => 1002,1,Macro(twoline,DAHDI/3,DAHDI/4)

Когда макросы [macro-oneline] и [macro-twoline] созданы, в контексте [default] надо написать только одну сроку для выполнения нескольких стандартных действий.

[from-phones1]
exten => _X.,1,Dial(SIP/sip_trunk/${EXTEN},180,)
exten => _X.,n,Macro(dialstatus,s,1)

exten => _X.,1,Dial(DAHDI/g2/${EXTEN},180,)
exten => _X.,n,Macro(dialstatus,s,1)

[macro-dialstatus]
exten => s,1,Answer
exten => s,n,Goto(s-${DIALSTATUS},1)
exten => s-NOANSWER,1,Hangup
exten => s-CONGESTION,1,Congestion
exten => s-CANCEL,1,Hangup
exten => s-BUSY,1,Playtones(425/375,0/375)
exten => s-BUSY,n,Busy(7)
exten => s-BUSY,n,Hangup
exten => s-CHANUNAVAIL,1,Hangup

Приложение Macro объявлено устаревшим, вместо него рекомендуется использовать GoSub.

Синтаксис Gosub

  Gosub([[context,]exten,]priority[(arg1[,...][,argN])])
[sub-test]
exten => _X.,1,Dial(${ARG1}/${ARG2},20,)
exten => _X.,n,Playback(tt-weasels)
exten => _X.,n,Hangup

[test]
exten => _X.,1,Gosub(sub-test,${EXTEN},1(SIP/trunk,${EXTEN}))

Запись разговоров Asterisk

[macro-mixmonitor]
exten => s,1,Set(RECORD_FILENAME=${STRFTIME(${EPOCH},,%Y%m%d-%H%M%S)}-${CALLERID(num)})
        same => n,MixMonitor(${RECORD_FILENAME}.wav,b)
        same =>  n,Dial(${ARG1},180,) 
[outbound_route1]
exten => _9.,1,Macro(mixmonitor,PJSIP/sipprovider/${EXTEN:1})

В данном примере вызов с префиксом '9', должен быть скоммутирован через SIP транк ITSP. Разговор будет записан в формате 'wav' и сохранен в директорию по умолчанию «/var/lib/asterisk/monitor/ГодМесяцДень-ЧасыМинутыСекунды-НомерВызывающего Абонента.wav

Структура same ⇒ позволяет сократить код, избежав многочисленных повторений «exten ⇒ s,» в данном случае.

Хорошая мысль поэкспериментировать и с другими переменными в имени файла, например ${UNIQUEID}.

Asterisk Dialstatus

Определим состояние линии и выберем действие на этом основании. Предположим, у вас есть несколько филиалов (например branch1 - внутренние номера 41ХХ и branch2 - номера 42ХХ), в которых используются шлюзы Cisco SPA8800. Используя один шаблонный GoSub выберем свободный FXO порт для исходящего вызова. Выбор нужного шлюза произведем на основании CALLERID(num) абонента.

[from-internal]
include => from-branch1
include => from-branch2

[from-branch1]
exten => _X./_41XX,1,Set(_TRK=SIP/branch1_fxo)
exten => _X./_41XX,n,Gosub(sub-spa8800,${EXTEN},1(${TRK}1,${TRK}2,${TRK}3,${TRK}4,${EXTEN},branch1))
exten => _X./_41XX,n,hangup

[from-branch2]
exten => _X./_42XX,1,Set(_TRK=SIP/branch2_fxo)
exten => _X./_42XX,n,Gosub(sub-spa8800,${EXTEN},1(${TRK}1,${TRK}2,${TRK}3,${TRK}4,${EXTEN},branch2))
exten => _X./_42XX,n,hangup

В первом блоке мы видим три контекста from-internal - общий контекст для всех внутренних абонентов, from-branch1 - контекст первого филиала, from-branch2 - контекст второго филиала итд. (филиалов, как вы понимаете может быть сколько угодно)
Первая строка контекстов from-branchN задает переменную: технология (SIP) и название транка, специфичного для данного филиала: SIP/branch_fxo
Строка с командой GoSub передает название транка как аргумент, добавляя цифру от 1 до 4.
Таким образом в контекст sub-spa8800 из контекста [from-branch1], например, поступают аргументы SIP/branch1_fxo1, SIP/branch1_fxo2, SIP/branch1_fxo3, SIP/branch1_fxo4
Пятым аргументом передается набираемый номер - ${EXTEN}
и последний, шестой аргумент - название филиала.
Итого в контекст sub-spa8800 передается 6 аргументов:

from-branch ${TRK}1${TRK}2${TRK}3${TRK}4${EXTEN}branch
sub-spa8800${ARG1}${ARG2}${ARG3}${ARG4}${ARG5}${ARG6}
[sub-spa8800]
exten => _X.,1,Noop()
exten => _X.,n,Set(CDR(userfield)=${ARG6}) ;запишет в cdr название филиала 
exten => _X.,n,Dial(${ARG1}/${ARG5},60,rt) ; попытка набора через первый порт fxo
exten => _X.,n,NoOp( Dial Status: ${DIALSTATUS}) 
exten => _X.,n,Goto(s-${DIALSTATUS},1) ; если вызов неудачен, 
;выполним действия на основании dialstatus
exten => s-NOANSWER,1,hangup ; не ответили в заданное время (60 сек), повесить трубку
exten => s-CONGESTION,1,Dial(${ARG2}/${ARG5},60,rt) ; пробуем fxo2, если fxo1 CONGESTION
exten => s-CONGESTION,n,Dial(${ARG3}/${ARG5},60,rt) ; пробуем fxo3
exten => s-CONGESTION,n,Dial(${ARG4}/${ARG5},60,rt) ; пробуем fxo4
exten => s-CONGESTION,n,Congestion ; все транки заняты
exten => s-CANCEL,1,Hangup ; вызываемый абонент отказался принять вызов
exten => s-BUSY,1,Busy ; вызываемый абонент занят
exten => s-CHANUNAVAIL,1,Dial(${ARG2}/${ARG5},60,rt) ; fxo1 недоступен, пробуем fxo2
exten => s-CHANUNAVAIL,n,Dial(${ARG3}/${ARG5},60,rt) ; пробуем fxo3
exten => s-CHANUNAVAIL,n,Dial(${ARG4}/${ARG5},60,rt) ; пробуем fxo4
exten => s-CHANUNAVAIL,n,Dial(PJSIP/${ARG5}@reserv-sip-trunk,60,rt) ; пробуем набрать через 
;резервный sip транк,  если шлюз недоступен.
exten => s-CHANUNAVAIL,n,Hangup ; отбой, все пропало
exten => h,1,Return ; вернемся в исходный контекст
;и продолжить выполнение диалплана.

Команда Dial возвращает переменную ${DIALSTATUS} с одним из следующих значений:

  • CHANUNAVAIL - канал недоступен
  • CONGESTION - канал переполнен
  • NOANSWER - истек таймаут вызова
  • BUSY - вызывемый абонент занят
  • ANSWER - канал ответил
  • CANCEL - вызываемый абонент отказался принять вызов

Команда GoTo(s-${DIALSTATUS},1) направляет выполнение диалплана в расширение s-${DIALSTATUS} в данном контексте, приоритет 1. Таким образом, мы можем предпринять различные действия, на основании статуса канала.

Настройка Asterisk

  • asterisk/dialplan.txt
  • Последние изменения: 2018/11/06