第六章 XML 拨号方案

       XML拨号方案是FreeSWITCH部署的心脏。对于许多来自不同行业背景的电信老鸟来说,这可能会让人感到困惑,甚至让人感到恐惧。事实上,这是非常简单与合乎逻辑的。你只需要以学习新事物的平常的心态对待就行。

        让我们消除迷信吧:

  1. XML根本没什么难度,它读起来就像纯文本配置文件一样
  2. 它不需要什么特别的编辑器,普通的文本编译器都适用:Notepad、Vim、Emacs、 Nano,能编辑文本就行
  3. 它不深奥,它是逻辑结构的文本

 

        这一章,我们甚至不会谈及XML本身,因为根本没必要。相反,我们将深入研究拨号方案的结构:根据其特点,一路呼叫将降落到拨号方案的某个特定"context"。"context"就像完全独立分离的“牢笼”,它们是拨号方案的“虚拟机”或“砂箱”。你可以用多个"context"实现多租户。

        每个context包含一些extension,进入condition会检查一些条件,根据检查结果决定是不是要执行相应的action。

        我们将研究拨号方案的多流控制:extension的"continue"属性和"break"属性。

        我们将回顾"call leg",以及A-leg和B-leg相关的有趣术语。

        最后,我们将讨论如何解释演示配置拨号方案,如何编写新的extension,以及基础FreeSWITCH拨号方案的所有构建块(APP、拨号串等)。

忘记你所知道的一切

        以下是拨号方案相关的常规概念:

       ●  拨号方案决定FreeSWITCH将如何处理收到的音视频呼叫。

扫描二维码关注公众号,回复: 11315965 查看本文章

       ●  拨号方案是一个不相关的模式的列表

       ●  每个单独的模式都拥有它自己的操作(action)列表

       ●  处理呼叫时,拨号方案的入口是它的开始处,沿着拨号方案的结束的方向进行

       ●  呼叫会从头到尾按顺序尝试匹配每个模式

       ●  如果呼叫与某个模式“匹配”成功,那么这个模式下的所有action都会被添加到这个呼叫的TODO 列表

       ●  在拨号方案遍历结束时,会执行TODO列表

       ●  单个模式匹配成功与否,可能导致拨号方案遍历过程的中止(换句话说,不继续尝试匹配余下的模式)

       ●  在 FreeSWITCH 的术语中,"EXTENSION"设计了一个独立的模式,它有一套自己的ACTION

       ●  独立模式实例: (呼叫的目的号码介于1000 和 1019之间,并且号叫时间是在早晨 10点到11点之间);(呼叫来自这个网段:192.168.1.0/24);(呼叫不是来自这个网段: 192.168.1.0/24); (呼叫来自一个特定的 DID);(呼叫来自Firefox 浏览器的WebRTC,并且目的号码是852); (呼叫来自一部内部的SIP话机)

       ●  action列表实例: (摘机应答,向呼叫者播放一个音频文件,挂机); (摘机应答,向呼叫者播放一个音频文件,监听呼叫都的DTMF按键,响应DTMF事件:查询数据库并根据返回播放不同的音频文件,挂机); (摘机应答,监测传真音,如果收到传真则接收,否则转接给接线员);(接机应答,把呼叫者拉到一个会议室中)

 

Context

        拨号方案可以划分为几个不同的部分,每个部分为一个context,它们彼此完全独立。基于这种分离设计,context可用于实现多租户系统(一台FreeSWITCH同时服务于公司A与公司B,不需要担心分机冲突)。

        根据呼叫的来处(哪个协议、网络接口或端口),或者根据发起用户的明确“context属性值”,接收到的呼叫将被“发送”到特定的context。context结束时,呼叫也就退出拨号方案了。

        Context的独立性是如此之强,以至于人们在讨论不同的拨号方案时,往往实际上指的是不同的context正式说来,拨号方案只有一份:XML拨号方案。出于实际目的,你可以把不同context视为不同拨号方案,这完全没关系,我们都能接受。

       在新部署的FreeSWITCH所携带的演示配置实例中,定义了三个context(只要你想,可以配置任意多个)

●  default: 处理内部信任用户所发起的呼叫

●  public:处理其它非系统用户发起的呼叫

●  features: 呼叫不会直接发到这里,但其它两个context会从这里“借用”一些特性

 

