拨号方案定义了Asterisk如何处理来话和去话,它由指令和步骤列表组成,Asterisk按步骤来执行这些指令.与传统电话系统不同,Asterisk的拨号方案是完全可定制的.
1拨号方案语法
拨号方案在文件extensions.conf中定义.文件extensions.conf通常在/etc/asterisk/目录下.但是其位置可以改变,取决于Asterisk的安装方式.其它常见的位置包括/usr/local/asterisk/etc/和/opt/asterisk/etc/.
拨号方案由4部分组成:contexts,extensions,priorities和applications.
Context
Dialplans被分成几个段,这些段称为context.Context用来对extension的组命名.在一个context中定义的extension完全独立于另一个context中定义的extensiont,本章末尾将介绍如何允许context之间相互交织.
Contexts的表示方法是把名字放在方括号([])的中间.这个名字可以由A-Z,数字0-9,以及连字号和下划线组成.如:[incoming]
Extension
在每一个context内,可以定义一个或者多个extension.extension是Asterisk要执行的指令,由来电或者通道上所拨数字来触发.
extension的语法是:exten=>
之后是extension的名字.在于电话系统打交道的时候,我们把extension看作是呼叫另一部电话所拨的号码.在asterisk上,意味着更多的东西.
一个完整的extension由三部分组成:
Extension的名字或者号码
Priority(每个extension可以有多个步骤,步骤的编号称作Priority)
应用(或者命令),针对呼叫完成一些动作
这三个部分用英文逗号分开,如:
exten=>name,priority,application()
例如:
exten=>123,1,Answer()
在这个例子中,extension的名字是123,priority是1,应用是Answer().
Priority
每个extension可以有多个步骤,称作priorities.每个priority都按顺序编号,从1开始.每个priority执行一个规定的应用.下面给出一个例子,这个extension接听电话(编号为1的priority),然后挂断(编号为2的priority):
exten=>123,1,Answer()
exten=>123,2,Hangup()
注意:必须确保priority从1开始并且是连续的编号.对于特定的extension,Asterisk遵从priority的数字顺序.
Application
无序号的priority
在Asterisk1.2版本里,Priority编号增加了新的变化,引入了npriority,表示"下一个"的意思.每次Asterisk遇n这个priority的时候,就取出前一个priority的编号加上1.例如:
extern=>123,1,Answer()
extern=>123,n,dosomething
extern=>123,n,do something else
extern=>123,n,doone last thing
extern=>123,n,Hangup()
1.2版也允许给priority分配一个文字标号.要给priority分配文字标号,只需要在priority后面的括号内加上这个标号,如:
extern=>123,n(label),dosomething
2一个简单的拨号方案
我们以一个简单的例子来开始.在呼叫进来时,Asterisk应答这个呼叫,播放声音文件,然后挂断,用这个简单例子来指出拨号方案基本原理.
为了工作正确,至少创建一个通道(Zap或Sip通道),并且作了配置.
sExtension
在着手拨号方案前,先介绍一个特别的extension,名字叫做s.当没有指定extension的呼叫(例如,正在振铃的FXO线路)进入context的时候,就由这个sextension来处理.(s表示"start"开始,因为多数的呼叫都是从sextension开始).我们要在呼叫上完成三个动作(接听,播放声音文件,最后挂断),所以要创建有三个priority的sextension.我们把这三个priority放到[incoming]里,所有来话都应该从这个context开始:
[incoming]
exten=>s,1,application()
exten=>s,2,application()
exten=>s,3,application()
现在我们所要做的事情就是填写应用.
Answer(),Playback()和Hangup()应用
Answer()应用用于接听正在振铃的通道,并不为接受来话的通道进行初始化设定(确有不少应用不需要先应答通道,但是在完成其它动作之前进行适当的应答是一个好习惯).Answer()不需要任何参量.
Playback()应用用于在通道上播放事先录制好的语音文件.在使用Playback()应用时,系统不会理会来自用户的输入.Asterisk带有很多专业录制的语音文件,默认的目录通常是/var/lib/asterisk/sounds/.这些文件都是GSM格式.使用格式如下:
Playback(filename);将播放文件名为filename.gsm的语音文件,并假定这个文件位于默认的语音文件目录内.
Playback(/home/tim/sounds/filename);也可以包括完整路径,将播放/home/tim/sounds/目录下的filename.gsm文件.
Playback(custom/filename);将播放默认语音文件目录内custom/子目录中的filename.gsm文件.
Hangup()完成挂断一个正在活动的通道.主叫方将收到通话挂断的指示.当要结束通话时,可以在context的末尾使用这个应用,以确保主叫不会继续停留在拨号方案内.这个应用不需要参量.
第一个拨号方案
方案如下:
[incoming]
exten=>s,1,Answer()
exten=>s,2,Playback(hello-world)
exten=>s,3,Hangup()
3在拨号方案中加入逻辑
刚刚建立的拨号方案是静态的,对每个呼叫总是作相同的事情,现在加入一些逻辑,让它根据用户的输入来完成一些不同的动作.
Background()和Goto()应用
构建交互式Asterisk系统的关键是Background()应用.与Playback()相同的是,它也播放事先录制好的语音文件;与Playback()不同的是,当主叫方按下电话键(1个或者多个)的时候,会中断语音的播放,转到与所按数字对应的extension.例如,假设主叫方按下5,Asterisk停止播放语音,把呼叫的控制发送给extension5的第一个priority.Background()语法与Playback()类似.
Background()应用通常用于创建语音菜单,以免接待员接听每一个电话
另外一个有用的应用是Goto().它用于把呼叫发送到另一个context,extension,以及priority.Goto应用使得在拨号方案的不同部分有序的转移非常容易.语法如下:
exten=>123,1,Goto(context,extension,priority)
看下面例子:
[incoming]
exten=>s,1,Answer()
exten=>s,2,Background(enter-ext-of-person)
exten=>1,1,Playback(digits/1)
exten=>1,2,Goto(incoming,s,1)
exten=>2,1,Playback(digits/1)
exten=>2,2,Goto(incoming,s,1)
非法输入和超时的处理
第一个语音菜单已经完成,我们在加入几个附加的特殊extension.首先,需要一个用来处理非法输入的extension,从而在主叫方按下一个无效输入(比如在上面的例子中按下3),呼叫被送到iextension.其次,需要一个extension来处理主叫方没有及时(默认的时间是10秒)输入的情况.如果主叫方在Background()完成语音文件播放之后很久才按键,呼叫将被转移到textension.如下:
[incoming]
exten=>s,1,Answer()
exten=>s,2,Background(enter-ext-of-person)
exten=>1,1,Playback(digits/1)
exten=>1,2,Goto(incoming,s,1)
exten=>2,1,Playback(digits/2)
exten=>2,2,Goto(incoming,s,1)
exten=>i,1,Playback(pbx-invalid)
exten=>i,2,Goto(incoming,s,1)
exten=>t,1,Playback(vm-goodbye)
exten=>t,2,Hangup()
现在功能还是有限,因为外面的主叫无法与实际的人联系.为了做到这一点,我们还需要一个Dial()应用.
使用Dial()应用
Dial()需要4个参量.第1个是呼叫的被叫地,由呼叫所采用的(传输)技术,反斜线,远地资源(通常是通道名称或编号)等组成.例如,假定我们要呼叫名字为Zap/1的Zap通道(连接了普通模拟电话的FXS通道),那么技术是Zap,资源是1.与此类似,到一个SIP设备的呼叫的被叫地是SIP/1234,而到一个IAX设备的呼叫的被叫地是IAX/fred.假如在extension123到达拨号方案时,要Asterisk对Zap/1通道振铃,要加入下面这个extension:
exten=>123,1,Dial(Zap/1)
当这个extension被执行时,Asterisk会连接通道上Zap/1的电话振铃,也可以同时拨多个通道,如下:
exten=>123,1,Dial(Zap/1&Zap/2&Zap/3)
Dial()会桥连来电,无论被叫地中的哪一个通道先接听.
Dial()应用的第2个参量是超时,单位为秒.如果给定了超时参量,Dial()会一直对被叫地进行呼叫,直到超时后才放弃,然后转移到该extension中的下一个priority.如果没有指定超时时间,Dial()会一直呼叫该通道,直到有人接听,或者主叫挂机.我们把10秒超时加到extension中:
exten=>123,1,Dial(Zap/1,10)
如果呼叫在超时之前被接听,通道就被桥连,拨号方案完成,如果被叫地没有应答,Dial()会继续到该extension的下一个priority.但是如果被叫通道忙,Dial()将转到priorityn+101,如果其存在的话(其中的n是Dial()被调用的priority).这样我们就能够以不同于被叫地忙的方式来处理未接听电话.如下:
exten=>123,1,Dial(Zap/1,10)
exten=>123,2,Playback(vm-nobodyavail)
exten=>123,3,Hangup()
exten=>123,102,Playback(tt-albusy)
exten=>123,103,Hangup()
正如你所看到的,在这个例子中,如果呼叫未被接听,将播放vm-nobodyavail.gsm语音文件,如果Zap/1通道正忙,则播放tt-allbusy.gsm语音文件.
Dial()应用的第3个参量是可选择的字符串.它包含一个或多个能够影响Dial()应用行为的字符.最常用的是字母r.如果把r作为第3个参量,在通知被叫通道有来电的这段时间内,主叫方会听到振铃声音.
此时,已经知道如何使用Dial()应用,拨号方案中编号为1和2的extension变得没有用了.我们把它们用extension101和102来代替,这样就允许外部的主叫方把电话打给tim和david:
[incoming]
exten=>s,1,Answer()
exten=>s,2,Background(enter-ext-of-person)
exten=>101,1,Dial(Zap/1,10)
exten=>101,2,Playback(vm-nobodyavail)
exten=>101,3,Hangup()
exten=>101,102,Playback(tt-allbusy)
exten=>101,103,Hangup()
exten=>102,1,Dial(SIP/david,10)
exten=>102,2,Playback(vm-nobodyavail)
exten=>102,3,Hangup()
exten=>102,102,Playback(tt-allbusy)
exten=>102,103,Hangup()
exten=>i,1,Playback(pbx-invalid)
exten=>i,2,Goto(incoming,s,1)
exten=>t,1,Playback(vm-goodbye)
exten=>t,2,Hangup()
Dial()应用的最后一个参量是URL.如果被叫通道支持在呼叫的同时接受URL,那么所指定的URL将被发送,这个参量很少使用.
如果要在FXOZap通道上产生一个去话,可以使用下面的语法在那个通道上拨号:
exten=>123,1,Dial(Zap/4/5551212)
这个例子在Zap/4通道上拨号码555-1212.对于其它类型的通道,如SIP和IAX,简单的把被叫地作为资源,见下:
exten=>123,1,Dial(SIP/1234)
exten=>124,1,Dial(IAX2/tim@asteriskdocs.org)
注意:任何参量为空,如保留超时参量为空,如下:
exten=>123,1,Dial(Zap/1,,r)
给内部呼叫增加Context
context的一个重要功能是为不同的主叫用户区分特权(如长途通话,或者呼叫特定的extension).在下面例子中,我们建立两个内部电话的extension,加到拨号方案里去,然后配置这两个extension可以彼此呼叫.为达到这个目的,创建一个新的叫做[internal]的context.
[incoming]
exten=>s,1,Answer()
exten=>s,2,Background(enter-ext-of-person)
exten=>101,1,Dial(Zap/1,10)
exten=>101,2,Playback(vm-nobodyavail)
exten=>101,3,Hangup()
exten=>101,102,Playback(tt-allbusy)
exten=>101,103,Hangup()
exten=>102,1,Dial(SIP/david,10)
exten=>102,2,Playback(vm-nobodyavail)
exten=>102,3,Hangup()
exten=>102,102,Playback(tt-allbusy)
exten=>102,103,Hangup()
exten=>i,1,Playback(pbx-invalid)
exten=>i,2,Goto(incoming,s,1)
exten=>t,1,Playback(vm-goodbye)
exten=>t,2,Hangup()
[internal]
exten=>101,1,Dial(Zap/1,,r)
exten=>102,1,Dial(SIP/david,,r)
在这个例子中,我们在[interanl]context中加了两个新的extension.这样,使用通道Zap/1的人可以拿起电话拨102来拨打通道channelSIP/david上的人,同样,注册为SIP/david的电话可以拨打101来拨打Zap/1的电话.
如果你认为你的用户可能通过支持名字的VoIP来传输拨号,添加使用名字的extension也不是什么不好的事情.如下修改:
[internal]
exten=>101,1,Dial(Zap/1,,r)
exten=>tim,1,Dial(Zap/1,,r)
exten=>102,1,Dial(SIP/david,,r)
exten=>david,1,Dial(SIP/david,,r)
使用变量
例如,建立一个叫做TIM的变量,并给其赋予值Zap/1.这样,在写拨号方案时,可以利用名字来引用Tim的通道,不需要直接记住Tim使用的Zap/1.为了把指赋给变量,只需要输入变量名称,等号和值,如下所示:
TIM=Zap/1
引用变量有两种方法.若要引用变量的名字,仅仅需要输入变量的名字就可以了,例如:TIM;但如果要引用变量的值,则必须输入美元符号,紧接着是大括号,在大括号内输入变量名.下面的例子说明了如何在应用中引用变量:
exten=>555,1,Dial(${TIM},,r)
在拨号方案中有三种变量可以使用:全局变量,通道变量和环境变量.
1.全局变量
全局变量适用于所有context里的所有extensions.全局变量的好用之处在于它可以用于拨号方案中的任何地方,能够增加可读性和可管理性.
全局变量应该在extensions.conf文件的开始利用[globals]context定义.也可以使用编程的方式定义,利用SetGlobalVar()应用.如下:
[globals]
TIM=Zap/1
或
[internal]
exten=>123,1,SetGlobalVar(TIM=Zap/1)
2.通道变量
通道变量与特定的呼叫相关的变量(如Caller*IDnumber),与全局变量不同,通道变量只能在当前呼叫存在期间定义,并只能用于参与该呼叫的通道.
有很多的预先定义的通道变量可以用于拨号方案,在Asterisk源程序的doc子目录下README文件中有详细的说明.通道变量使用Set()应用来设置:
exten=>123,1,Set(MAGICNUMBER=42)
3.环境变量
环境变量是一种在Asterisk中访问操作系统环境变量的方法.这些变量以${ENV(var)}形式引用,其中的var是所要引用的操作系统环境变量.
4.在拨号方案中加入变量
我们为两个人,Tim和David加入变量:
[globals]
TIM=SIP/tim
DAVID=Zap/1
[incoming]
exten=>s,1,Answer()
exten=>s,2,Background(enter-ext-of-person)
exten=>101,1,Dial(${TIM},10)
exten=>101,2,Playback(vm-nobodyavail)
exten=>101,3,Hangup()
exten=>101,102,Playback(tt-allbusy)
exten=>101,103,Hangup()
exten=>102,1,Dial(${DAVID},10)
exten=>102,2,Playback(vm-nobodyavail)
exten=>102,3,Hangup()
exten=>102,102,Playback(tt-allbusy)
exten=>102,103,Hangup()
exten=>i,1,Playback(pbx-invalid)
exten=>i,2,Goto(incoming,s,1)
exten=>t,1,Playback(vm-goodbye)
exten=>t,2,Hangup()
[internal]
exten=>101,1,Dial(${TIM},,r)
exten=>102,1,Dial(${DAVID},,r)
模式匹配
模式匹配可以使用一段代码来对应许多不同的extensions.
1.模式匹配语法
使用模式匹配的时候,用不同的字母和符号来代表肯恩个要匹配的数字.模式总是用一个下划线(_)开始,它告诉Asterisk要做模式匹配,这不是一个extension名字.(这意味着不能用下划线作为extension名字的开始字符)
在下划线之后,可以使用一个或者多个下面列出的字符:
X
匹配0-9的任何数字.
Z
匹配1-9的任何数字
N
匹配2-9的任何数字
[15-7]
匹配任何数字或者指定的数字范围.在这个例子中,匹配1,5,6或7.
.(句号)
通配符,匹配一个或多个字符.
应该在匹配了其他数字之后在使用通配符,如:
_.
实际上,如果你试图使用它,Asterisk会警告你.如果可能,尽量使用下面的模式:
_X.
若要在拨号方案中使用模式匹配,只要把模式放在extension名字的位置:
exten=>_NXX,1,Playback(auth-thankyou)
在这个例子中,模式会匹配3位的extension,从200到999,这就是说,在这个context中,如果主叫拨200-999之间的任何extension,都会听到声音文件auth-thankyou.gsm的声音.
必须了解的一件事情是,如果Asterisk发现有多个模式与所拨的extension匹配,它会使用最接近的那一个模式.比如说定义了下面的两个模式,主叫方拨的号码是888-555-1212:
exten=>_555XXXX,1,Playback(digits/1)
exten=>_55512XX,1,Playback(digits/2)
在这个例子中,会选择第2个extension,因为它更接近.
2.模式匹配的实例
NANP与话费欺诈
略
3.使用${EXTEN}通道变量
一旦拨了某个extension,Asterisk会把通道变量${EXTEN}设置为所拨的数字.可以使用应用SayDigits()来检测出来:
exten=>_XXX,1,SayDigits(${EXTEN})
在这个例子中,SayDigits()应用会把所拨的3位extension读出来.
通常,把extension的前面几位去掉对于处理${EXTEN}是很有用的.可以利用这样的语法来实现:${EXTEN:x},其中x是要去掉的位数.例如,假设EXTEN的值是95551212,那么${EXTEN:1}等于5551212.再来看另外一个例子:
exten=>_XXX,1,SayDigits(${EXTEN:1})
在这个例子中,SayDigits()应用把所拨的extension的最后两位读出来.如果x是负数,SayDigits()给出所拨的extension的最后x位.在下面的例子中,SayDigits()只读出所拨的extension的最后1位:
exten=>_XXX,1,SayDigits(${EXTEN:-1})
开启去话拨号
允许用户向外拨打电话,首先要做的一件事情是给[globals]context加一个变量,用于定义那一个通道可以用来向外拨打电话:
[globals]
TIM=SIP/tim
DAVID=Zap/1
OUTBOUNDTRUNK=Zap/4
接下来,在拨号方案中添加一个用于去话的context.使用一个单独的context的目的是能够规定和控制谁可以拨打电话,以及可以拨打什么样的去话.
首先,建立一个用于本地电话的context.为了与传统电话交换机保持一致,我们在模式之前放上9,因此用户必须在呼叫外部号码之前拨9:
[outbound-local]
exten=>_9NXXXXXX,1,Dial(${OUTBOUNDTRUNK}/${EXTEN:1})
exten=>_9NXXXXXX,2,Congestion()
exten=>_9NXXXXXX,102,Congestion()
注意:拨9并没有立即给你外线,这和传统的PBX系统不同.一旦在FXS线路上拨9,拨号音会停止.如果在拨9之后还希望有拨号音,加入下面一行(就在context定义之后):
ignorepat=>9
这个指令告诉Asterisk继续提供拨号音,即便是在主叫方已经拨了指示的模式.
回顾一下我们刚刚所做的事情.增加了一个全局变量OUTBOUNDTRUNK,它会控制使用哪一个通道用于去话,还增加了一个用于本地去话的context.在priority1中,取出所拨的extension,用${EXTEN:1}语法去掉9,然后试图在变量OUTBOUNDTRUNK所指定的通道上拨这个号码.如果呼叫成功,主叫方就与去话通道建立桥连.如果呼叫不成功(要么是通道忙,要么是因为某种原因不能拨这个号码,调用Congestion()应用,播放"快忙音"(拥挤声音)让主叫方知道呼叫不成功.
在进一步往下走之前,先确认一下这个拨号方案允许拨打紧急电话号码:
[outbound-local]
exten=>_9NXXXXXX,1,Dial(${OUTBOUNDTRUNK}/${EXTEN:1})
exten=>_9NXXXXXX,2,Congestion()
exten=>_9NXXXXXX,102,Congestion()
exten=>911,1,Dial(${OUTBOUNDTRUNK}/911)
exten=>9911,1,Dial(${OUTBOUNDTRUNK}/911)
下面,给拨号方案加一个用于长途电话的context:
[outbound-long-distance]
exten=>_9NXXXXXX,1,Dial(${OUTBOUNDTRUNK}/${EXTEN:1})
exten=>_91NXXNXXXXXX,2,Congestion()
exten=>_91NXXNXXXXXX,102,Congestion()
现在有了两个新的context,如何允许内部用户利用它们?我们需要一种办法来使得一个context能够使用另一个context.
INCLUDES
Asterisk允许在一个context中使用另一个context,通过include指令来实现.这用来授予访问给不同的拨号方案段.我们使用include功能来让[internal]context中的用户能够拨打去话.首先介绍一下语法.
include语句的形式如下所示,其中的是我们要包含在当前context的远地context:
include=>context
在当前context包含另外的context时,必须注意包含的顺序.Asterisk首先试图在当前context中匹配extension.如果不成功,会去尝试第一个包含进来的context,然后按照包含顺序再去尝试其他的context.
到目前为止,拨号方案有两个context用于去话.但是[internal]context中的人还不能够使用它们.我们用在[internal]context包含两个去话context来实现使用,如下所示:
[globals]
TIM=SIP/tim
DAVID=Zap/1
OUTBOUNDTRUNK=Zap/4
[incoming]
exten=>s,1,Answer()
exten=>s,2,Background(enter-ext-of-person)
exten=>101,1,Dial(${TIM},10)
exten=>101,2,Playback(vm-nobodyavail)
exten=>101,3,Hangup()
exten=>101,102,Playback(tt-allbusy)
exten=>101,103,Hangup()
exten=>102,1,Dial(${DAVID},10)
exten=>102,2,Playback(vm-nobodyavail)
exten=>102,3,Hangup()
exten=>102,102,Playback(tt-allbusy)
exten=>102,103,Hangup()
exten=>i,1,Playback(pbx-invalid)
exten=>i,2,Goto(incoming,s,1)
exten=>t,1,Playback(vm-goodbye)
exten=>t,2,Hangup()
[internal]
include=>outbound-local
include=>outbound-long-distance
exten=>101,1,Dial(${TIM},,r)
exten=>102,1,Dial(${DAVID},,r)
[outbound-local]
exten=>_9NXXXXXX,1,Dial(${OUTBOUNDTRUNK}/${EXTEN:1})
exten=>_9NXXXXXX,2,Congestion()
exten=>_9NXXXXXX,102,Congestion()
exten=>911,1,Dial(${OUTBOUNDTRUNK}/911)
exten=>9911,1,Dial(${OUTBOUNDTRUNK}/911)
[outbound-long-distance]
exten=>_9NXXXXXX,1,Dial(${OUTBOUNDTRUNK}/${EXTEN:1})
exten=>_91NXXNXXXXXX,2,Congestion()
exten=>_91NXXNXXXXXX,102,Congestion()
这两个include语句让[internal]context内的主叫方可以拨打去话.应该注意到,出于安全的考虑,要确保[inbound]context永远不要允许拨打去话.(如果一旦给了这样的机会,外面的人可以拨入你的系统,然后在拨打收费电话出去,让你来承担通话费用!)