Freeswitch XML Dialplan

https://freeswitch.org/confluence/display/FREESWITCH/XML+Dialplan
Created by Jim Hayes, last modified by Ryan Harris
Перевод Олег Звездочкин.

XML диалплан используется Freeswitch по умолчанию.

Основная задача диалплана маршрутизация вызова к конечной точке ( endpoint), которой может быть традиционный внутренний абонент атс, voicemail, ivr (интерактивное голосовое меню), очередь колл центра (queue), группа внутренних абонентов или внешний номер. Диалплан очень гибок.

Диалплан может быть разделён на контексты (contexts), которые группируют наборы расширений (extensions). Контекст может быть присвоен sip профилю или транку и др. Вызовы могут передаваться из контекста в контекст. Например, по умолчанию, в ванильной конфигурации FS, диалплан раделен на два контекста, внешний - public и внутренний - default. Вызовы приходящие через внешний контекст, пройдя предварительную обработку перенаправляются во внутренний контекст для вызова расширений (extensions).

Контексты диалплана, также позволяют создавать многопользовательские (multi-tenant) АТС, на базе одного сервера. Каждый тенант может иметь собственные расширения и другие ресурсы в собственных контекстах диалплана не конфликтуя с другими.

 

Понимание данной документации требует базовых знаний регулярных выражений (RegExp).  XML диалплан использует Perl Compatible Regular Expression (PCRE) синтаксис в полях условий (conditions).

Простые диалпланы легко создаются с минимальными знаниями о регулярных выражениях.

В создании умеренно сложных XML диалпланов поможет глубокое понимание PCRE, а также представление о переменных, массивах и управлении потоками в скриптовых языках программирования.

После изучения данной документации вы научитесь:

  • Привязывать sip профили к контекстам диалплана для совершения вызовов.
  • Создавать хорошо управляемый диалплан используюший регулярные выражения, для вызовов множества расширений через единый шаблон.
  • Понимать потоки обработки вызовов через условия и вложенные условия.
  • А также, почему и где эти условия применяются в диалплане.
Данный обзор использует в примерах Sofia SIP драйвер, как источник вызовов обрабатываемых диалпланом. Остальные драйверы каналов FS используют схожий механизм.

Когда вызов поступает в АТС, Sofia первой принимает его. Она собирает информацию о вызове и определяет какой контекст должен быть использован.

Sofia передает информацию при помощи переменных канала назначенных вызову, которые диалплан использует для манипуляций с вызовом.
Переменные содержат подробную информацию о вызове:

info channel variables

info channel variables