Default context

        “default context”是演示配置的主力军:所有内部话机发起的话务都发给"default" context处理。它是演示配置中最复杂的context,配置了非常多的extension。

        它的大部分extension(后面我们会解释FreeSWITCH里术语"extension"的准确含义)都不是呼叫的“最终目的地”,而只是根据是否匹配某些条件,把一些变量附加给呼叫描述。其中一些extension接入到某种服务(IVR、语音信箱、会议等)。default context里只有一个extension是用于桥接内部话机的,也就是传统意义上分机的概念。

        演示配置中,XML拨号方案的default context定义在/usr/local/freeswitch/conf/dialplan/default.xml file文件,它包含了/usr/local/freeswitch/conf/dialplan/default/ directory目录下的所有XML文件。

Public context

        在演示配置拨号方案中,"public" context处理所有"外部"发起的呼叫,包括未授权用户的呼叫。这个context很简单,只包含两个extension。未授权用户和外部呼叫可以做什么,能够访问什么服务,这些完全受限(实际上是零)。

        第一个extension在FreeSWITCH控制台上打印一些呼叫相关的调试信息,而第二个extension把话务转接给default context的一个内部话机。

        演示配置拨号方案的public context定义在/usr/local/freeswitch/conf/dialplan/public.xml文件中,它包含/usr/local/freeswitch/conf/dialplan/public/目录下的所有XML文件。DID通常在这里管理,来电时,拨号方案里的extension与ITSP提供的电话号码做模式匹配,进而决定路由,这些规则都可以在/usr/local/freeswitch/conf/dialplan/public/中扩展。比如:其中的一个XML文件,处理从我们的ITSP发过来的话务,拨打+1212555444将会被桥接到Sara注册的内部话机;而1212555445则会被引导到我们的IVR(“您好,欢迎来到FreeSWITCH世界,您的呼叫对我们非常重要”,等等)。

 

Features

        呼叫不会直接路由给演示配置的"features" context。相反,features context中所列的extension可以被其它context“执行”。

        我们可以所features context想像成一个仓库,存储可用特性的仓库。

        例如:演示配置中,features context提供了一个extension可以模拟“呼叫转移”功能,即使话机终端没有“呼叫转移”功能键,我们也可以利用它发起转移。

        演示配置中,features context定义在/usr/local/freeswitch/conf/dialplan/features.xml文件中。

 

Extension

        FreeSWITCH术语中,extension不是一个话机,也不是一种服务。一个Extension是一个模式(标准)和一组操作列表的配置组合。

        接收到的呼叫在拨号方案中一个接一个地遍历extension。每路呼叫都有许多特征(目标号码、一天中的时间、传输的方式,等等)。每个extension中,这些特征(由“变量”表述)都可以根据模式进行检查。如果模式与特征“匹配”,它所指定的action列表将会被添加到呼叫的TODO列表中。拨号方案遍历完之后,TODO列表中堆积的action将会被逐个执行。

        Extension被包含在XML标记<extension>和</extension>之间。Extension可以有两个属性:name continue

        属性"name"除了调试输出信息之外,没有任何用处。它只是个名字,能帮你了解事情发生的经过。

        属性"continue"是最重要的。它决定了如果这个extension的模式匹配成功,在把它的action添加到TODO列表中之后,是继续匹配下一个extension还是立刻退出拨号方案(马上开始执行TODO列表中的指令)。

        缺省情况下,呼叫找到第一个匹配的extension之后就会退出拨号方案。如果你这样设置:<extension name="myextensionname" continue="true">,那么呼叫在成功匹配这个extension之后,还会继续匹配下一个。

 

Condition (和 "模式")

        一般而言,Condition就是前面几章我们所说的“模式”的构建块。

        Condition包含在"extension"中,它包含"action"。Condition通常看起来是这样的:

<extension name="this_extension">

<condition field="destination_number" expression=""^1234$">

<!-- action -->

<!-- action -->

</condition>

</extension>

 

        如上所示,在一个拨号方案的"context"(演示配置的"default"或"public")内,我们会看到"extension"的的起始标记,紧接着是condition开始标记,最后是condition结束标记和extension结束标记,在开始标记和结束标记之间,是一系列的action描述。

        在上面实例中,condition检查"destination_number"变量值是否与正则表达式"^1234$"匹配。如果拨打的号码是1234,那么,condition起始标记和结束标记间的所有action都会被添加到TODO列表中(结束拨号方案遍历之后,TODO列表中的所有action都会被执行)。

<extension name="that_extension">

<condition field="ani" expression=""^5551212$"/>

<condition field="destination_number" expression=""^1234$">

<!-- action -->

<!-- action -->

</condition>

