В последнее время наблюдается повышенный интерес к Voip программе Freepbx. Это объясняется повышенной стабильностью, большим набором возможностей, открытостью кода и хорошими темпами развития. Однако по распространению на территории России эта программа на мой взгляд значительно отстает от другого представителя данного класса программ - Asterisk. В значительной мере это связано с отсуствием рускоязычной документации на данную программу. Мы решили упростить изучение Freeswitch и начать процесс перевода официальной документации на русский язык.

Весь перевод мы будем публиковать в отдельном разделе доступном по ссылке http://garantum.ru/content/category/8-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 для сохранения записей в массивы.]

© Внимание! Все права на перевод принадлежат фирме Гарантум. При ссылке или цитировании данной информации обязательно вставляйте ссылку на источник.