2018-10-12 09:54:45.578381 [INFO] mod_dptools.c:1761 CHANNEL_DATA:
Channel-State: [CS_EXECUTE]
Channel-Call-State: [RINGING]
Channel-State-Number: [4]
Channel-Name: [sofia/office/2666@192.168.0.231:5077]
Unique-ID: [a262c323-0553-4b66-8452-177827217e64]
Call-Direction: [inbound]
Presence-Call-Direction: [inbound]
Channel-HIT-Dialplan: [true]
Channel-Call-UUID: [a262c323-0553-4b66-8452-177827217e64]
Answer-State: [ringing]
Channel-Read-Codec-Name: [PCMA]
Channel-Read-Codec-Rate: [8000]
Channel-Read-Codec-Bit-Rate: [64000]
Channel-Write-Codec-Name: [PCMA]
Channel-Write-Codec-Rate: [8000]
Channel-Write-Codec-Bit-Rate: [64000]
Caller-Direction: [inbound]
Caller-Logical-Direction: [inbound]
Caller-Username: [2666]
Caller-Dialplan: [XML]
Caller-Caller-ID-Name: [2666]
Caller-Caller-ID-Number: [2666]
Caller-Orig-Caller-ID-Name: [2666]
Caller-Orig-Caller-ID-Number: [2666]
Caller-Network-Addr: [192.168.100.97]
Caller-ANI: [2666]
Caller-Destination-Number: [2669]
Caller-Unique-ID: [a262c323-0553-4b66-8452-177827217e64]
Caller-Source: [mod_sofia]
Caller-Context: [default]
Caller-Channel-Name: [sofia/office/2666@192.168.0.231:5077]
Caller-Profile-Index: [1]
Caller-Profile-Created-Time: [1539327285578381]
Caller-Channel-Created-Time: [1539327285578381]
Caller-Channel-Answered-Time: [0]
Caller-Channel-Progress-Time: [0]
Caller-Channel-Progress-Media-Time: [0]
Caller-Channel-Hangup-Time: [0]
Caller-Channel-Transfer-Time: [0]
Caller-Channel-Resurrect-Time: [0]
Caller-Channel-Bridged-Time: [0]
Caller-Channel-Last-Hold: [0]
Caller-Channel-Hold-Accum: [0]
Caller-Screen-Bit: [true]
Caller-Privacy-Hide-Name: [false]
Caller-Privacy-Hide-Number: [false]
variable_direction: [inbound]
variable_uuid: [a262c323-0553-4b66-8452-177827217e64]
variable_session_id: [30216]
variable_sip_from_user: [2666]
variable_sip_from_port: [5077]
variable_sip_from_uri: [2666@192.168.0.231:5077]
variable_sip_from_host: [192.168.0.231]
variable_video_media_flow: [sendrecv]
variable_channel_name: [sofia/office/2666@192.168.0.231:5077]
variable_sip_call_id: [0_412809328@192.168.100.97]
variable_ep_codec_string: [CORE_PCM_MODULE.PCMA@8000h@20i@64000b,CORE_PCM_MODULE.PCMU@8000h@20i@64000b,mod_spandsp.G722@8000h@20i@64000b,mod_opus.opus@48000h@20i@2c]
variable_sip_local_network_addr: [192.168.0.231]
variable_sip_network_ip: [192.168.100.97]
variable_sip_network_port: [5060]
variable_sip_invite_stamp: [1539327285578381]
variable_sip_received_ip: [192.168.100.97]
variable_sip_received_port: [5060]
variable_sip_via_protocol: [udp]
variable_sip_authorized: [true]
variable_sip_acl_authed_by: [spb]
variable_sip_from_user_stripped: [2666]
variable_sip_from_tag: [1159539958]
variable_sofia_profile_name: [office]
variable_recovery_profile_name: [office]
variable_sip_full_via: [SIP/2.0/UDP 192.168.100.97:5060;branch=z9hG4bK1596091148;rport=5060]
variable_sip_full_from: [;tag=1159539958]
variable_sip_full_to: []
variable_sip_allow: [INVITE, INFO, PRACK, ACK, BYE, CANCEL, OPTIONS, NOTIFY, REGISTER, SUBSCRIBE, REFER, PUBLISH, UPDATE, MESSAGE]
variable_sip_req_params: [user=phone]
variable_sip_req_user: [2669]
variable_sip_req_port: [5077]
variable_sip_req_uri: [2669@192.168.0.231:5077]
variable_sip_req_host: [192.168.0.231]
variable_sip_to_params: [user=phone]
variable_sip_to_user: [2669]
variable_sip_to_port: [5077]
variable_sip_to_uri: [2669@192.168.0.231:5077]
variable_sip_to_host: [192.168.0.231]
variable_sip_contact_user: [2666]
variable_sip_contact_port: [5060]
variable_sip_contact_uri: [2666@192.168.100.97:5060]
variable_sip_contact_host: [192.168.100.97]
variable_sip_user_agent: [Yealink SIP-T19P_E2 53.82.0.20]
variable_sip_via_host: [192.168.100.97]
variable_sip_via_port: [5060]
variable_sip_via_rport: [5060]
variable_max_forwards: [70]
variable_switch_r_sdp: [v=0
o=- 20309 20309 IN IP4 192.168.100.97
s=SDP data
c=IN IP4 192.168.100.97
t=0 0
m=audio 12456 RTP/AVP 8 0 9 107 101
a=rtpmap:8 PCMA/8000
a=rtpmap:0 PCMU/8000
a=rtpmap:9 G722/8000
a=rtpmap:107 opus/48000/2
a=rtpmap:101 telephone-event/8000
a=fmtp:101 0-15
a=ptime:20
]
variable_rtp_use_codec_string: [PCMA,PCMU,G722,opus]
variable_audio_media_flow: [sendrecv]
variable_remote_media_ip: [192.168.100.97]
variable_remote_media_port: [12456]
variable_rtp_audio_recv_pt: [8]
variable_rtp_use_codec_name: [PCMA]
variable_rtp_use_codec_rate: [8000]
variable_rtp_use_codec_ptime: [20]
variable_rtp_use_codec_channels: [1]
variable_rtp_last_audio_codec_string: [PCMA@8000h@20i@1c]
variable_read_codec: [PCMA]
variable_original_read_codec: [PCMA]
variable_read_rate: [8000]
variable_original_read_rate: [8000]
variable_write_codec: [PCMA]
variable_write_rate: [8000]
variable_dtmf_type: [rfc2833]
variable_endpoint_disposition: [RECEIVED]
variable_call_uuid: [a262c323-0553-4b66-8452-177827217e64]
variable_current_application: [info]


Например переменная destination_number содержит цифры набранные вызывающим абонентом, другие переменные содержат инф. о CallerID, IP адресе звонящего и т.п.

XML дилплан организован как серия расширений (extensions). Диалплан пошагово проверяет все известные ему расширения, пока не найдет подходящее. Диалплан может остановиться после первого совпадения условий или продолжить проверку, это зависит от дополнительных параметров, указанных в условии/условиях расширений. Об этом будет подробно рассказано позже. Совпадение находится путем оценки условий (conditions) внутри каждого расширения. Множество условий могут быть назначены в одном расширении, диалплан найдя совпадение, будет продолжать проверять условия уже внутри расширения.

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

Если вложенные условия не совпадают в пределах одного расширения, возможно указать анти-действия (anti-actions) которые будут выполнены.