</extension>

 

        这个实例中,"that_extension"实际上有两条condition,一条挨着一条(条件叠加)。第一条condition的起始标记和结束标记间没有指定"action"(XML中,结束标记是由斜杠"/"引导的)。

        它的含义是:首先检查"ani"变量(主叫号码)是不是5551212。如果是,接着检查第二个条件:目标号码是不是1234。如果是,则把action添加到TODO列表中。

        条件叠加是逻辑与关系,它们必须全为真值,才能执行指定的action。这是因为condition有一个隐含属性:"break='on-false'"。

<extension name="other_extension">

<condition field="ani" expression=""^5551212$" break="on-true"/>

<condition field="destination_number" expression=""^1234$">

<!-- action -->

<!-- action -->

</condition>

</extension>

 

        在"other_extension"示例中,我们要了解"break"属性的工作。这里,如果表达式值为真,那么中断条件判断流。因此,在这个示例中,如果主叫号码不是5551212,那么需要判断第二个条件。

        下面是"break"属性的可选值(它的含义是跳出条件判断逻辑,跳出extension):

        ● on-false: 这是缺省值,如果表达式值为 false ,不再检查下一个条件

        ● on-true: 如果表达式值为 true ,不再检查下一个条件

        ● never: 永远不跳出,继续检查后续条件

        ● always: 不检查后续条件,直接跳出

 

Call leg (通道)

        每个行业都有自己的术语和一些神奇的词汇。在电信行业中,无论采用什么底层技术(SIP、 WebRTC、 TDM),你都会经常碰到这两个词汇:"call legs"和"channel(通道)"。

        第一个令人困惑的事实:每个"call leg"实际上都是一个单独的呼叫。也就是说:通常说一通呼叫由一个"A-leg"和一个"B-leg"组成。实际上,"A-leg" 和 "B-leg"才是真正的呼叫。

        第二个令人困惑的事实:每个leg都是一个通道。因此,大部分的“通话”都由两个通道(A-leg 和 B-leg)组成,而有些“通话”(如连接IVR、语音信箱)却只有一个通道(A-leg)。

        这个有趣的术语形成的原因是:当人们讨论“一路呼叫”时,他们通常指的是从主叫到被叫的端到端(end-to-end)的语音或视频连接。此外,更重要的是,呼叫是以这种方式计费的,在主叫和被叫间建立一个完整的“电路”。当你在这样一个完整的呼叫电路中间插入一个服务器时(比方说FreeSWITCH),它实际上被拆分成两个完全独立的呼叫(也称为通道):一个是从主叫到FreeSWITCH的,另一个是从FreeSWITCH到被叫的。为了更好地理解这一点,请考虑一下当您提取语音留言时的情景:呼叫“电路”只在你和语音信箱服务器间建立,它只是呼叫的"A-leg"(这时没有"B-leg"参与)。从中间服务器(FreeSWITCH)的角度看来,"A-leg"是它收到的,由主叫发起的呼叫。

        在接收到一个呼叫之后,FreeSWITCH会根据拨号方案发起另一个call leg(事实上,就是创建另一条独立的通道)。有些时候,拨号方案也可能同时发起多个B-leg(比如,你可以同时呼叫某个人的手机、办公桌面电话和家庭电话)。这些由FreeSWITCH新发起的呼叫,都是B-leg

        考虑这样一种情况:FreeSWITCH收到一路呼叫,它在FreeSWITCH中被分配为一个通道,这个通道称为A-leg。让我们假设呼叫的目标号码是1010,在演示配置拨号方案中,它匹配"Local_Extension"的定义。这时候,FreeSWITCH向每个注册"1010"的终端都发起一路呼叫(每路分配一个通道)。这些呼叫就是B-leg/通道。如果其中一个终端应答了,FreeSWITCH会桥接两个通道(主叫通道和被通道)的媒体流,然后双方用户就可以彼此交谈了。现在,这个完整的“呼叫”实际上是由两个独立的呼叫(通道)组成的:A-leg(从主叫到FreeSWTICT的呼叫)和B-leg (从FreeSWITCH到被)。如果始终没人应答,超时之后,FreeSWITCH所发起的B-leg就会消亡(被FreeSWITCH取消)。然后A-leg将被接到语音信箱系统。这时,这个完整的“呼叫”是仅由一个通道组成的,那就是从主叫到FreeSWITCH间的A-leg。

 

通道变量

        FreeSWITCH的每个独立通道(呼叫),都会关联许多特征和值,我们称之为“通道变量”。

        你可以利用变量获取通道内部信息并控制通道的行为。

        有些通道变量在通道分配时赋值。有些通道变量值在通道生命周期中会改变。有些变量是只读的,有些是可写的,这意味着我们可以修改变量值。你可以通过拨号方案或脚本在通道中创建变量。一些特定的通道变量值会改变通道的行为,而你就可以通过写("set")变量值来控制通道行为。在处理呼叫时,已经自动设置了数量惊人的通道变量。

        通过SSH连接带演示配置的FreeSWITCH服务器,执行/usr/local/freeswitch/bin/fs_cli,然后呼叫9192:

<extension name="show_info">

<condition field="destination_number" expression="^9192$">

<action application="answer"/>

<action application="info"/>

<action application="sleep" data="250"/>

<action application="hangup"/>

</condition>

</extension>

 

        这个extension很简单,它检查destination_number变量,如果匹配9192,就发应答消息,然后执行"info" app,然后休眠四分之一秒,最后挂机。正如我们将在本章后面看到的:"info" app将在控制台上转储每个通道变量和它的值。这是一个富贵的调试工具!

下        面是将在终端上以绿色字符输出的内容摘录(事实上这只是其中的一小部分。你应该亲自尝试一下!):

 

2017-05-25 17:34:15.625917 [INFO] mod_dptools.c:1743 CHANNEL_DATA:

Channel-State: [CS_EXECUTE] Channel-Call-State: [ACTIVE] Channel-State-Number: [4]

Channel-Name: [sofia/internal/[email protected]]

...

variable_direction: [inbound]

variable_uuid: [12cd1ed7-d04a-40a8-b1b5-495af077c69c] variable_session_id: [2]

variable_sip_from_user: [1011]

variable_sip_from_uri: [[email protected]]

...

variable_accountcode: [1011] variable_user_context: [default]

variable_effective_caller_id_name: [Extension 1011]

variable_effective_caller_id_number: [1011] variable_outbound_caller_id_name: [FreeSWITCH] variable_outbound_caller_id_number: [0000000000] variable_callgroup: [techsupport] variable_user_name: [1011]

variable_domain_name: [lab.opentelecomsolutions.com]

...

variable_endpoint_disposition: [ANSWER] variable_current_application: [info]

 

        所有以"variable_"前缀打头的行,都可以在拨号方案中以${}结构访问。比如,上面输出中的最后一行:"variable_current_application: [info]",它表示通道变量"${current_application}"当前的值是"info"。我们可以写一个新的extension来示范一下:

<extension name="giovanni_01">

<condition field="destination_number" expression="^290864$">

<action application="answer"/>

<action application="log" data="WARNING the value of current_application channel variable is: ${current_application}"/>

<action application="hangup"/>

</condition>

</extension>

 

        我们把新extension插在/usr/local/freeswitch/conf/dialplan/default.xml 文件的头部,放在下面内容之后:

<include>

<context name="default">

 

        连接fs_cli,执行"fsctl loglevel 5"命令,然后再执行"reloadxml"命令。现在,呼叫290864看看(1964年8月29日是作者的生日)。你的终端将会看到类似内容:

 

        现在,让我们先设一个变量,再把它读取回来,添加三行:

<extension name="giovanni_02">

<condition field="destination_number" expression="^290864$">

<action application="answer"/>

<action application="log" data="WARNING the value of current_application channel variable is: ${current_application}"/>

<action application="log" data="WARNING the value of giovanni_nice_guy channel variable is: ||${giovanni_nice_guy}||"/>

<action application="set" data="giovanni_nice_guy=verymuch"/>

<action application="log" data="WARNING the value of giovanni_nice_guy channel variable is: ||${giovanni_nice_guy}||"/>

<action application="hangup"/>

</condition>

</extension>

 

 

        在控制台执行reloadxml,然后再次呼叫290864:

 

        从输出中我们看到:变量${giovanni_nice_guy}在赋值("set"调用)之前是不存在的。

        更多关于通道变量的深入内容、它们的特性、它们控制FreeSWITCH的用法,以及更多信息,请参阅第9章“深层拨号方案

 

正则表达式

        正则表达式是拨号方案的心脏,FreeSWITCH的其它配置,也经常用到。它是一种分析、切片、揉合文本字符串的超级智能方式。在FreeSWITCH中,我们使用最优秀、最聪明、最伟大的正则表达式----与Perl兼容的正则表达式(PCRE)。这不是很漂亮吗?是的。

       正则表达式检查字符串是否匹配模式。正则表达式还可以用其他内容替换字符串的某些部分。此外,正则表达式可以“选择”字符串的一部分,并在返回结果时复用它。

        在拨号方案中,用正则表达式定义"condition"中的"expression"限定域。FreeSWITCH演示拨号方案中,最常用的配置结构是这样的:

<extension name="giovanni_03">

<condition field="destination_number" expression=""^(1234)$">

