FreeSwitch的Dialplan拨号计划

 第一、工作原理:

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

第一阶段:拨号方案解析(也叫路由ROUTING阶段),这时,呼叫遍历拨号方案,并在TODO列表中堆积action(没有执行);

第二阶段:执行(EXECUTE)阶段,这时所有堆积下来的action将被批量执行。

  拨号计划的配置文件在 conf/dialplan 中,在前面的章节中我们讲过,它们是在 freeswitch.xml 中,由 <X-PRE-PROCESS cmd="include" data="dialplan/*.xml"/> 装入的。

  拨号计划由多个 Context (上下文/环境)组成。每个 Context 中有多个 Extension (分支,在简单的 PBX 中也可以认为是分机号,但很显然,Extension 涵盖的内容远比分机号多)。所以,Context 就是多个 Extension 的逻辑集合,它相当于一个分组,一个 Context 中的 Extension 与其它 Context 中的 Extension 在逻辑上是隔离的

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

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

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

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

第二、拨号计划的介绍:

例一:Channel 的建立过程。

FreeSWITCH 默认的配置提供了 1000 - 1019 共 20 个 SIP 账号,密码都是 1234 。

<extension name="Local_Extension">
    <condition field="destination_number" expression="^(10[01][0-9])$">
    
        <action application="set" data="dialed_extension=$1"/>
        <action application="export" data="dialed_extension=$1"/>

    </condition>
</extension>

这个框架说明,用正则表达式 (10[01][0-9])$ 来匹配被叫号码,它匹配所有 1000 - 1019 这 20 个号码。这里我们假设在 SIP 客户端上,用 1000 和 1001 分别注册到了 FreeSWITCH 上,则 1000 呼叫 1001 时,FreeSWITCH 会建立一个 Channel,该 Channel 构成一次呼叫的 a-leg(一条腿)。初始化完毕后,Channel 进入 ROUTING 状态,即进入 Dialplan。由于被叫号码 1001 与这里的正则表达式匹配,所以,会执行下面这些 Action。

另外,由于我们在正则表达式中使用了 “( )”,因此,匹配结果会放入变量 $1 中,因此,在这里,$1 = 1001。

补充:set 是将变量设置到当前的 Channel 上,即 a-leg。而 export 则也将变量设置到 b-leg 上。

例二:拨号框架:extension、condition和action讲解。

<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都会被执行)。

补充说明正则:

1,^:表示“字符串开始”,

2,$:它表示字符串结束;

3,这两个标记符之间,用圆括符括起一些其它字符。圆括符会“抓取”它包含的内容,并把它放在一个“寄存器”里。在本例中,文本“1234”被放在寄存器“$1”中(如果有多对圆括符,那么每二对里的内容放在寄存器“$2”中,以此类推)。

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

模式

含义

345

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

^345

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

345$

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

^345$

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

\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打头的任何四位长度的数字字符串,并把整个数值存入变量$

^1234$         ^ 匹配字符串开头,$ 匹配结尾,所以本表达式严格匹配 1234
^1234|5678$    | 是或的意思,表示匹配 1234 或 5678
^123[0-9]$     [ ] 表式匹配其中的任意一个字符,其中的 - 是省略的方式,表示 0 到 9,它等于 [0123456789]
               也就是说它会匹配 1230,1231,1232 ... 1239
^123\d$        同上,\d 等于 [0-9]
^123\d+$       + 号表示1个或多个它前面的字符,因为 + 前面是 \d,所以它就等于1个或多个数字,实际上,
               它匹配任何以123开头的至少4位数的数字串,如1230,12300,12311,123456789等
^123\d*$       *号与+号的不同在于,它匹配0个或多个前面的字符。
               所以,它匹配以123开头的至少3位数的数字串,如 123,123789