XML диалплан базовая концепция, изучение которой требуется для работы с FS. Если вы хорошо знакомы с другими скриптовыми языками программирования, то понять логические операции и течение потоков обработки диалплана, не составит особого труда и вы сможете создавать сложные продвинутые сценарии. Но XML не может решить всех задач возникающих при создании современных систем связи. Обширный XML диалплан может быть запутанным и сложным для понимания. Для решения нетривиальных задач существуют дополнительные процессоры диалплана. Диалплан можно писать на Lua, Python, Javascript и т.п. Чаще всего используется Lua.

Вводный пример

Следующий пример показывает маршрутизацию вызова на два екстеншена, 500 и 501.
Перед тем как использовать его, потребуется привязать наш диалплан в sip профиле параметром context. Устройства зарегистрированные через этот профиль будут использовать указанный диалплан.

<profile name="example_sip_profile">
    <param name="context" value="example"/>
    ...other configuration statements...
</profile>

Пример диалплана: 

<context name="example">
    <extension name="500">
        <condition field="destination_number" expression="^500$">
            <action application="bridge" data="user/500"/> 
        </condition>
    </extension>
 
    <extension name="501">
        <condition field="destination_number" expression="^501$">
            <action application="bridge" data="user/501"/>
            <action application="answer"/>
            <action application="sleep" data="1000"/>
            <action application="bridge" data="loopback/app=voicemail:default ${domain_name} ${dialed_extension}"/>
        </condition>
    </extension>
</context>

Когда выполняется вызов, оценивается каждое расширение пока не будет найдено совпадение.

Первая строка примера содержит блок <context name="example">...</context>. Все определения в этом блоке относятся к диалплану по имени «example».
Вложения внутри контекстного блока, являются блоками расширения, содержащими соответствующие "conditions" и соответствующие им правила "actions". Расширения обрабатываются в том порядке, в котором они отображаются в файле конфигурации.

В примере Sofia выполнит диалплан когда получит входящий вызов.
Предположим вызывается номер 501:

  • Первое расширение игнорируется, т.к. не совпадает условие по destination_number.
  • Второе расширение совпадает по условию и будет вызван номер 501,
    • если он не ответит, вызов будет отвечен приложением answer,
    • будет выдержана пауза 1000ms
    • и вызов будет перенаправлен на голосовую почту.

Информация к размышлению

  • Найдите action в примере, который переводит вызов на голосовую почту.
    • Обратите внимание на переменные канала domain_name и dialed_extensions и как они передаются в приложение voicemail.
    • Вы можете создавать и задавать собственные переменные, такие как эти, в своем диалаплане.
    • Подумайте будут ли они переданы? Если нет, то почему?
  • Диалплан прекращает выполнение дополнительных действий, как только вызов будет соединен (bridge) с другим екстеншеном или передан в другой диалплан. Это значит, что в нашем примере, если 501 ответит последующие действия не будут выполнены после окончания вызова (но это поведение по умолчанию можно изменить).
  • Создание диалплана указанным способом для вызова множества расширений может быть очень нудным и не очень эффективным занятием. Возможно ли создать диалплан, в котором 1000 расширений будут вызываться одним определением?
Возможно приведенный пример вообще не будет работать, почему?

Структура директорий и файлов диалплана

Конфигурационные файлы диалплана Freeswitch хранятся в ../conf/dialplan (или /etc/freeswitch/dialplan ). Каждый контекст и его расширения вынесенные в отдельные файлы храняться в поддиректориях, обычно имеющих то же имя, что и контекст. Это не обязательная, но рекомендованная структура.

Пример структуры файлов диалплана для простой АТС:

freeswitch/
    conf/
        dialplan/
            public.xml
            public/
                00_security_screen.xml
                10_inbound_sip-bandwidth-r-us.xml
                29_inbound_sip-super-call.xml
                ...
            default.xml
            default/
                00_feature_codes.xml
                05_voicemail_access.xml
                20_extension_2001.xml
                20_extension_2002.xml
                ...

Информация к размышлению

  • Данная структура это простой пример, который может послужить хорошей отправной точкой для создания вашей собственной структуры диалплана.
  • Любой .xml файл помещенный в директорию diaplan, будет загружен при старте FS.
  • public.xml содержит содержит общую конфигурацию для контекста public, включая все файлы в директории public.
  • default.xml содержит общую конфигурацию для контекста default, включая все файлы в директории default.
  • Файлы загружаются и читаются в алфавитном порядке, так что начинать имена файлов с цифр это хорошая практика, которая позволяет контролировать порядок обработки расширений.

Context

Контекст это группа расширений (extensions).

Единственный параметр контекста, это его имя. Это имя использует Sofia (или любой другой драйвер канала FS) для отправки вызовов в маршрутизацию:

Sip_profile → Context → Extensions(Conditions(Actions))

Понятие контекста FS немного другое, чем, например, понятие контекста Asterisk.
Для создания комплексного диалплана в рамках одного домена, вполне можно обойтись двумя базовыми контекстами, а роль контекстов в понимании Asterisk, будут выполнять обусловленые расширения.
Во главе угла тут стоят conditions - условия определяют выбор екстеншена, а контекст определяет набор extensions доступных данному профилю.
<param name="context"... как правило задается профилю (sip_profile), но этот параметр можно задать и gateway (транку).
Ну и конечно можно передавать вызовы из контекста в контекст из расширений.