<action application="log" data="WARNING this is $1"/>

</condition>

</extension>

        如果我们把这个extension插入到演示配置示例中default.xml的头部,重载配置后呼叫1234,那么条件正好匹配:通道变量destination_number值1234正则表达式"^(1234)$"匹配。这个表达式以插入符(^)打头;它表示“字符串开始”,以美元符($),它表示字符串结束;这两个标记符之间,用圆括符括起一些其它字符。圆括符会“抓取”它包含的内容,并把它放在一个“寄存器”里。在本例中,文本“1234”被放在寄存器“$1”中(如果有多对圆括符,那么每二对里的内容放在寄存器“$2”中,以此类推)。

        让我们看一个复杂一点的正则表达式用法:

<extension name="giovanni_04">

<condition field="destination_number" expression="^1800(\d{3})(\d{2}).*$">

<action application="answer"/>

<action application="log" data="WARNING this is $1 and this is

$2"/>

<action application="hangup"/>

</condition>

</extension>

 

        把这个extension插入到default.xml文件头部,执行reloadxml命令,然后呼叫18009876543,你将会在控制台看到类似以下的输出:

        解读条件匹配的表达式"^1800(\d{3})(\d{2}).*$":这个语句检查destination_number的值,以1800打头,紧跟着三位数字,然后再跟着两位数字,再后面接着任意的字符串。跟在1800后面的三位数存入寄存器$1(第一对圆括符),再后面的两位(第8第9位)存入突破口$2。寄存器内容的作用域仅限于当前condition的开始标记到结束标记之间。

以下是一些正则表达式的释义:

模式

含义

123

匹配包含序列“123”的任何字符串

^123

匹配以序列"123"打头的任何字符串

123$

匹配以序列"123"结尾的任何字符串

^123$

匹配与序列“123”完全相同的字符串

\d

匹配任何单个数字(0-9)

\d\d

匹配两个连续数字

^\d\d\d$

匹配长度正好是三位的数字字符串

^\d{7}$

匹配长度正好是七位的数字字符串

^(\d{7})$

匹配长度正好是七位的数字字符串,并把匹配的值存入变量$1

^1?(\d{10})$

匹配可能以数字"1" 打头,并跟着10位长度的数字字符串,并把后10位数字存入变量$1。(原字符串可能是10位或以1打头的11位数字)。

^(3\d\d\d)$

匹配以3打头的任何四位长度的数字字符串,并把整个数值存入变量$1

 

我们可以在FreeSWITCH命令行测试正则表达式,以确保它们工作正确。

FreeSWITCH控制台命令regex至少需要两个参数:待测试的数据和待匹配的模式,参数分割符是管道符(|)。如果数据与模式匹配成功,regex返回true,否则返回false。你可以在fs_cli尝试下面这些示例:

freeswitch@internal> regex 1234|\d true

freeswitch@internal> regex 1234|\d\d\d\d true

freeswitch@internal> regex 1234|\d{4} true

freeswitch@internal> regex 1234|\d{5} false

freeswitch@internal> regex 1234|^1234$ true

freeswitch@internal> regex 1234|234 true

freeswitch@internal> regex 1234|^234 false

        regex命令还有一种捕获语法,可以存储和返回寄存器的值。表达式%0包含整个匹配的值,%1包含第一个寄存器($1)的值,%2包含第二个寄存器($2)的值,以此类推。

freeswitch@internal> regex 18005551212|1?(\d\d\d)(\d\d\d)(\d\d\d\d) true

freeswitch@internal> regex 18005551212|1?(\d\d\d)(\d\d\d)(\d\d\d\d)|%0 18005551212

freeswitch@internal> regex 18005551212|1?(\d\d\d)(\d\d\d)(\d\d\d\d)|%1 800

freeswitch@internal> regex 18005551212|1?(\d\d\d)(\d\d\d)(\d\d\d\d)|%2 555

freeswitch@internal> regex 18005551212|1?(\d\d\d)(\d\d\d)(\d\d\d\d)|%3 1212

freeswitch@internal> regex 18005551212|1?(\d\d\d)(\d\d\d)(\d\d\d\d)|%1%2%3

8005551212

freeswitch@internal> regex 18005551212|1?(\d\d\d)(\d\d\d)(\d\d\d\d)|%1-

%2-%3

800-555-1212

freeswitch@internal> regex 18005551212|^7|%0 18005551212

 

       网上有很多关于如何使用perl正则表达式的教程,对于FreeSWITCH用户来说,Confluence上有一个精心设计的页面可以参考。打开http://freeswitch.org/confluence,并搜索"regular expression"。

 