^123           跟上面一样,由于没有结尾的$,它匹配任何以123开头的数字串,但除此之外,它还匹配后面是字母的情况,如 123abc
123$           匹配任何以123结尾的字符串
^123\d{5}$     {5}表示精确匹配5位,包含它前面的一个字符。在这里,它匹配以123开头的所有8位的电话号码
^123(\d+)$     ( )在匹配中不起作用,跟^123\d+是相同的,但它对匹配结果有作用,
               匹配结果中除123之外的数字都将存储在$1这个变量中,在下一步使用
^123(\d)(\d+)$ 如果用它跟12345678匹配,则匹配成功,结果是 $1 = 4, $2 = 5678

例三:Action与Anti-action的作用

这两个命令都存在拨号盘中,如果条件表达式匹配成功,则执行Action;否则执行Anti-action

<extension name="TEST">
  <condition field="destination_number" expression="^4321$">
     <action application="answer"/>
     <action application="log" data="YES"/>
     <anti-action application="log" data="ERROR"/>
     <action application="hangup"/>
  </condition>
</extension>

如果拨号4321,控制台则输出YES。执行:answer、hangup指令,但不执行anti-action操作。

如果拨号其他非4321,控制台输出ERROR,其他的指令不在执行。

例四:多condition组合使用

<extension name="that_extension">
    <condition field="ani" expression=""^54321$"/>
    <condition field="destination_number" expression=""^1234$">
        <!-- action -->
        <!-- action -->
    </condition>
</extension>

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

补充说明:

    1, ani:主呼叫号码

    2, destination_number:呼叫的目标号码

    3,condition有一个隐含属性:"break='on-false'"。

例五:Break属性

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

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

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

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

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

<extension name="other_extension">
    <condition field="ani" expression=""^5551212$" break="on-true"/>
    <condition field="destination_number" expression=""^1234$">
        <!-- action -->
        <!-- action -->
    </condition>
 
</extension>

在这个示例中,如果主叫号码不是5551212,那么需要判断第二个条件。如果呼叫的是5551212那么就不用再判断第二个条件了。

例六:info属性

<extension name="show_info">
    <condition field="destination_number" expression="^10086$">
        <action application="answer"/>
        <action application="info"/>
        <action application="sleep" data="1000"/>
        <action application="hangup"/>
    </condition>
</extension>

补充说明:

  1,检查destination_number变量,是否是10086,

  2,如果是呼叫号10086,就应答(answer),

  3,执行"info" app,将在控制台上转储每个通道变量和它的值。

  4,休眠1秒(sleep)

  5,挂机(hangup)

上述简单的例子主要通过info,把通道信息全部列出来,方便将来的调试使用。

例七:通道变量的获取

"variable_"前缀打头的行,都可以在拨号方案中以${}结构访问

比如,上面输出中的最后一行:"variable_current_application: [info]",它表示通道变量"${current_application}"当前的值是"info"。

写一个新的extension来示范一下:

<extension name="test">
    <condition field="destination_number" expression="^1234$">
        <action application="answer"/>
        <action application="log" data="current_channel variable is: ${current_application}"/>
        <action application="hangup"/>
    </condition>
</extension>

例八:自定义通道变量

设置通道变量nice_guy,并获取该值信息

<extension name="Test">
    <condition field="destination_number" expression="^1234$">
        <action application="answer"/>
        <action application="log" data="current_channel variable is: ${current_application}"/>
        <action application="log" data="nice_guy is: ${nice_guy}"/>
        <action application="set" data="nice_guy=verymuch"/>
        <action application="log" data="nice_guy is: ${nice_guy}"/>
        <action application="hangup"/>
    </condition>
</extension>

补充说明:

   第一次打印nice_guy is:

   第一次打印nice_guy is:verymuch(通过set已经赋值了,故有第二次输出有内容的)

例九:Set的"inline"属性

如果你在拨号方案中设置一个通道变量,然后在条件匹配中检查这个变量,它将无法如你预期的样子工作!这是因为,设置变量的"action"将在EXECUTE阶段执行,而条件检查是在之前的ROUTING阶段进行的。 为了达到目的,你必须在set时使用"inline"属性,这样能把参数设置提前到ROUTING阶段,如下:

<extension name="Test">
  <condition field="destination_number" expression="^1234$">
    <action application="answer"/>
    <action application="set" data="key=789" inline="true"/>
  </condition>
  <condition field="${key}" expression="789">
    <action application="log" data="YES"/> 
    <anti-action application="log" data="ERROR"/>
  </condition>
</extension>

本例是在条件1下,action阶段设置key=789信道变量,然后再第二条件condition中获取。如果不在set设置时候增加inline=true标记,否则获取不到该值。
 

第三、外连拨号参数讲解:

1,answer

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

<action application="answer"/>

2,bridge

bridge APP发起新的呼叫作为B-leg,并把新建的B-leg媒体流与呼入的媒体流(也就是A-leg)桥接。如果拨号串以逗号分隔,那么拨号是同时进行的。如果拨号串以管道符分隔,那么拨号是按次序进行的。第一部摘机的话机将接收话务,同时会取消其它话机的呼叫

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

3,hangup

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

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

4,ivr

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

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

5,play_and_get_digits

  play_and_get_digits会向主叫播放一个音频文件,同时监听主叫的DTMF按键,并作出响应。这在编写Freeswitch脚本时尤其有用,它允许完整的应用程序逻辑和控制流。 在拨号方案里,你可以用它实现简单的交互,而不需要调用ivr app和XML IVR语言。

<action application="play_and_get_digits" data="4 6 3 7000 
/path/to/sound.wav /path/to/invalid.wav my_keys \d+ "/>
<action application="log" data="User entered these digits: ${my_keys}"/>

    参数说明:

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

6,playback

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

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

7,sleep

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

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

8,transfer

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

<action application="transfer" data="54321"/>
<action application="transfer" data="789 XML custom"/>

9,sofia

 基本上Sofia的拨号串有两种格式。

<!-- Correct -->
<action application="bridge" data="sofia/gateway/myway/user">
<!-- 通过internal profile拨号的等效项是这样的-->
<action application="bridge" data="sofia/internal/user@192.168.1.8 /">

10,echo

挂起 听到自己的回声

<action application="echo" data=""/>

11,delay_echo

挂起 延迟 听到自己的回声

<action application="delay_echo" data="5000"/>

12,park

<action application="park"/>

13,log

日志内容输出

<action application="log" data="INFO Hello FreeSwitch,I am stupid."/>

14,info

查看信道所有信息,在 FreeSWITCH 中,每一次呼叫都由一条或多条“腿”(Call Leg)组成,其中的一条腿又称为一个 Channel(信道),每一个 Channel 都有好多属性,用于标识 Channel 的状态,性能等,这些属性称为 Channel Variable(信道变量),简写为 Channel Var 或 Chan Var 或 Var。

<action application="info" data=""/>

15,set

set app设置一个通道变量

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

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

16,export 

设置一个变量

<action application="set" data="dialed_extension=$1"/>
<action application="export" data="dialed_extension=$1"/>

 set 是将变量设置到当前的 Channel 上,即 a-leg。而 export 则也将变量设置到 b-leg 上。当然,这里 b-leg 还不存在。所以在这里它对该 Channel 的影响与 set 其实是一样的。因此,使用 set 完全是多余的。但是除此之外,export 还设置了一个特殊的变量,叫 export_vars,它的值是dialed_extension。

17,bind_meta_app 

作用是在该 Channel 是绑定 DTMF

<action application="bind_meta_app" data="1 b s execute_extension::dx XML features"/>
<action application="bind_meta_app" data="2 b s record_session::$${recordings_dir}/${caller_id_number}.${strftime(%Y-%m-%d-%H-%M-%S)}.wav"/>
<action application="bind_meta_app" data="3 b s execute_extension::cf XML features"/>
<action application="bind_meta_app" data="4 b s execute_extension::att_xfer XML features"/>