<context name="default">
    <!-- one or more extension tags -->
</context>

Extensions

Каждый контекст содержит одно или более расширений. Расширения это пункты назначения для вызовов, через них проходит маршрутизация.

Имя extensions не связано напрямую с вызываемым телефоном. Параметр name должен быть уникальным. Другие контексты могут передавать управление вызовом используя это имя.

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

Внутри расширения может содержаться одно или более условий (conditions). Если совпадение есть, выполняются действия (actions), если нет, могут быть назначены anti-actions.

Пример:

<extension name="Your_extension_name_here">
    <condition(s)...
        <action(s) .../>
        <anti-action(s) .../>
    </condition>
</extension>

инф. к размышлению

инф. к размышлению

Но чтобы extension был выбран, должно пройзойти первичное совпадение условий и уже далее может происходить ветвление actions | anti-actions по вложенным условиям. Произойдет ли выбор данного extension и выполнение anti-action если условие только одно и не совпадает?


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

<extension name="500" continue="true">

Conditions

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

Условия сравнивают заданные регулярные выражения с переменными присвоенными вызову. Первый пример просто ожидает совпадения переменной destination_number с номером «500»:

<extension name="500">
    <condition field="destination_number" expression="^500$">
        <action application="bridge" data="user/500"/> 
    </condition>
</extension>

Условия используют Perl Compatible Regular Expression library. (См. документацию Perl Compatible Regular Expression)

Пример 1: Capturing Digits

Следущий пример демонстрирует как удалить цифры из набранного номера при помощи PCRE. (Отрежем код страны и города для городского вызова в локальной тлф. сети)

В России, последние 7 цифр из 11 являются локальной частью номера. (По крайней мере для миллионников). Так, если наша АТС находится в Санкт Петербурге первые четыре цифры не надо транслировать в городскую телефонную сеть, они всегда будут 8812:

<condition field="destination_number" expression="^8812\d\d\d\d\d\d\d$">

Условие совпадает с любым 11-значным номером, начинающимся с "8812“. Захватим последние 7 цифр, заключив их в круглые скобки:

<condition field="destination_number" expression="^8812(\d\d\d\d\d\d\d)$">

Условие все также совпадает с 11-значным номером «8812…….», но последние 7 цифр будут сохранены в переменной $1. Эту временную переменную мы и используем для вызова в городскую тлф. сеть:

<extension name="local_calls">
    <condition field="destination_number" expression="^8812(\d\d\d\d\d\d\d)$">
        <action application="bridge" data="sofia/gateway/LocalTelco/$1"/>
    </condition>
</extension>

destination_number изначально - 88123216111 сохранен через $1, как 3216111 и в таком виде отправлен в Петербургскую Тлф. Сеть.

Чтобы добавить цифры за переменной заключите ее в брекеты (фигурн. скобки) - «sofia/gateway/LocalTelco/${1}5».

Пример 2: Logical AND

Предположим вы хотите перенаправлять вызовы с «500» на «531», но только по воскресеньям.

В XML диалплане можно задавать больше одного условия в одном расширении. Если основное условие совпадает, будут проверяться вложенные условия и выполняться предусмотренные ими действия.

Вот тому пример:

<condition field="destination_number" expression="^500$"/>
<condition wday="1">
    <action application="bridge" data="sofia/internal/531@example.com"/>     # если Воскр. (1-й день недели) вызываем 531
    <anti-action application="bridge" data="sofia/internal/500@example.com"/>     # если нет, то 500.
</condition>

anti-action использованы, чтобы направить вызов в нужное место, если последнее условие не совпало.

Структура Conditions

  • Синтаксис:
  • Предварительные условия должны быть закрыты - />. Они ее имеют вложенных действий, а только определяют выбор данного расширения. Условия содержащие финальные действия, обрамляют их:

<condition>…</condition>.

  • Они следуют за «предварительными» условиями, но могут обходиться и без них.
  • Если «последнее» condition имеет вложенные условия, то они могут иметь вышеописанную структуру.

Нажмите, чтобы отобразить

Нажмите, чтобы скрыть

  • Don't be fooled: does not evaluate all the conditions to make its decision. This design pattern only works because stops processing an extension after the first condition failure.

Пример 3: Logical OR

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

Есть несколько способов реализации. Используйте подходящий для ваших задач.

Простейший метод использует ИЛИ прямо в регулярном выражении. destination_number совпадает с «501» или «502»:

 <condition field="destination_number" expression="^501|502$">
    action(s)...
</condition>

Этот метод хорош, если используется только одно полe (field) условия. Если требуется сравнение для двух или более разных полей, тогда используется расширенный синтаксис регулярных выражений FS:

 <condition regex="any">
  <regex field="some_field" expression="Some Value"/>
  <regex field="another_field" expression="^Another\s*Value$"/>
  <action(s) ...>
  <anti-action(s)...>
</condition>