Action和anti-action

        Action和anti-action生存在condition内部。如果条件表达式匹配成功,则执行Action;否则执行Anti-action。

        让我们看一个"hello world"的示例,在default.xml的拨号方案头部插入以下内容:

<extension name="giovanni_05">

<condition field="destination_number" expression="^4321$">

<action application="answer"/>

<action application="log" data="WARNING YESSSSS"/>

<anti-action application="log" data="WARNING NOOOOOOOOT"/>

<action application="hangup"/>

</condition>

</extension>

 

       接下来和往常一样,reloadxml,然后拨打4321,你将在控制台看到类似以下的输出:

现在,呼叫 5555 (或随便拨个号):

      如你所见,这一次只执行anti-action的指令。没有执行"answer",也没有执行hangup(这里也会触发挂机,但不是执行拨号方案里显式的调用,而是呼叫的TODO列表没有其它更多的anti-action指令了,除了挂机,还能做什么呢?)。

 

拨号方案的工作原理

        呼叫抵达FreeSWITCH时,根据它使用的技术、网络特征(profile),或者根据用户目录的显式配置(针对注册用户发起的呼叫),会着陆到一个特定的拨号方案context。呼叫会从头到尾遍历context,尝试挨个匹配每个extension。在某个extension内部,根据条件匹配结果,会把action或anti-action添加到呼叫的TODO列表。根据condition的"break"参数值,决定是停止处理下一条condition,还是继续判断条件。根据extension的"continue"值,决定是继续处理下一个extension还是跳出。当呼叫结束拨号方案的context遍历时(可能是因为"continue"值结束,也可能是确实遍历所有extensions结束),TODO列表中所存储的所有(anti-)action将被挨个执行。

        因此,这里有两个独立的处理阶段:第一个是拨号方案解析(也叫路由ROUTING阶段),这时,呼叫遍历拨号方案,并在TODO列表中堆积action(没有执行);第二阶段是执行(EXECUTE)阶段,这时所有堆积下来的action将被批量执行。

        如果我们在FreeSWITCH控制台打开调试开关"fsctl loglevel 7",然后再次呼叫1234,就可以在控制台上清晰地看到阶段的变化:

        如你所见:呼叫首先进入ROUTING阶段,检查拨号方案中某个context(这里是"default")的extension,查找匹配项。这里,第一个extension匹配失败,起初我们以为我们会继续检查下一个extension。但这个失败的条件内部包含了一个anti- action,因此我们实际上要添加反向指令!极的比赛仍然是比赛,事情不仅是浪漫的。

        因此,在把anti-action添加到TODO列表中之后,呼叫停止遍历拨号方案的context,进入EXECUTE阶段。

        如果我们删除(或注释掉) condition里的anti-action,然后reloadxml,再呼叫一次,我们会发现context里的其它所有extension都会被检查一遍。

 

        注意:务必牢记,如果你在拨号方案中设置一个通道变量,然后在条件匹配中检查这个变量,它将无法如你预期的样子工作!!!

<extension name="giovanni_06">

<condition field="destination_number" expression="^4321$">

<action application="answer"/>

<action application="set" data="myvariable=abc"/>

</condition>

<condition field="${myvariable}" expression="abc">

<action application="log" data="WARNING YESSSSS"/>

<anti-action application="log" data="WARNING NOOOOOOOOT"/>

</condition>

</extension>

 

        这是因为,设置变量的"action"将在EXECUTE阶段执行,而条件检查是在之前的ROUTING阶段进行的。

        为了达到目的,你必须在set时使用"inline"属性,这样能把参数设置提前到ROUTING阶段。下面这个例子工作良好:

<extension name="giovanni_07">

<condition field="destination_number" expression="^4321$">

<action application="answer"/>

<action application="set" data="myvariable=abc" inline="true"/>

</condition>

<condition field="${myvariable}" expression="abc">

<action application="log" data="WARNING YESSSSS"/>

<anti-action application="log" data="WARNING NOOOOOOOOT"/>

</condition>

</extension>

 

        此外,下面内容将永远无法工作(你不能内联play_and_get_digits的结果,请用脚本替代):

<extension name="play_and_get_digits with say">

<condition field="destination_number" expression="^(6500)$">

<action application="play_and_get_digits" data="1 1 1 3000 # say:'press one for technical support' silence_stream://250 choice \d+" />

</condition>

<condition field="${choice}" expression="1">

<action application="log" data="TECH SUPPORT!"/>

<anti-action application="log" data="OPERATOR"/>

</condition>

</extension>

 

 