上面四行分别绑定了 1、2、3、4 四个按键,它们都绑定到了 b-leg上。注意,这时候 b-leg 还不存在。

18,voicemail

语音信箱

<action application="voicemail" data="default ${domain_name} ${dialed_extension}"/>

第四、内连拨号计划 - Inline Dialplan

Inline dialplan 与 Action 中的 inline 参数是不同的。它与 XML Dialplan 不同,它没有 Extension,也没有复杂的 Condition。而只是象 XML Dialplan中那样简单的 Action 的叠加。

它的语法格式:

app1:arg1,app2:arg2,app3:arg3

从语法可以看出,它只是多个 APP 以及参数组成的字符串,APP之间用逗号分隔,而APP与参数之间用冒号分隔。如果参数中有空格,则整个字符串都需要使用单引号引起来。在我们上面的例子 中,你通过拨打 9196 来找到对应的 XML dialplan,在这里,我们可以直接在命令行上写出对应的 inline 形式:

originate user/1000 echo inline
originate user/1000 answer,echo inline

读到这里,你可能要问,它与“originate user/1000 &echo” 有什么区别呢?在回答这个问题之前,我们需要先看一下 originate 的语法:

originate <call_url> <exten>|&<application_name>(<app_args>)
[<dialplan>] [<context>] [<cid_name>] [<cid_num>] [<timeout_sec>]

首先,它的第一个参数是呼叫字符串, 第二个参数可以是 & 加上一个 APP,APP的参数要放到 ( ) 里,如:

originate user/1000 &echo
originate user/1000 &playback(/tmp/sound.wav)
originate user/1000 &record(/tmp/recording.wav)

这是最简单的形式,首先,originate 会产生一个 Channel,它会呼叫 user/1000 这个用户。请注意,这是一个单腿的通话,因此只有一个 Channel。但一个 Channel 有两端,一端是 1000 这个用户,另一端是 FreeSWITCH。在 user/1000 接电话后(严格说是收到它的 earlymedia 后),FreeSWITCH 即开始在该 Channel 上执行 & 后面的 APP。但这种形式只能执行一个 APP,如果要执行多个,就需要将电话转入 Dialplan:

originate user/1000 9196
originate user/1000 9196 XML default

上面两个命令是一样的。它的作用是,在 user/1000 接电话后,电话的另一端(也就是 FreeSWITCH)需要对电话进行路由,在这里,它要将电话路由到 9196 这个 Extension 上,第一条命令由于没有指定是哪个 Dialplan,因此它会在默认的 XML Dialplan 中查找,同时,XML Dialplan 需要一个 Context, 它默认就是 default。它的效果是跟你直接用软电话拨打 9196 这个分机一样的(所不同的是呼叫的方向问题,这种情况相当于回拨)。

  当然,除此之外,它还可以加一些可选的参数,用于指定来电显示(Caller ID)的名字(cid_name)和号码(cid_number),以及超时的秒数,如:

originate user/1000 9196 XML default 'Seven Du' 9196 30

当然,我们这里学了 Inline Dialplan,所以你也可以这样用:

originate user/1000 echo inline

请注意,在 XML Dialplan 中, 9196 是一个分机号,而 Inline 中的 echo 则是一个 APP,当然你也可以顺序执行多个 APP:

originate user/1000 answer,playback:/tmp/pleace_leave_a_message.wav,record:/tmp/recording.wav inline
originate user/1000 playback:/tmp/beep.wav,bridge:user/1001 inline

 有时候,APP 的参数中可能会有逗号,因而会与默认的 APP 间的逗号分隔符相冲突,以下的 m 语法形式将默认的逗号改为 ^ 分隔(以下三行实际上为一行)。

originate user/1000
'm:^:playback/tmp/beep.wav^bridge:
{ignore_early_media=true,originate_caller_id_number=1000}user/1001'