Обратите внимание на regex="any" в начальном условии. Это говорит, что любое условие правда (true) и если какое-либо из последующих условий совпадет, то будут выполнены его действия. Если ни одно из вложенных условий не совпадает, то будут выполнены anti-actions, ибо any всегда true. Следующий пример выполняется, если caller_id_name = «Some User» или если caller_id_number = «1001»:

<extension name="Regex OR Example">
  <condition regex="any">
    <!-- If either of these is true then the subsequent actions are added to execute list -->
    <regex field="caller_id_name" expression="Some User"/>
    <regex field="caller_id_number" expression="^1001$"/>
    <action application="log" data="INFO At least one of the conditions matched!"/>
    <!-- If *none* of the regexes is true then the anti-actions are added to the execute list -->
    <anti-action application="log" data="WARNING None of the conditions matched!"/>
  </condition>
</extension> 

<condition regex=…> может принимать ещё два значения: all и xor. Рассмотрим все три значения подробнее:

  • regex="any" - Мы использовали это выше. Любые данные совпадают с начальным условием, значит для любого вызова вложенные условия будут рассмотрены, а действия выполнены по их совпадению.
  • regex="all" - эквивалентно логической операции «AND». Все последующие условия должны совпасть, чтобы действия были выполнены. Это другой вариант реализации «AND» из примера 2. Данный синтаксис сделает ваш диалплан более удобочитаемым.
  • regex="xor" - логическая операция «XOR». «XOR» - обычно подразумевает «eXclusive-OR», т.е. только одно условие (exactly one) должно совпасть, чтобы действия были выполнены.
  • Действия и анти-действия выполняются также, как и в обычном блоке условий.

Рассмотрим подробнее данный метод. Если caller_id_name = «Michael S Collins» ИЛИ caller_id_number = 1002, 3757 или 2816 переменная calling_user будет назначена, как «mercutioviz», иначе же - «loser». После воспроизведения общего приветственного сообщения будет проиграно сообщение выбранное на основании значения переменной calling_user.

<extension name="Regex OR example 2" continue="true">
  <condition regex="any" break="never">
    <regex field="caller_id_name" expression="^Michael\s*S?\s*Collins"/>
    <regex field="caller_id_number" expression="^1001|3757|2816$"/>
    <action application="set" data="calling_user=mercutioviz" inline="true"/>
    <anti-action application="set" data="calling_user=loser" inline="true"/>
  </condition>
 
  <condition>
    <action application="answer"/>
    <action application="sleep" data="500"/>
    <action application="playback" data="ivr/ivr-welcome_to_freeswitch.wav"/>
    <action application="sleep" data="500"/>
  </condition>
 
  <condition field="${calling_user}" expression="^loser$">
    <action application="playback" data="ivr/ivr-dude_you_suck.wav"/>
    <anti-action application="playback" data="ivr/ivr-dude_you_rock.wav"/>
  </condition>
</extension>  

inline

Обратите внимание на опцию inline="true" добавленную к действиям устанавливающим значение пользовательской переменной calling_user. Обычно, когда обрабатывается расширение, все действия собираются вместе и выполняются ПОСЛЕ проверки всех условий. Но т.к. переменная calling_user используется в последующих условиях, требуется назначить её сразу при совпадении условия к которому относится данное действие. Для этого и существует параметр inline.

Т.е., если задан параметр inline="true" действие будет выполнено НЕМЕДЛЕННО, а не после проверки всех условий. Данный параметр может применяться не ко всем типам действий (action application).

Без указания inline="true", третий блок условий в примере не будет выполнен.

Пример 4: Logical XOR

Мы рассмотрели использование regex= clause в отношении AND и OR. Для полноты приведем последний пример использования XOR. Действия в примере будут выполнены, если совпадет первое ИЛИ второе условие, но НЕ будут выполнены, если совпадают оба.

 <extension name="Regex XOR example 3" continue="true">
  <condition regex="xor">
    <!-- If only one of these is true then the subsequent actions are added to execute list -->
    <regex field="caller_id_name" expression="Some User"/>
    <regex field="caller_id_number" expression="^1001$"/>
    <action application="log" data="INFO Only one of the conditions matched!"/>
    <!-- If *none* of the regexes is true then the anti-actions are added to the execute list -->
    <anti-action application="log" data="WARNING None of the conditions matched!"/>
  </condition>
</extension> 

Actions and Anti-Actions

Последняя часть определения расширений это действия (action application). Действия логическое завершение расширения, они выполняют соединения и предоставляют другие функции. Список приложений (applications) весьма обширен, вы можете ознакомиться с ним здесь.

Action Description
answer Отвечает на вызов
bridge Совершает вызов
log Записывает событие в лог
hangup Разъединяет вызов
playbackПроигрывает аудио файл или тональный сигнал
set Устанавливает переменную канала
transferПередает обработку вызова другому расширению

inline

Действия не выполняются «in-line» одновременно с условиями, они только связываются с ними. Сначала проверяются все условия в расширении, а затем выполняются привязанные к совпавшим условиями действия.

примечание 1 В большинстве случаев вы не можете получить результат выполняемого действия СРАЗУ для проверки или использования в последующих <condition…> или <action …>. Но это ограничение можно обойти, если передать обработку вызова в другое расширение (<action application=«transfer»…). Тогда все действия первого расширения будут выполнены и [например] переменные назначены.