重要的拨号方案app

        FreeSWITCH有许多(几百个)app可供拨号方案和脚本使用。它们大部分由mod_dp_tools模块提供,但几乎每个FreeSWITCH模块都会向你的工具箱中添加一些app。让我们回顾一些最主流的构建块,你可以马上体验的app。

answer

answer APP向主叫方发摘机信号,并建立媒体流。在SIP术语中,它触发FreeSWITCH发送一个"200 OK"消息,协商音视频编码,并开始RTP流传输。

示例:

<action application="answer"/>

 

bridge

bridge APP发起新的呼叫作为B-leg,并把新建的B-leg媒体流与呼入的媒体流(也就是A-leg)桥接。

参数语法:

bridge data="<dialstring>[,<dialstring>][|<dialstring>]"

 

        如果拨号串以逗号分隔,那么拨号是同时进行的。如果拨号串以管道符分隔,那么拨号是按次序进行的。第一部摘机的话机将接收话务,同时会取消其它话机的呼叫

示例:

<action application="bridge" data="user/1000"/>

<action application="bridge" data="sofia/gateway/my_gateway_name/$1"/>

 

        本章后面会讲述拨号串的格式。

hangup

hangup APP断开媒体流并结束通话。

参数语法:挂机原因参数是可选的。

示例:

<action application="hangup"/>

<action application="hangup" data="USER_BUSY"/>

ivr

ivr APP把话务接到一个预定义的IVR。

参数语法:要执行IVR的名字

示例:

<action application="ivr" data="ivr_demo"/>

 

play_and_get_digits

        play_and_get_digits会向主叫播放一个音频文件,同时监听主叫的DTMF按键,并作出响应。这在编写Freeswitch脚本时尤其有用,它允许完整的应用程序逻辑和控制流。

        在拨号方案里,你可以用它实现简单的交互,而不需要调用ivr app和XML IVR语言。

参数语法:

<min> <max> <tries> <timeout> <terminators> <file> <invalid_file>

<var_name> <regex> [<digit_timeout>] ['<failure_ext> [failure_dp [failure_context]]']

Arguments:

min: 要收集的最少位数

max: 要收集的最多位数

tries: 尝试播放文件和收集数字的次数

timeout: 假定用户结束按键输入之前需要等待的时间,单位毫秒