当然你也可以用在任何需要 Dialplan 的地方,如(以下两行实为一行)

uuid_transfer 2bde6598-0f1a-48fe-80bc-a457a31b0055
'set:test_var=test_value,info,palyback:/tmp/beep.wav,record:/tmp/recording.wav'

第五、应用场景:

场景一:

freeswitch 调试dialplan,自动接听,播放一段彩铃后开始录音,录音指定时间后自动挂机

<!-- 单腿 录音-->
<extension name="onelegrecord" >
  <condition field="destination_number" expression="^(1001)$">
     <action application="answer"/>
     <action application="playback" data="/home/freeswitch/voice/rengongluzhi.mp3"/>
     <action application="record_session" data="/home/freeswitch/record/${strftime(%Y-%m-%d-%H-%M-%S)}_${destination_number}_${caller_id_number}.mp3"/>
     <action application="sleep" data="10000"/>
     <action application="hangup"/>
     <action application="info" data=""/>
  </condition>
</extension>

红色背景部分,使用record_session,则会起一个进程单独录音,后面继续执行,如果使用record,则record一致会录下去,除非对方挂机。

<action application="sleep" data="10000"/>  这里指定录音时间是10秒

场景二:

拨打1237(也可以自定义其他号码),就可以开始录制拨打电话这方的声音,可以指定生成文件的位置,挂断后wav文件生成完成。

  <extension name="record">
    <condition field="destination_number" expression="^1237$">
	  <action application="answer"/>
  	  <action application="playback" data="tone_stream://%(100,1000,800)"/>
	  <action application="record" data="/tmp/$1.wav"/>
    </condition>
  </extension>

其中,/tmp/$1.wav是录音生成文件的路径和文件名。下载到windows本地,然后直接播放,就可以听到自己的声音了。

场景三:

没什么好解释的,如果拨 9196,就能听到自己的回声

<extension name="echo">
    <condition field="destination_number" expression="^9196$">
        <action application="answer"/>
        <action application="echo"/>
    </condition>
</extension>

场景四: 

延迟回声 与 echo 基本一样,但回声会有一定延迟,5000 是毫秒数。

<extension name="delay_echo">
    <condition field="destination_number" expression="^9195$">
        <action application="answer"/>
        <action application="delay_echo" data="5000"/>
    </condition>
</extension>

第六、出局Socket – 让话务主动连接你

 拨号方案"socket"向指定IP:端口发起一条出局的TCP连接,这时第三方可以控制呼叫,这和连接mod_event_socket的入局控制完全一样。

你必须先运行一个“服务”应用,它监听指定的IP:端口,并准备接收"socket"拨号方案APP发起的连接。一旦TCP连接建立,命令和输出内容和我们之前所见的完全一样。

当你调用socket时,FreeSWITCH会自动把话务置为parked状态。

在拨号方案中调用socket的语法是:

<ip>:<port> [<keywords>]

下面是几个具体的使用实例:

<action application="socket" data="127.0.0.1:8084"/>

<action application="socket" data="127.0.0.1:8084 async"/>

<action application="socket" data="127.0.0.1:8084 full"/>

<action application="socket" data="127.0.0.1:8084 async full"/>

关键字full 和async 会改行为:

  • async关键字async说明所有命令将立即返回(异步执行),从而可以在执行命令堆栈的同时监视socket的事件。如果没有async关键字,那么每个event socket都会堵塞到执行完毕。
  • full关键字full说明对端将拥有event socket的完整命令集。这和入局event socket连接的命令集是一样的,因此,你可以执行API命令,获取全局事件,等等。如果没有full关键字,那么命令集将被限制为特定呼叫相关的范围。换句话说,如果没有full关键字,那么,发到这个socket的命令只能影响当前正在处理的通道。同样地,这个socket连接将只接收这条通道相关的事件。

  • 17
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

常生果

喜欢我,请支持我

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值