Или же выполнить действие «inline»:

примечание 2 Некоторые, но не все, приложения (applications) могут быть выполнены с параметром inline="true". Данный параметр чаще всего полезен в приложении set. Список приложений в которых может быть использован "inline" приведен ниже, в этом документе

Anti-Actions

В нескольких предыдущих примерах, мы использовали выражение anti-action. Само его имя подразумевает, что действие будет выполнено, если условие ложно. Для каждого условия, если action не может быть выполнено, anti-actions будут выполнены и наоборот.

Dialplan Variables

Итак, мы рассмотрели основные концепции диалплана и его структуру. В примерах было показано использование:

  • Extensions - расширений
  • Conditions - условий
  • Regular Expressions - регулярных выражений
  • Actions and Anti-Actions - действий и анти-действий.

Теперь рассмотрим тему переменных. Они являются важной частью диалплана.

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

Рассмотрим несколько переменных канала, чтобы понять как они используются и преобразуются в диалплане FS.

Доступ к переменным

Есть три разновидности переменных доступных для использования в диалплане Freeswitch:

  • * Переменные канала
    • Глобальные переменные
    • Встроенные переменные (time, date, etc.)
Переменные канала (Channel Variables)

В переменных канала храниться информацию о текущем вызове для контроля его прохождения через диалплан. Channel variables содержат обширную коллекцию инфы о вызове, а также массив параметров, которые можно задавать в процессе обработки вызова диалпланом. Доступ к переменным осуществляется по имени заключенном в ${}, например для доступа к доменному имени по умолчанию, можно просто указать ${domain_name} в строке набора, и имя будет поставлен:

    <action application="bridge" data="user/1000@${domain_name}/>

Значение переменной канала задается так:

    <action application="set" data="call_timeout=30"/>

Полный список Channel Vaiables и способы их использования можно посмотреть тут.
В этом же документе приведем короткий список часто используемых переменных:

Variable Name Description
caller_id_name Имя вызывающего абонента.
destination_number Вызываемый номер.
direction направление вызова inbound или outbound (также известные как: a-leg, b-leg)
channel_name Имя inbound канала, например:sofia/sales/John_Smith@192.168.1.1
call_timeout Таймаут вызова в секундах.
state Состояние канала, например CS_EXECUTE или CS_HANGUP
bridge_hangup_causeКоды причин разъединения вызова, например NO_ANSWER, NORMAL_CLEARING или USER_BUSY
Встроенные переменные (Built-In)

Доступ к встроенным (Built-In) осуществляется без префикса ${}

Встроенные переменные:

Built-In Name Description
Стандартное: Дата и Время
date-timeТекущие дата и время: YYYY-MM-DD HH:MM:SS
пример: 2014-08-12 15:34:59
time-of-dayТекущее локальное время: HH:MM:SS
пример: 15:34:59
Дата и время составляющие (все для локальной временной зоны)
year Текущий год. (от 1970 до 9999)
monМесяц. (от 1 до 12)
mdayДень текущего месяца. (от 1 до 31)
hourЧас текущих суток. (от 0 до 23)
minuteМинута текущего час. (от 0 до 59)
Дополнительные составляющие
wdayДень недели. (Воскр. = 1 до 7)
weekТекущая неделя с 1 Янв (от 1 до 53)
mweekНеделя текущего месяца. (от 1 до 6)
ydayДень текущего года (от 1 до 366)
mindayТекущая минута с полуночи. (от 0 до 1440)
tz-offsetСмещение временной зоны от Гринвича. (от -11 до +11)
dstВозвращает 1 если Летнее Время или 0 если нет.

В операторах условий вы можете сравнивать с более чем одной встроенной переменной. Все сравнения должны соответствовать (логическому AND) до того, как будут выполнены его действия. Существует несколько способов совпадения со встроенной переменной. Вы можете:

Kind Example Description
Equality <condition wday="1"> Is it Sunday?
Range <condition wday="2-4"> Is it Monday, Tuesday, or Wednesday?
List <condition wday="1,4"> Is it Sunday or Wednesday?
Combination<condition wday="1-3,7">Is it Sunday, Monday, Tuesday, or Saturday?

Диапазоны могут использоваться для времени и дат:

<condition time-of-day="08:00:00-09:00:00">
<condition date-time="2010-10-01 00:00:01~2010-10-15 23:59:59">
Для даты оператор диапазона ~, а не -

Nested Conditions

Условия могут быть вложены одно в другое. Вложенные условия довольно сложны. Возможно лучше использовать скриптовые расширения диалплана, такие как Fs: mod_lua.

Когда (основное) условие имеет вложенные (условия), сначала оценивается его выражение и его (основные) действия добавляются в список подготовленный к выполнению. Затем оцениваются вложенные условия.

Каждое условие подразумевает параметр require-nested, который имеет значение по умолчанию - true.

Когда у нас есть условие с require-nested = "true", тогда ВСЕ его вложенные условия должны быть истинными ( т е. основное условие И все вложенные, должны быть истинными), чтобы основное условие было оценено как истинное и все действия выполнены.