terminators: 用于结束输入的按键(不必等等搜集位数满或超时,一般是井号 #)

file: 提取按键输入时播放的音频文件

invalid_file: 当输入与regex 参数不匹配时播放的音频文件

var_name: 搜集信息要存入的通道变量名

regex: 检查输入合法性的正则表达式

digit_timeout (optional): 两个按键输入间等待的超时时间,单位毫秒(缺省为timeout值)

failure_ext (可选): 如果主叫没有输入合法值,可以转移的目标号码

* failure_dp (可选,用于 failure_ext): 使用failure_ext 时,指定目标拨号方案类型(几乎总是”XML”)

failure_context (可选,用于failure_dp): 使用failure_dp 时,目标拨号方案context

 

示例:

 

<action application="play_and_get_digits" data="2 5 3 8000 #

/path/to/sound_file.wav /path/to/invalid_sound.wav my_digits \d+ "/>

<action application="log" data="User entered these digits: ${my_digits}"/>

    参数说明:

  • 最少输入两位
  • 最多输入五位
  • 尝试三次
  • 等待8秒没有输入认为输入结束
  • 输入井号表示结束
  • 搜集输入时播放/path/to/sound_file.wav
  • 非法输入时播放/path/to/invalid_sound.wav
  • 用通道变量my_digits存储输入内容
  • 以模式匹配\d+检查输入内容

 

playback

        playback APP向主叫播放一个音频文件。文件格式可以是FreeSWITCH模块支持的任意格式。FreeSWITCH演示配置中,所有音频文件都是wav格式的。

         参数语法:音频文件的绝对路径,或基于缺省音频文件目录的相对路径。

示例:

<action application="playback" data="/absolute/path/to/sound.wav"/>

<action application="playback" data="voicemail/vm-goodbye.wav"/>

 

set

set app设置一个通道变量

参数语法:

<variable_name=value>

 

示例:

<action application="set" data="my_chan_var=example_value"/>

<action application="log" data="INFO my_chan_var contains ${my_chan_var}"/>

 

sleep

sleep暂停拨号方案执行,参数单位毫秒

参数语法:休眠毫秒数

示例:

<action application="sleep" data="1000"/>

 

transfer

transfer app把话务送回拨号方案。这将触发一个全新的ROUTING阶段和EXECUTION阶段。

参数语法:

<destination number> [destination dialplan [destination context]]

 

示例:

<action application="transfer" data="9664"/>

<action application="transfer" data="12345 XML custom"/>

 

 

拨号串格式

        拨号串是发起外呼时(FreeSWITCH创建的,向外的呼叫)传递的参数。每个终端模块都有它自己的拨号串语法。其中最重要的终端模块是mod_sofia(支持SIP协议)和mod_verto(支持VERTO协议)。

        拨号方案中,通过"bridge" app发起外呼。如果你正在拨号方案中处理一路来电,那么新创建的呼叫将会与来电话务桥接。

        所有注册用户可以从用户目录中获取它们的拨号串(演示配置里都是这样的)。在用户目录中,可以为所有用户设置一个通用的拨号串,同时,为某个用户或群组精心设置一个独立的拨号串。每个值都可以被更具体的信息覆盖。

        此外,你还可以在命令行或脚本中,通过"originate"命令发起呼叫。

 

SIP (sofia)

        基本上Sofia的拨号串有两种格式,具体采用哪种取决于是否使用网关:

sofia/<profile name>/<user@domain> sofia/gateway/<gateway name>/<user>

 

        正如我们在第四章(用户目录,SIPVerto)中学到的,不论是否有网关,我们都可以拨打另一个SIP终端。当我们使用Sofia模块,通过FreeSWITCH profile的SIP配置拨号时,有必要指定用户名和域名。但是,当我们通过网关拨号时,大可不必包含域名,因为域名信息中网关的配置文件里有定义。因此,下面的格式是不允许的:

<!-- Wrong -->

<action application="bridge" data="sofia/gateway/my_gateway/[email protected]"/>

 

        正确的语法是这样的::

<!-- Correct -->

<action application="bridge" data="sofia/gateway/my_gateway/user">

 

通过internal profile拨号的等效项是这样的:

<!--Also correct -->

<action application="bridge" data="sofia/internal/[email protected] /">

 

当我们呼叫FreeSWITCH 的注册用户时,有一种快捷方式可用:

user/<user id>[@domain]

这种语法,让你轻易能够呼叫系统上的注册话机。事实上,Local_Extension in conf/dialplan/default.xml中就使用这种方法呼叫注册用户:

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

如果你的FreeSWITCH上只定义了一个域,那么@domain 参数是可选的。

 

VERTO

        Verto比SIP简单得多,它连接直接注册到同一台FreeSWITCH的用户。因此,从FreeSWITCH拨号方案的角度看,它与SIP的"user"快捷方式很像,你无需费心:

<action application="bridge"

data="${verto_contact ${user id}@${domain}}"/>

 

        它实际上使用verto_contact API命令查找呼叫VERTO注册用户所需要的底层RTC数据。

 

其它拨号串格式

        以下是一些其它类型的拨号串实例:

loopback/<destination number>: This creates a call leg and puts it in the dialplan at <destination_number> freetdm/<span>/<channel>/<phone number>: This creates a call leg on a

telephony interface card (see http://wiki.freeswitch.org/wiki/FreeTDM for more information on using traditional telephony hardware with FreeSWITCH)

group/<group name>[@domain]: This calls a group of users defined in User Directory

 

Originate 命令

        以下是originate命令的语法:

originate <dialstring> <destination number>

 

        它的dialtring参数和拨号方案中bridge app的拨号串格式完全相同。事实上,"destination number"参数可以是拨号方案的app,因此你可以执行这样的命令:

 

        originate sofia/internal/[email protected] &bridge(sofia/internal/[email protected])

 

        这条命令将向userA发起一路呼叫,如果userA应答,系统会向userB发起另一路呼叫,在userB应答后,两路呼叫将被桥接在一起,然后双方用户就可以交谈了。

 

总结

这一章,我们讨论了以下内容:

  • XML拨号方案的基本结构
  • 拨号方案划分为多个context
  • Context包含extension
  • Extension包含一个或多个condition
  • Condition决定了最终是执行action还是anti-action

 

       然后我们回顾了"call leg"的定义(A-leg是入呼的话务,B-leg是FreeSWITCH为了回应A-leg在处理拨号方案时发起的外呼)。

       我们还研究了通道变量,了解如何设置和检查它们,然后解释了拨号方案遍历过程和TODO列表的"action"积累和执行阶段。

       最后我们研究了一些是非常有用的基础构建块:一些APP用法释义和拨号串的语法。

      下一章,我们将学习怎样使用XML构建比拨号方案更强大的IVR。

猜你喜欢

转载自blog.csdn.net/yetyongjin/article/details/99652597
今日推荐