Описание Dialplan Freeswitch (Часть первая)
В последнее время наблюдается повышенный интерес к Voip программе Freepbx. Это объясняется повышенной стабильностью, большим набором возможностей, открытостью кода и хорошими темпами развития. Однако по распространению на территории России эта программа на мой взгляд значительно отстает от другого представителя данного класса программ - Asterisk. В значительной мере это связано с отсуствием рускоязычной документации на данную программу. Мы решили упростить изучение Freeswitch и начать процесс перевода официальной документации на русский язык.
Весь перевод мы будем публиковать в отдельном разделе доступном по ссылке Раздел Freeswitch
Структура каталогов конфигурации Диалплана Freeswitch (Configuration Directory Structure)
В конфигурации FreeSwitch планы набора (dialplans) хранятся в каталоге conf/dialplans, внутри которого для каждого из контекстов предназначен отдельный подкаталог. Это рекомендуемая конфигурация. Например, данный экземпляр структуры содержит диалплан для простой IP-АТС:
freeswitch/
conf/
dialplans/
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
...
Несколько деталей на заметку:
- Структура этого диалплана похожа на образец конфигурации, который идёт с FreeSWITCH по умолчанию. Он является хорошей отправной точкой, чтобы настроить собственную IP-АТС, как мы здесь и сделали.
- Любой xml-файл, помещеный в каталог dialplans, будет подхватываться при старте FreeSWITCH. Это не чудо, а обычный процесс загрузки конфигурации по умолчанию.
- public.xml содержит всю общую информацию о конфигурации для публичного контекста, включая все файлы XML из публичного каталога.
- default.xml содержит всю общую информацию о конфигурации для контекста по умолчанию, включая все файлы XML из каталога по умолчанию.
- Файлы включаются в алфавитном порядке, так что хорошей практикой является начинать имя файла с цифры, чтобы обеспечить обработку частей вашего диалплана в правильном порядке.
Анатомия XML Диалплана
Для создания XML-диалплана используется несколько элементов. Обычно в диалплане логически схожие функции и действия группируются в 'context'. В состав контекста входят направления 'extensions', условия 'conditions' для каждого из них, а также ассоциированные действия 'actions', которые выполняются при совпадении условий.
Ниже приведен пример диалплана, чтобы проиллюстрировать эти понятия. "Обёртка" XML была удалена, чтобы сделать основные понятия более наглядными:
<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>
Каждое из правил поочерёдно обрабатывается до тех пор, пока не обнаруживается тег, дающий FreeSWITCH команду на выполнение нужного действия. При этом вы не ограничены каким-то одним условием или тегом действия для данного направления.
В примере выше звонок на направление с номером 501 закольцовывает процесс. Если пользователь не отвечает, второе действие принимает вызов, а следующие действия после задержки в 1000 миллисекунд (1 секунду) соединяют звонящего с системой голосовой почты.
Контекст (Context)
Контексты — это логическое группирование направлений. В одном контексте может содержаться множество направлений.
Тег контекста имеет обязательный параметр 'name' — имя. Существует одно зарезервированное имя, 'any', соответствующее одному любому контексту. Имя используется обработчиками входящих вызовов (такими как [Sofia] SIP драйвер) для выбора диалплана, срабатывающего при необходимости маршрутизации вызова. В диалплане часто имеется более одного контекста.
Полное определение контекста приведено ниже. Скорей всего вам не понадобятся все участки кода, но здесь они приводятся для полноты.
<?xml version="1.0"?>
<document type="freeswitch/xml">
<section name="dialplan" description="Regex/XML Dialplan">
<!-- контекст по умолчанию для безопасного старта -->
<context name="default">
<!-- один или несколько тегов направлений -->
</context>
<!-- дополнительные контексты -->
</section>
</document>
Направление (Extensions)
Extensions — это направления вызова. Они являются основой системы маршрутизации набранных номеров во FreeSWITCH. Направления с присвоенными им именами содержат группы условий, при совпадении которых выполняются определенные действия.
Параметр 'name' является обязательным: это должно быть уникальное имя, присвоенное направлению для идентификации и последующего использования. Например:
<extension name="Your extension name here">
<condition(s).../>
<action(s) />
</condition>
</extension>
ПРИМЕЧАНИЕ: Как правило, когда направление совпадает с вашим диалпланом, выполняются соответствующие действия и процесс останавливается. Дополнительный параметр 'continue' позволяет диалплану продолжать работать.
<extension name="500" continue="true">
Условия (Conditions)
Условия в диалпланах обычно используются для согласования номера абонента с одним из направлений. Однако у них гораздо больше возможностей, чем может показаться на первый взгляд.
Во FreeSWITCH имеется набор встроенных переменных, используемых для тестирования. В этом примере встроенная переменная destination_number сравнивается с регулярным выражением ^500$. Данное сравнение является "истинным", если <destination_number> имеет значение 500.
<extension name="500">
<condition field="destination_number" expression="^500$">
<action application="bridge" data="user/500"/>
</condition>
</extension>
Каждое условие анализируется с помощью библиотеки Perl Compatible Regular Expression.
Если регулярное выражение содержит какие-либо условия, заключённые в скобки, и это выражение выполняется, переменным $1,$2...$N будут присвоены значения соответствующего содержимого скобок, и в дальнейшем эти переменные могут быть использованы внутри тегов действий в пределах этого блока направления. В настоящее время доступно не более девяти переменных $.
Например, это простое выражение проверяет четырехзначный номер, и последние две цифры заносит в переменную $1.
<condition field="destination_number" expression="^dd(dd)$">
<action application="bridge" data="sofia/internal/$1@example.com"/>
</condition>
Для номера 3425 в переменную $1 заносится число 25, а затем устанавливается соединение с 25@example.com.
Если после переменной $N следует число, то переменная может быть заключена в скобки: ${N}. Например:
<condition field="destination_number" expression="^dd(dd)$">
<action application="bridge" data="sofia/internal/${1}0@example.com"/>
</condition>
Множественное сравнение (Multiple Conditions) (Логическое И (AND))
Вы можете эмулировать операции с логическим И (AND), доступные во многих языках программирования, используя множественные условия.
При помещении в направление более одного условия, все условия должны быть проверены до того, как будут производиться действия. Например, этот блок будет выполнять действия, только если номер назначения — 500 И день недели — воскресенье.
<condition field="destination_number" expression="^500$"/>
<condition wday="1">
action(s)
</condition>
Имейте в виду, что вы должны следить за правильностью синтаксиса XML при использовании этой структуры. Убедитесь, что все условия, кроме последнего, закрыты с помощью />. Последнее условие содержит финальные действия, которые необходимо выполнить, и закрывается на строке после последнего действия.
По умолчанию, если любое условие ложно, FreeSWITCH переходит к анти-действию или следующему направлению не оценивая остальные условия.
Вложенное сравнение (Nested Conditions).
Недавно добавлено. Смотрите http://jira.freeswitch.org/browse/FS-4935. Новый атрибут вложенных условий — "require-nested", который может быть истинным или ложным, и указывает, должны ли вложенные условия принять положительные значения для выполнения других действий в родительском условии, где находится данный атрибут.
Поэтому, если "require-nested" имеет значение true, которое являтеся дефолтным, даже если это специально не указано, то вложенное условие (sub-condition) должно вернуть значение true или иметь параметр break=never точно так же, как и родительские направления. Обычно каждый уровень вложенных условий анализируется тем же методом, что и направление, только вместо продолжения используется алгоритм "require-nested", описанный выше.
Если значение "require-nested" ложно, то независимо от того, что происходит во вложенном условии, даже при полном его отказе, процесс переходит к следующему действию в родительском условии.
С помощью этого сочетания алгоритмов вы сможете выполнить одно главное условие с "require-nested=false", а затем серию вложенных условий, в каждом из которых выполняется логическое И.
<extension name="nested_example">
<condition field="destination_number" expression="^1234$" require-nested="false">
<condition field="f1" expression="e1"/>
<condition field="f2" expression="e2"/>
<condition field="f3" expression="e3"/>
<condition field="destination_number" expression="3" break="never">
<action application="playback" data="foo.wav" />
</condition>
<action application="playback" data="bar.wav" />
</condition>
</extension>
Вложенное сравнение. Предостережение и примечания.
Первое, что нужно иметь в виду: если в процессе анализа находится условие с вложенными дочерними условиями, всегда сначала будут анализироваться дочерние условия! Это означает, что вы не можете сделать так:
<extension name="nested_example">
<condition field="destination_number" expression="^1234$" require-nested="false">
<action application="log" data="INFO I'm before the nested conditions..."/>
<condition field="${foo1}" expression="bar1" break="never">
<action application="log" data="INFO foo1 is bar1" />
</condition>
<action application="log" data="INFO I'm in between the nested conditions..."/>
<condition field="${foo2}" expression="bar2" break="never">
<action application="log" data="INFO foo2 is bar2" />
</condition>
<action application="log" data="INFO I'm after the nested conditions..."/>
Это не даст того результата, которого вы ожидаете. Вместо этого линии будут выполняться в следующем порядке:
<action application="log" data="INFO foo1 is bar1" />
<action application="log" data="INFO foo2 is bar2" />
<action application="log" data="INFO I'm before the nested conditions..."/>
<action application="log" data="INFO I'm in between the nested conditions..."/>
<action application="log" data="INFO I'm after the nested conditions..."/>
Итак, запомните: в первую очередь подчиненные условия! Их действия и анти-действия будут обрабатываться и добавляться в список задач раньше родительских.
Ещё одно важное примечание — захват в переменные $1, $2 и т. д. работает:
<extension name="Local_Extension">
<condition field="destination_number" expression="^($${extension_regex})$" require-nested="false">
<condition field="caller_id_number" expression="^(.*)$">
<action application="log" data="WARNING Inside nested condition, CID Num is $1"/>
</condition>
<action application="log" data="NOTICE Can you see me?"/>
<condition field="caller_id_name" expression="^(.*)$">
<action application="log" data="WARNING Inside nested condition, CID Name is $1"/>
</condition>
<action application="log" data="WARNING dest num is $1"/>
...
Приведенный выше пример будет нормально функционировать. Держите эти два правила в голове, и вы сможете делать много интересных вещей с вашими диалпланами!
Множественное сравнение (Multiple Conditions) (Логическое OR, XOR))
Возможно также эмулирование операций с логическим OR (ИЛИ), доступных во многих языках программирования, используя множественные условия. В этой ситуации, если хотя бы одно из условий выполняется, то действия будут осуществлены.
Например, в этом блоке действия выполнятся, если номер назначения — 501 ИЛИ 502.
<condition field="destination_number" expression="^501|502$">
action(s)...
</condition>
Этот метод хорошо работает, если ваше условие OR находится в той же области. Однако если вам необходимо использовать несколько различных областей, то применяйте новый синтаксис regex, добавленный 1 ноября 2011. Он выглядит так:
<condition regex="all|any|xor">
<regex field="some_field" expression="Some Value"/>
<regex field="another_field" expression="^Anothers*Value$"/>
<action(s) ...>
<anti-action(s)...>
</condition>
Условие regex может иметь одно из трех значений:
*all — эквивалент операции с логическим AND (И). Все выражения, содержащиеся в условии должны быть истинными, чтобы действие осуществилось.
*any — эквивалент операции с логическим OR (ИЛИ). Любое из выражений, содержащихся в условии может быть истинным, чтобы действие осуществилось.
*xor — эквивалент операции с логическим XOR. Только одно из выражений может быть истинным, чтобы действие осуществилось.
Действия и анти-действия осуществляются так же, как и в стандартном блоке условия. Например, этот блок проверяет ID и номер звонящего, после чего выполняет действия, если находит имя "Some User" OR номер 1001:
<extension name="Regex OR example 1" continue="true">
<condition regex="any">
<!-- Если что-нибудь из этого верно, то последующие действия добавляются в список выполнения -->
<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!"/>
<!-- Если *никакой* из этих regex не верен, то анти-действия добавляются в список выполнения -->
<anti-action application="log" data="WARNING None of the conditions matched!"/>
</condition>
</extension>
Этот метод облегчает проверку имени абонента OR номера его ID и выполняет действия, если что-либо из этого истинно.
Чуть более продвинутый вариант использования этого метода показан ниже. Если имя абонента — "Michael S Collins" OR ID абонента — 1002, 3757, или 2816, то переменная calling_user принимает значение "mercutioviz". Если ни одно условие не истинно, то переменная calling_user принимает значение "loser". После воспроизведения приветственного сообщения воспроизводится пользовательское сообщение, основанное на переменной calling_user.
<extension name="Regex OR example 2" continue="true">
<condition regex="any" break="never">
<regex field="caller_id_name" expression="^Michaels*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>
В следующем примере показано, как создать блок с условием XOR (исключающее ИЛИ), который является справедливым, если только одно из условий возвращает значение true.
<extension name="Regex XOR example 3" continue="true">
<condition regex="xor">
<!-- Если только что-то одно из этого верно, то последующие действия добавляются в список выполнения -->
<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!"/>
<!-- Если *никакой* из этих regex не верен, то анти-действия добавляются в список выполнения -->
<anti-action application="log" data="WARNING None of the conditions matched!"/>
</condition>
</extension>
Другой пример показывает, как убедиться в соответствии всех выражений, прежде чем будут выполняться действия, в обратном случае будут выполнены анти-действия. В этом случае SIP-шлюз должен принадлежать провайдеру по умолчанию, вызов должен быть экстренным и функция автоответчика должна быть включена с сохранением в базе данных:
<condition regex="all">
<regex field="${sip_gateway}" expression="^${default_provider}$"/>
<regex field="${emergency_call}" expression="^true$"/>
<regex field="${db(select/emergency/autoanswer)}" expression="^1$"/>
<!-- следующие действия выполняются, если проходят все выражения regex -->
<action application="set" data="call_timeout=60"/>
<action application="set" data="effective_caller_id_name=${regex(${caller_id_name}|^Emerg(_.*)$|Auto%1)}"/>
<action application="set" data="autoanswered=true"/>
<action application="bridge" data="user/1000@${domain_name},sofia/gateway/1006_7217/${mobile_number}"/>
<!-- следующие анти-действия выполняются, если какое-либо из выражений regex не проходит -->
<anti-action application="set" data="effective_caller_id_name=${regex(${caller_id_name}|^Emerg(_.*)$|NotAuto%1)}"/>
<anti-action application="set" data="call_timeout=30"/>
<anti-action application="set" data="autoanswered=false"/>
<anti-action application="bridge" data="user/1000@${domain_name},sofia/gateway/1006_7217/${mobile_number}"/>
</condition>
Примечание: Если записывать данные в любое из регулярных выражений с использованием операторов ( ), в любых последующих действиях при расширении выражений (с использованием синтаксиса $N) будут >учитываться только данные из последней обнаруженной записи. [Что это значит?: Вы можете дополнительно установить DP_REGEX_MATCH_1 .. DP_REGEX_MATCH_N для сохранения записей в массивы.]
© Внимание! Все права на перевод принадлежат фирме Гарантум. При ссылке или цитировании данной информации обязательно вставляйте ссылку на источник.