Если require-nested = "false", достаточно, чтобы только основное выражение было истинным, тогда независимо от оценки вложенных условий, его действия будут выполнены. Действия же вложенных будут выполнены, если они истинны.

Краткое резюме

Основное условие истинно и require-nested="true":

Тогда оцениваются вложения и если они ВСЕ истинны, тогда выполняются действия в порядке перечисления. Если хоть одно из вложенных условий ложь, тогда ВСЕ условия считаются ложью и действия не выполняются.

Основное условие истинно, а require-nested="false":

Тогда действия вложенных условий выполняются в зависимости от того ложны они или нет, независимо от истинности друг друга.

Также учитываются значения параметра break.

Нажмите, чтобы отобразить

Нажмите, чтобы скрыть

When a (main) condition has nested conditions, first its (main) expression is evaluated, and its (main) actions are pushed in TODO list, then nested conditions are evaluated, and their actions pushed.

Each condition has an implied parameter «require-nested», that is by default set to «true».

When we have a condition with require-nested parameter set to «true», then ALL its nested conditions must evaluate to true (AND its expression must evaluate to true) for the condition to be evaluated to true.

If «require-nested» is set to false, then only the expression must evaluate to true for the condition to be true, whatever the nested conditions are.

Conditions are checked the usual way (respecting the «break» parameter), and actions inside conditions added to TODO list.

«Поиграйтесь» с приведенным ниже примером, устанавливая параметры в разные возможные значения. Это поможет лучше понять механизм работы вложенных условий.

<extension name="nested_example">
    <condition field="destination_number" expression="^2901$" require-nested="false">
        <action application="log" data="ERR 00 CIDnum is ${caller_id_number} CIDname is ${caller_id_name}" />
        <action application="set" data="var_01=N/A" inline="true"/>
        <action application="set" data="var_02=N/A" inline="true"/>
        <action application="set" data="var_03=N/A" inline="true"/>
        <action application="set" data="var_04=N/A" inline="true"/>
        <action application="set" data="var_05=N/A" inline="true"/>
        <action application="log" data="ERR 01 I'm before..."/>
        <action application="set" data="var_01=01" inline="true"/>
        <action application="log" data="ERR 02 I'm before  ${var_01} ${var_02} ${var_03} ${var_04} ${var_05}"/>
        <condition field="caller_id_number" expression="1011" break="on-false">
            <action application="log" data="ERR 03 I'm the first..."/>
            <action application="log" data="ERR 04 I'm the first CIDnum is ${caller_id_number}" />
            <action application="set" data="var_02=02" inline="true"/>
            <action application="log" data="ERR 05 I'm the first ${var_01} ${var_02} ${var_03} ${var_04} ${var_05}"/>
        </condition>
        <action application="log" data="ERR 06 I'm in between..."/>
        <action application="set" data="var_03=03" inline="true"/>
        <action application="log" data="ERR 07 I'm in between ${var_01} ${var_02} ${var_03} ${var_04} ${var_05}"/>
        <condition field="${caller_id_name}" expression="Giovanni" break="on-false">
            <action application="log" data="ERR 08 I'm the second..."/>
            <action application="log" data="ERR 09 I'm the second CIDname is ${caller_id_name}" />
            <action application="set" data="var_04=04" inline="true"/>
            <action application="log" data="ERR 10 I'm the second ${var_01} ${var_02} ${var_03} ${var_04} ${var_05}"/>
        </condition>
        <action application="log" data="ERR 11 I'm after..."/>
        <action application="set" data="var_05=05" inline="true"/>
        <action application="log" data="ERR 12 I'm after ${var_01} ${var_02} ${var_03} ${var_04} ${var_05}"/>
    </condition>
</extension>
<extension name="call_has_not_stopped_before_here">
    <condition field="destination_number" expression=".*">
        <action application="log" data="ERR NOT STOPPED BEFORE HERE"/>
    </condition>
</extension>

Значения которые вы можете изменить:

  • Выражения: 2901, 1011, Giovanni
  • В первом условии, require-nested может быть true или false
  • Параметр inline для действия set может быть установлен в true или false
  • Параметр break во вложенных условиях может принимать значения: on-false, on-true, never, always

require-nested="false"

"var_04=N/A" потому что отому что одно условие (caller_id_name) не совпало и переменной назначено значение "основного" условия:

Regex (PASS) [nested_example] destination_number(2901) =~ /^2901$/ break=on-false
Regex (PASS) [nested_example_recur_1] caller_id_number(1011) =~ /1011/ break=on-false
Regex (FAIL) [nested_example_recur_1] ${caller_id_name}(NOT_Giovanni) =~ /Giovanni/ break=on-false
[ERR] mod_dptools.c:1742 00 CIDnum is 1011 CIDname is NOT_Giovanni
[ERR] mod_dptools.c:1742 01 I'm before...
[ERR] mod_dptools.c:1742 02 I'm before  01 02 03 N/A 05
[ERR] mod_dptools.c:1742 06 I'm in between...
[ERR] mod_dptools.c:1742 07 I'm in between 01 02 03 N/A 05
[ERR] mod_dptools.c:1742 11 I'm after...
[ERR] mod_dptools.c:1742 12 I'm after 01 02 03 N/A 05
[ERR] mod_dptools.c:1742 03 I'm the first...
[ERR] mod_dptools.c:1742 04 I'm the first CIDnum is 1011
[ERR] mod_dptools.c:1742 05 I'm the first 01 02 03 N/A 05

Все условия совпали, переменным переназначены значения из вложенных условий:

Regex (PASS) [nested_example] destination_number(2901) =~ /^2901$/ break=on-false
Regex (PASS) [nested_example_recur_1] caller_id_number(1011) =~ /1011/ break=on-false
Regex (PASS) [nested_example_recur_1] ${caller_id_name}(Giovanni) =~ /Giovanni/ break=on-false
[ERR] mod_dptools.c:1742 00 CIDnum is 1011 CIDname is Giovanni
[ERR] mod_dptools.c:1742 01 I'm before...
[ERR] mod_dptools.c:1742 02 I'm before  01 02 03 04 05
[ERR] mod_dptools.c:1742 06 I'm in between...
[ERR] mod_dptools.c:1742 07 I'm in between 01 02 03 04 05
[ERR] mod_dptools.c:1742 11 I'm after...
[ERR] mod_dptools.c:1742 12 I'm after 01 02 03 04 05
[ERR] mod_dptools.c:1742 03 I'm the first...
[ERR] mod_dptools.c:1742 04 I'm the first CIDnum is 1011
[ERR] mod_dptools.c:1742 05 I'm the first 01 02 03 04 05

require-nested="true"

Действия не выполняются. потому что одно условие (caller_id_name) не совпало:

Regex (PASS) [nested_example] destination_number(2901) =~ /^2901$/ break=on-false
Regex (PASS) [nested_example_recur_1] caller_id_number(1011) =~ /1011/ break=on-false
Regex (FAIL) [nested_example_recur_1] ${caller_id_name}(NOT_Giovanni) =~ /Giovanni/ break=on-false

Advanced Condition/Action Rules

break="on-true"

Рассмотрим использование break="on-true" и break="on-false" на примере основанных на времени условий. Вызывается номер 1100, действующая служба поддержки работает с 8-ми до до 22 часов, за исключением пятницы, когда рабочий день с 8-ми до 13 часов. В любое другое время вызов отправляется на голосовую почту.

<extension name="Time-of-day">
<!--Если первоначальное условие ложно, будет выполнен переход к след. расширениям.-->
    <condition field="destination_number" expression="^1100$" break="on-false"/>
<!--on-true прекращает проверку остальных условий, если это истинно.-->
    <condition wday="6" hour="8-12" break="on-true">
<!--Fri, 8am-12:59pm 
I don't care if Monday's blue
Tuesday's grey and Wednesday too
Thursday I don't care about you
It's Friday, I'm in love-->
        <action application="transfer" data="1105 XML default"/>
    </condition>
    <condition wday="2-5" hour="8-21" break="on-true">
<!--Monday-Thursday, 8am-9:59pm-->
        <action application="transfer" data="1105 XML default"/>
    </condition>
    <condition>
<!--последнее действие поймает все, что не попало в указанное время и отправит на голосовую почту. -->
        <action application="voicemail" data="default ${domain} 1105"/>
    </condition>
</extension>

Состояние break="on-true" прекращает проверку других условий, если его условие истинно.
По умолчанию, если не указано другое, подразумевается поведение break="on-false".

break="never"

Состояние break="never" указывает всегда рассматривать последующие условия данного расширения, даже если условие ложно. Это может быть использовано для установки меток в процессе обслуживания вызова. В примере назначается переменная begins_with_one, если вызываемый номер начинается с «1».

<extension name="break-demo">
    <condition field="destination_number" expression="^1(\d+)$" break="never">
        <action application="set" data="begins_with_one=true"/>
    </condition>
    <condition field="destination_number" expression="^(\d+)$">
     ...other actions that may query begins_with_one...
    </condition>
</extension>

Asterisk Pattern Matching

В дополнение к PCRE Freeswitch также поддерживает паттерны Asterisk. Выражения должны начинаться с нижнего подчеркивания _.
См. также: https://freeswitch.org/confluence/display/FREESWITCH/mod_dialplan_asterisk
Звездочка * должна экранироваться \ (смотрите в примере).

<extension name="US-Domestic">
    <condition field="destination_number" expression="_(NXXXXXXXXX)">
    <action application="bridge" data="sofia/internal/$1@example.com"/>
    </condition>
</extension>
 
<extension name="star-code-using-escape">
    <condition field="destination_number" expression="_(\*XX)(.)">
        <action application="log" data="ERR captured $1 ~~~ $2"/>
        <action application="answer"/>
        <action application="playback" data="tone_stream://path=${base_dir}/conf/tetris.ttml;loops=10"/>
    </condition>
</extension>

FS XML Dialplan Example Library

  • freeswitch/dp/fs_xml_dialplan.txt
  • Последние изменения: 2019/09/15