The dialplan is the routing core of an Asterisk server. Its sole role is to look at what is dialed, and route the call to its destination. This is the core of any telephony system and Asterisk is no different.
The dialplan is made up of three elements—extensions, contexts, and priorities. An extension is number or pattern that the dialed number is to be matched against and a context is a collection of extensions (and possibly other included contexts too). Each extension will have one or more priorities, each of which appear on a separate line, and the priority sequence always starts with the priority "1".
If you have read Building Telephony Systems with Asterisk, you will know how to use extensions, priorities, contexts, and included contexts to handle incoming and outgoing calls as well as to set up features such as:
Call Queues
Call Parking
Direct Inward Dialling
Voicemail
Automated Phone Directory
Conference Rooms
In this chapter, we will build on this knowledge by looking at:
Significant updates since Asterisk 1.2
Pattern ordering within and between contexts
Extending the dialplan with variables
The
DEVSTATE()
functionThe
SYSTEM
application
We will then use this knowledge to provide examples of:
Advanced call routing with the
DEVSTATE()
functionCall routing based on the time of the day
Using multiple ADSL lines within Asterisk to boost call capacity
The dialplan is primarily defined in the extensions.conf
file. This can also include additional files that are added into it using the #include
directive. For instance, systems using the FreePBX GUI will have extensions_additional.conf, extensions_custom.conf
, and extensions_override_freepbx.conf
as standard files, which have been added using #include
into the extensions.conf
file. We must also remain aware of files such as the features.conf
file, as they also include numbers that can be dialed such as codes for Pickup and Call Parking, and so form part of the dialplan.
A list of standard and optional Asterisk configuration files can be found at http://www.voip-info.org/wiki/view/Asterisk+config+files.
The dialplan is primarily defined in the extensions.conf
file. This can also include additional files that are added into it using the #include
directive. For instance, systems using the FreePBX GUI will have extensions_additional.conf, extensions_custom.conf
, and extensions_override_freepbx.conf
as standard files, which have been added using #include
into the extensions.conf
file. We must also remain aware of files such as the features.conf
file, as they also include numbers that can be dialed such as codes for Pickup and Call Parking, and so form part of the dialplan.
A list of standard and optional Asterisk configuration files can be found at http://www.voip-info.org/wiki/view/Asterisk+config+files.
Being familiar with Asterisk, you will have a good working understanding of extensions and contexts already. They are, of course, the very heartbeat of Asterisk, and as such they are probably subject to the most change from version to version, as Asterisk evolves to cater for new hardware, software, and more complex working practices. So let's have a quick review of extensions and contexts, pointing out significant changes in versions 1.4 and 1.6, before we proceed to the more advanced techniques and uses.
Within the dialplan, matching can be either direct or partial against a pattern. Normally in a PBX, these patterns are numeric. But with Asterisk, they can also be alphanumeric or even just alpha. For example 2000, DID01234123456
, and Main_number
are all valid extensions. As very few phones contain alphabetic keys, the last two are typically only used for incoming DID channels. For the majority of this chapter, we will stick to numeric patterns.
Let's start to explore pattern matching by looking at an extremely simple dialplan:
[context_1] exten => 123,1,Answer() exten => 123,n,SayDigits(999${CALLERID(num)}) exten => 123,n,Hangup()
In this dialplan, when a user with a context of context_1
dials 123
, they will hear 999 and their caller ID will be read back to them.
Now let's look at a slightly more complex context:
[context_1] exten => _1X.,1,Answer() exten => _1X.,n,SayDigits(${EXTEN}${CALLERID(num)}) exten => _1X.,n,Hangup() exten => 123,1,Answer() exten => 123,n,SayDigits(123${CALLERID(num)}) exten => 123,n,Hangup()
You might expect that 123
would match against the _1X
. extension, as that appears first in the context. However, the way Asterisk orders the dialplan when loading means that exact matches are checked for before pattern matches. Hence if you dial 123, it matches against the 123 pattern first and not the _1X
. pattern. This pattern would only route the call if an exact match did not exist in the context.
Note
It is sensible not to use the pattern _
. as a catch-all pattern, as this will catch the Asterisk special extensions like i, t, h
as well. It is far better to use the _X
pattern.
Once understood, pattern matching is pretty straightforward and does what we expect. However, if you introduce included contexts into the mix, things may work in a way you did not expect and the order needs to be thought through carefully. In particular, it's crucial to understand that Asterisk only checks included contexts after checking for exact matches and pattern matches in the local context. The following example illustrates this:
[context_1] include => context_2 exten => _1X.,1,Answer() exten => _1X.,n,SayDigits(${EXTEN}${CALLERID(num)}) exten => _1X.,n,Hangup() include => context_3 exten => 123,1,Answer() exten => 123,n,SayDigits(123${CALLERID(num)}) exten => 123,n,Hangup()
The above dialplan is sorted internally by Asterisk shown as follows, and you can see that though the included contexts are at the top and in the middle, the local context is read first, then the included contexts are read in the order that they were added. Hence, in this case, a dial string of 122
would be matched by the _1X
. pattern before the included contexts are searched.
'123' => 1. Answer() 2. SayDigits(123${CALLERID(num)}) 3. Hangup() '_1X.' => 1. Answer() 2. SayDigits(${EXTEN}${CALLERID(num)}) 3. Hangup() Include => 'context_2' Include => 'context_3'
Note
If you have a catch-all pattern in your dialplan, consider putting it into a separate context. You can then use the include
directive to append that context to the end of the active context, thus ensuring that all of the other pattern matching is attempted first.
One of the most powerful tools you will use on the Asterisk command line is dialplan show <exten>@<context>
. For example:
dialplan show 122@context_1
This will show you the matching order that Asterisk will use for the given extension in the specified context, and if there are matches in any included contexts, those contexts will be explicitly identified.
Finally, in a context you may have a switch
statement, which includes the dialplan of an external system into the local dialplan. In essence, it's an include
for remote systems. Though typing dialplan show
will always show the switch
statement at the bottom, the defined context on the remote system is searched after the local context on your system and before any local included contexts! So again, you have to be very careful as to what is the context on the remote system as this will be searched before your included contexts.
The syntax of the switch
state is as follows:
switch =>IAX2/user:[key]@server/context
The user
and key
are defined in the called server's iax.conf
file, and the context is, of course, in the server's dialplan.
In our examples so far we could have achieved the desired results very easily without the use of multiple contexts. The simple functionality we have looked at could be carried out in a single, all-encompassing context. In practice, this approach could be applicable for systems with a very limited number of users and trunks, and with very restricted functionality, as there may not be a need to restrict the calling habits of a subset of users.
Use of contexts becomes desirable when we need to offer different options to different users. This is likely to be most applicable in medium and large companies, where you may have "users" ranging from the CEO down to an emergency phone in a lift. However, it can also be the case in smaller companies, where you might want to restrict home workers from making international calls for instance. When you get many different types of users, writing a distinct dialplan for each becomes problematic. The sheer size and complexity of the dialplan will make code management very complicated.
To simplify things, we first need to think about what makes the dialplan for each extension different. Then we need to think about what remains the same for each extension, as this needs to be made to work as well. What we often find is that most of these differences can be stored and called in two main ways:
We will come to variables shortly, but the grouping of extensions into contexts allows us to separate concise and distinct functions from each other. In doing so, we can control very tightly which contexts are used in each scenario, and also implement one "master" copy of each distinct function, aiding maintenance of the code.
To illustrate, let's expand our context a bit and use call barring as an example. We will initially have three levels for this example local, national, and international.
These are defined as follows:
Any number starting with a 1-9 is local
Anything starting with a 00 is international.
Anything else starting with a 0 is national or a mobile number.
This is a simplified example, and uses the UK format of dial prefixes.
We have in this example three contexts local_num, national_num
and international_num
. These would correspond to the levels of access we have decided on for our users. For example, an executive phone would be allowed access to all numbers whereas a phone on the shop floor may only be allowed access to local numbers.
We will create the three contexts shown as follows. All we are doing in our example is reading back 1, 2, or 3 to indicate the pattern that has been matched followed by the number dialed ${EXTEN}
.
[local_num] Exten => _Z.,1,Answer() Exten => _Z.,n,SayDigits(1${EXTEN}) Exten => _Z.,n,Hangup() ; [national_num] Exten => _0Z.,1,Answer() Exten => _0Z.,n,SayDigits(2${EXTEN}) Exten => _0Z.,n,Hangup() ; [international_num] Exten => _00X.,1,Answer() Exten => _00X.,n,SayDigits(3${EXTEN}) Exten => _00X.,n,Hangup()
For each context we could write an ordered list to cover all patterns, but it is much neater to create a master context for each user. For example:
[local] Include => local_num [national] Include => national_num Include => local_num [international] Include => international_num Include => national_num Include => local_num
Therefore, in the previous example, a user with the national
context can dial a normal national number, but not an international number. A user with the international
context has the ability to dial both numbers.
This is a pretty simple example with just three level of access, but the modular nature due to the use of contexts allows us to expand it very quickly and easily. For example, we have a user 1000
(our CEO) and he can dial internationally. We also have 1098
and 1099
, which are users on the shop floor, and can dial reception and the emergency services.
In this example, we give our CEO a context of [supauser],while
the shop floor has a context of [emergencyuser]
.
The [supauser]
context has to be able to dial everything, so it looks like this:
[supauser] include => premium_num ; allows dialing to premium rate numbers include => international_num ; allows international dialing include => national_num ; allows national calls include => mobile_num ; allows calls to mobile phones include => local_num ; allows local rate calls include => free_num ; allows free calls such as 800 or operator services include => internal_num ; allows the calling of extensions include => emergency ; allows calls to the emergency services include => default ; allows access to system features
The shop floor just has the following context:
[emergencyuser] include => emergency ; allows calls to emergency services reception.
As you can see, we can mix and match these contexts to cover many different types of extensions. Although you may be asking, "Will this really save me time?" well, let's look at two examples. Firstly, our supplier reduces the cost of UK 0870 numbers to free in the evenings as has happened in the UK with BT(British Telecom). Secondly, we also want the shop floor phone (1099) to be able to dial extensions and toll free calls, but not change the dialplan for 1098
.
We will deal with the simplest of these extensions (1099) first. All we need to do is change the context associated with this user to a new context called [freeuser]:
[freeuser] include => free_num ; allows calls to free numbers include => internal_num ; allows the calling of extensions include => emergency ; allows calls to the emergency services include => default ; allows access to system features .
This is a fast and easy change, which will have no effect on other shop floor users.
And to the change to 0870 numbers, this once again can be put into effect very simply. The only change is that evening and weekend calls are now free. Therefore, we could put it into a [free]
context. Although, it isn't always free. It is free only at weekends which would not be suitable. Hence, for this we use the GotoIfTime
application, which sets the context, extension, and priority in the channel based on the system time, day, date, and month supplied by the OS.
By adding the following to the free context, users can now dial 0870 numbers at the defined times.
exten => _0870XXXXXXX,1,GotoIfTime(17:59-08:00,mon-fri,*,*?national, ${EXTEN},1) exten => _0870XXXXXXX,1,GotoIfTime(*,sat-sun,*,*?national,${EXTEN},1)
In this case, we have made a change for all users who also have a context allowing both local and free calls (as their context includes the free context).
The GotoIfTime()
application can introduce some powerful functionality into your dialplan if used properly. An example that follows is for a support company where calls are routed to the call centre or staff member on call at a specific time. The customer had centers round the globe and we routed the calls to whichever center was open at that time of day.
[folthesun] ; ;This section sets the constants and variables for numbers and times ;Nine timezones are defined to allow for 4 a day and sat and sun working ;At present there are 6 destinations for NA AU and EMEA ; exten => s,1,set(__tzone1=00:00-07:59) exten => s,n,set(__tzone2=08:00-17:30) exten => s,n,set(__tzone3=17:31-23:59) exten => s,n,set(__tzone4=17:31-23:59) exten => s,n,set(__tzone5=00:00-23:59) exten => s,n,set(__tzone6=00:00-23:59) exten => s,n,set(__tzone7=00:00-23:59) exten => s,n,set(__tzone8=00:00-23:59) exten => s,n,set(__tzone9=00:00-23:59) ; exten => s,n,set(_dest1=01234123456) ;dest1 emea_pager exten => s,n,set(_dest2=001765412345) ;dest2 na_pager exten => s,n,set(_dest3=006165453457) ;dest3 au_pager exten => s,n,set(_dest4=08441231234) ;dest4 uk_no exten => s,n,set(_dest5=001744519651) ;dest5 na_no exten => s,n,set(_dest6=006118954654) ;dest6 au_no ; exten => s,n,set(dialpre=9) ;dialing prefix ; exten => s,n,set(dialcon=international) ;dialing context ; exten => s,n,Goto(ftstimeing,s,1) ; [ftstimeing] ; ;This sections runs though the days of the week and checks the time ;against DOW and time ; exten => s,1,GotoIfTime(${tzone1}|mon|*|*?dest1,1) exten => s,n,GotoIfTime(${tzone2}|mon|*|*?dest4,1) exten => s,n,GotoIfTime(${tzone3}|mon|*|*?dest5,1) exten => s,n,GotoIfTime(${tzone4}|mon|*|*?dest5,1) exten => s,n,GotoIfTime(${tzone1}|tue|*|*?dest1,1) exten => s,n,GotoIfTime(${tzone2}|tue|*|*?dest4,1) exten => s,n,GotoIfTime(${tzone3}|tue|*|*?dest5,1) exten => s,n,GotoIfTime(${tzone4}|tue|*|*?dest5,1) exten => s,n,GotoIfTime(${tzone1}|wed|*|*?dest1,1) exten => s,n,GotoIfTime(${tzone2}|wed|*|*?dest4,1) exten => s,n,GotoIfTime(${tzone3}|wed|*|*?dest5,1) exten => s,n,GotoIfTime(${tzone4}|wed|*|*?dest5,1) exten => s,n,GotoIfTime(${tzone1}|thu|*|*?dest1,1) exten => s,n,GotoIfTime(${tzone2}|thu|*|*?dest4,1) exten => s,n,GotoIfTime(${tzone3}|thu|*|*?dest5,1) exten => s,n,GotoIfTime(${tzone4}|thu|*|*?dest5,1) exten => s,n,GotoIfTime(${tzone1}|fri|*|*?dest1,1) exten => s,n,GotoIfTime(${tzone2}|fri|*|*?dest4,1) exten => s,n,GotoIfTime(${tzone3}|fri|*|*?dest5,1) exten => s,n,GotoIfTime(${tzone4}|fri|*|*?dest5,1) exten => s,n,GotoIfTime(${tzone5}|sat|*|*?dest1,1) exten => s,n,GotoIfTime(${tzone6}|sun|*|*?dest1,1) ; ;Fall through point exten => s,n,Goto(dest1,1) contextGotoIfTime() application, using; ;Dialed using the Local channel so call handling is observered ; exten => dest1,1,Noop(Calling ${dest1}) exten => dest1,n,Dial(Local/${dialpre}${dest1}@${dialcon}) exten => dest1,n,Hangup() exten => dest2,1,Noop(Calling ${dest2}) exten => dest2,n,Dial(Local/${dialpre}${dest2}@${dialcon}) exten => dest2,n,Hangup() exten => dest3,1,Noop(Calling ${dest3}) exten => dest3,n,Dial(Local/${dialpre}${dest3}@${dialcon}) exten => dest3,n,Hangup() exten => dest4,1,Noop(Calling ${dest4}) exten => dest4,n,Dial(Local/${dialpre}${dest4}@${dialcon}) exten => dest4,n,Hangup() exten => dest5,1,Noop(Calling ${dest5}) exten => dest5,n,Dial(Local/${dialpre}${dest5}@${dialcon}) exten => dest5,n,Hangup() exten => dest6,1,Noop(Calling ${dest6}) exten => dest6,n,Dial(Local/${dialpre}${dest6}@${dialcon}) exten => dest6,n,Hangup() exten => i,1,Hangup() exten => t,1,Hangup() exten => h,1,Hangup()
This can be expanded to include public holidays, if required. It can be possible to handle many years' public holidays in one line. For example, between the years 2009 and 2016, the UK's summer public holiday falls on the dates between the 25th and 31st of August and is always a Monday. Therefore, we have something like this:
GotoIfTime(*,Mon,25-31,Aug?dest1,1)
This will catch all UK summer public holidays, and as there are no other Mondays in August clashing with these dates, it's a set-and-forget for many years (just don't forget to change it after 2016!). The same goes for the majority of other public holidays except for Easter.
For these variable dates, we can resort back to the internal database to store the details and then use the GotoIf()
application to check if the date is a holiday.
Variables are key to making the dialplan and system work in a manner that a user expects. The user would expect the system to know everything they have set on their extension, and not have to enter codes or dial special access numbers.
There are a number of places in which variables can be stored including the dialplan, sip.conf, iax.conf, chan_DAHDI.conf
(in version1.6), and the Asterisk database (AstDB). For example, if we have a number of static dial strings we wish to store for each type of call and carrier we use, and then use them in a number of sections, the [globals]
section of the extensions.conf
file is the obvious place to declare them. If we wish to set a variable when a call is initiated from a SIP device, external caller ID or account codes are a good example, the setvar
command in the sip.conf
file is ideal for that purpose. Just remember that it won't work for calls sent to that device just when the calls are made. Finally, the AstDB is great for variables that are more transient in nature, such as call counts.
On occasion, when using complicated dialplans you may wish for a variable's value to be kept as the call progresses. This is achieved by adding a _
[underscore] or a __
[double underscore] before the variable name.
A single _
will cause that variable to be inherited into the channel that started from the original channel, for example:
Set(_name1=value1)
If you want the variable to be inherited to all child channels indefinitely, then add __
before the variable name. For example:
Set(__name2=value2)
This should not be confused with setting the variable with the g
option, as this sets it as a global variable. Doing so makes the variable available to all channels globally.
So, you may ask "why might we store dial strings as a variable?" The simple reason is that it allows a minimal amount of code for dialing all numbers, but still allows for different classes of restriction, by which we mean allowing different users to have different restrictions in what they can and cannot dial.
To pass these variables we will use a macro. Macros are like a template that we can use for repeated tasks, and they allow the passing of variables in an ordered fashion to the macro context. The call will jump to the s extension. The calling extension, context, and priority are stored in ${MACRO_EXTEN}, ${MACRO_CONTEXT}
, and ${MACRO_PRIORITY}
respectively. Arguments passed are accessed as ${ARG1}, ${ARG2}
, and so on within the Macro. While a Macro is being executed, it becomes the context, so you must be able to handle the h, i
, and t
extensions if required within that context.
Let's build our small macro dialplan. We have a variable defined in the globals
section of the extensions.conf
file as follows:
[globals] INT_CALL=IAX2/username@peer_out/ INT_CALL_ID=01234123456 ; default international callerID INT_CALL_LIMIT=5 ; Limit on the number of calls
In the context that we use for dialing, we have:
; International long distance through trunk exten => _90.,1,Macro(outdial,${INT_CALL})
Here, we have defined the macro we are going to pass the call to, along with a single variable we defined in the globals
section (the value of the calling extension can be retrieved within the macro by using ${MACRO_EXTEN})
.
The macro context looks like this:
[macro-outdial] exten => s,4,Dial(${ARG1}${MACRO_EXTEN:1},180)
This is the same as the dial string:
exten => s,4,Dial(IAX2/username@peer_out/01234123456,180)
We have seen that we can pass one dial string, but let's now pass other variables to the Dial()
application, such as a backup route for outgoing calls, and the caller ID we want to use for the call.
exten => _90.,1,Macro(outdial,${INTCALL},${INT_CALL_ID},${INT_CALL_LIMIT}) [macro-outdial] exten => s,1,Set(GROUP()=OUTBOUND_GROUP) ;Set Group exten => s,2,GotoIf($[${GROUP_COUNT(OUTBOUND_GROUP)} > ${ARG3}]?103) ;Exceeded? exten => s,3,Set(CALLERID(num)=${ARG2}) exten => s,4,Dial(${ARG1}${MACRO_EXTEN:1},180)
Now it's time to bring some .conf
file variables into the mix. Using the setvar
facility in the sip.conf, iax.conf
and chan_dahdi.conf
files, we can set variables specific for every user such as unique caller ID, call limits, whether we want to record the call, account codes. Basically, anything that will help you handle calls more efficiently.
setvar=account_code=2206 setvar=callidnum=01234123456 setvar=tenantID=2
Note
One problem using .conf
files is that the relevant channel module needs to be reloaded after a change, and in the case of DAHDI, Asterisk would need to be restarted. This isn't too much of an issue but the need can be removed by using the AstDB for storing commonly changed settings, such as caller ID and recordings.
You may think that all this variable use is over-complicated, but consider a system that supports multiple tenants. Using these techniques, you will only need one dialplan for multiple tenants instead of one per tenant. Simply set the tenantID
in the relevant .conf
file and then store the tenants' features in the globals
section of the dialplan and in the AstDB, and all calls will go out as that tenant group. The concept is the same for other scenarios, such as departments that require cross charging of telephone costs.
Setting and retrieving variables in the AstDB is very simple and achieved through the use of the Set()
application. Variables can exist in splendid isolation or be grouped into families. The syntax for setting a variable is:
Set(DB(family/variable)=value)
Retrieving the variable's value is equally as simple:
Set(result=${DB(family/variable)})
So, let's have a look at how we can implement a simple multi-tenant dialplan using multiple variable stores:
INT_CALL1=IAX2/username@peer_out_1/ INT_CALL2=IAX2/username@peer_out_1/ INT_CALL_LIMIT1=5 ; Limit on the number of calls INT_CALL_LIMIT2=5 ; Limit on the number of calls exten => _90[1-2]XXXXXXXXX,1,Set(INTCALL=INTCALL${tenantID}) exten => _90[1-2]XXXXXXXXX,n,Set(INT_CALL_LIMIT=INT_CALL_LIMIT${tenantID}) exten => _90[1-2]XXXXXXXXX,n,Macro(outdial,${INTCALL}, ${callidnum},${INT_CALL_LIMIT})
As we can see, we have been able to cut down the amount of code and make it universal for different types of users and systems. Using a macro lets us pass an ordered list of arguments. It is easiest to think of macro arguments as a list of variables since they are handled the same way.
Note
Due to the way macro is implemented, it executes the priorities contained within it via a sub-engine, and a fixed per-thread memory stack allowance. Macros are limited to seven levels of nesting. It can be possible that stack-intensive applications in deeply-nested macros could cause Asterisk to crash. Take this into account and be very careful when nesting macros.
In this section, we are going to look at the DEVSTATE()
function and the System()
application. We will see how we can check and change the "status" of devices with the DEVSTATE()
function and use the system application to cause scripts on the server to be run.
The func_devstate
application allows the status of a peer to be known before you dial it. This is very useful in many applications. We will cover a few of them here but you will be able to find many more.
The func_devstate
application is part of Asterisk 1.6, but Russell Bryant (of Digium) has a back-ported version for Asterisk 1.4. This can now be found at:
http://svn.digium.com/community/russell/asterisk-1.4/func_devstate-1.4/func_devstate.c
For most Linux distributions, installing the function is pretty simple:
cd /usr/src/asterisk/funcs
wget http://svn.digium.com/community/russell/asterisk-1.4/func_devstate-1.4/func_devstate.c
cd ..
make clean
./configure
make menuselect
Choose option -> 6. Dialplan Functions
Then make sure that you have an entry like 8.func_devstate
make
make install
Note
If under Dialplan Functions
, the DEVSTATE()
function does not show up, you will need to edit the menuselect-tree
to add it.
<member name="func_devstate" displayname="Gets or sets a device state in the dialplan" remove_on_change="funcs/func_devstate.o funcs/func_devstate.so">
Then compile Asterisk as shown previously.
The DEVSTATE()
function is versatile, allowing us to check and/or set the status of a device, as its name suggests. One very common use is to activate phone lamps, showing users if they have set a feature such as DND or call forwarding. In the following examples, we will look at both setting and checking methods:
The function reports on, or can set, the following states:
NOT_INUSE INUSE BUSY INVALID UNAVAILABLE RINGING RINGINUSE ONHOLD
The application can be used here to check that an outgoing peer is "available" and not "down", before you send a call to it. This is useful if you have peers or remote systems that are on variable quality connections.
exten => _90.,1,Macro(outdial,${PRIDIAL},${INT_CALL_ID},${INT_CALL_LIMIT},${BAKDIAL},${PRIPEER}) [macro-outdial] exten => s,1, Set(GROUP()=OUTBOUND_GROUP) ;Set Group exten => s,2,GotoIf($["${DEVSTATE(${ARG5})}"="UNAVALIABLE"]?s,7) exten => s,3, GotoIf($[${GROUP_COUNT(OUTBOUND_GROUP)} > ${ARG3}]?103) exten => s,4, Set(CALLERID(num)=${ARG2}) exten => s,5, Dial(${ARG1}${MACRO_EXTEN:1},180) exten => s,6, Hangup() exten => s,7, Dial(${ARG4}${MACRO_EXTEN:1},180) exten => s,8, Hangup() exten => s,n, Noop(call Limit exceeded)
This example is an expansion of our previously used macro and has a couple of extra arguments passed to it. This makes it very flexible, as the backup peer can be different for each dialed number.
The application lets you see if extensions are busy or "out of service" before calling them. This can be useful for handsets that support call waiting, but you don't want to fully disable it for all calls. Before calling the extension, you can check to see if the extension has call waiting enabled and then, depending on the result, check the device status as follows:
exten => 2XXX,1,Macro(dialext) [Macro-dialext] exten => s,1,NoOp(SIP/${MACRO_EXTEN} has state ${DEVSTATE(SIP/$ {MACRO_EXTEN})}) exten => s,n,Set(CW=${DB(CW/${MACRO_EXTEN})}) exten => s,n,GotoIf($["${CW}"="YES"]?dial) exten => s,n,GotoIf($["${DEVSTATE(SIP/${MACRO_EXTEN})}"!="NOT_INUSE"]? s-BUSY,1) exten => s,n(dial),Dial(SIP/${MACRO_EXTEN},35) exten => s,n,Goto(s-BUSY,1) exten => s-BUSY,1,Voicemail(${MACRO_EXTEN},b) exten => s-BUSY,n,Hangup()
In the previous example, we have used the internal database to set the flag to say if call waiting is enabled or not. If call waiting is anything other than YES
, the status of the extension will be checked, otherwise the DEVSTATE
isn't checked and the extension is just called. As we will see next, we can expand this to light a BLF (Busy Lamp Field) key as well, to give a visual indication to users of the device status.
We can also use the DEVSTATE()
function to set BLF lights on and off, a very simple but highly effective feature. This is particularly helpful if you are using the dialplan for setting call forwards or DND. It can also show if a call center agent is logged in or not, on their phone.
To illustrate this functionality, we have a very simple example showing how to turn the light on and off. It uses one number to toggle the light status and is not specific for the particular phone all phones dial the same number and it is the CHANNEL
variable, which is used to set it for a specific phone. In this example, we have two hints 4078
and 4071
, and these are linked to extensions 5078
and 5071
.
Using this code and adding additional code to set the database key for call waiting (as we have already covered) would give the phone user a visual indication as to whether call waiting is set or not.
exten => 4071,hint,Custom:light5071 exten => 4078,hint,Custom:light5078 exten => 1236,1,Goto(1236-${DEVSTATE(Custom:light${CHANNEL:4:4})}|1) exten => 1236-UNKNOWN,1,Goto(1236-NOT_INUSE|1) exten => 1236-NOT_INUSE,1,Noop(Turn ${CHANNEL:4:4} light on) exten => 1236-NOT_INUSE,n,Set(DEVSTATE(Custom:light${CHANNEL:4:4})= INUSE) exten => 1236-INUSE,1,Noop(Turn ${CHANNEL:4:4} light off) exten => 1236-INUSE,n,Set(DEVSTATE(Custom:light{CHANNEL:4:4})= NOT_INUSE)
By using userevent
, you can also send out manager events to update the Flash Operator Panel. The following would set the CW
flag for the Flash Operator Panel for our extension and change the icon to reflect the status.
exten => 1236-NOT_INUSE,2,UserEvent(ASTDB|Channel: ${CHANNEL}^Family: CW^Value: SET ^)
Note
There is also a version of DEVSTATE()
called EXTSTATE()
. It is a modified version of the DEVSTATE()
function that returns the state of an extension, rather than the state of a device. This means you can write dialplan logic based on the state of an extension (in use, ringing, on hold, and so on). The extension just needs to have a hint so we can determine which devices to check.
We're going to have a look at how DEVSTATE()
has been used to address an unusual situation. A call-centre customer wished to temporarily increase their outgoing call capacity, in this case by 20 concurrent calls, to cater for a particular project. However, in their location, with their budget and given the temporary need for extra capacity, the only effective means of boosting bandwidth is to utilize multiple ADSL circuits. In other words, SDSL and leased line circuits were too costly for consideration. Therefore, there was a need to bond multiple ADSL circuits together within Asterisk, in order to provide a single high-bandwidth circuit for outbound calls.
It may seem obvious, but when calculating call capacity with ADSL circuits, the figure we're interested in is the lower of the upload/download speeds. It doesn't matter if you have a superfast 20 MB DSL circuit, chances are that you only have an uplink speed of 800 kbps or less. It must also be remembered that once traffic exceeds 50% of the link speed, collisions and latency are likely to become an issue and must be addressed. This gives you a theoretical limit of up to 10 uncompressed calls per circuit if you're really lucky. Of course, with the GSM codec you can get a lot more, but at the cost of audio quality, which your customer is unlikely to accept. They expect PSTN quality and nothing less. If bandwidth utilization is on the borderline with an uncompressed codec, it can be advantageous to use a commercial (non-free) codec such as G729, which is obtainable from Digium.
Using the criteria already discussed and assuming a 500 kbps uplink speed, it was determined that four broadband circuits were needed. This may sound expensive, but in reality it's not, when you consider that the alternative was 20 channels of a PRI (ISDN 30), which worked out at twice the cost of four PSTN lines with broadband. As we'll see later in the sales appendix, a major benefit of VoIP is that the customer is paying much less for line rental. This solution only reinforces that benefit.
We are going to describe a solution that used four broadband circuits, but another advantage of this approach is that it is very scalable. To illustrate, a system has been set up for a charity in the UK that had 75 agents placing thousands of calls a day on just eight broadband circuits.
Note
This example was tested and proven using Asterisk 1.4.19.2, and should work with releases up to 1.4.19.2 - it’s operation cannot be guaranteed in other versions.
Once the circuits are delivered, you will end up with four routers connected to the broadband service of your choice. Each router will have a unique IP address. In our case, we shall assume they are as follows:
192.168.1.1 192.168.1.2 192.168.1.3 192.168.1.4
We will also assume that the VoIP ITSP has multiple IP addresses that you can connect to, though if not, you can probably do some clever address translation in the routers.
Let's assume our VoIP ISP has provided us with the following external IP addresses:
88.88.88.81 88.88.88.82 88.88.88.83 88.88.88.84
Within Linux, you can easily set up different gateway addresses for a given destination. The file that manages the gateways is normally called ifup-routes
in the /etc/sysconfig/network-scripts
directory.
To configure the gateways, we append the following to the ifip-routes
file:
/sbin/route add 88.88.88.81 gw 192.168.1.1
/sbin/route add 88.88.88.82 gw 192.168.1.2
/sbin/route add 88.88.88.83 gw 192.168.1.3
/sbin/route add 88.88.88.84 gw 192.168.1.4
Taking the last entry, what we're saying is that for all traffic to 88.88.88.84
, route it via the router at 192.168.1.4
.
If you reboot and run the command route n
in a terminal session, you'll see these routes in place.
We now turn our attention to the Asterisk configuration. When we make a call, we're going to keep count of how many calls we have on a broadband line, so that when the circuit is "full", we can move on to the next available one.
Firstly, in the extensions.conf
file, we need to declare a variable that sets the maximum concurrent calls we will allow through any one router.
MAXVOIPCALLS=5 ; Maximum Calls we allow over IP (outbound)
We've previously set up four entries in the iax.conf
file called iaxline1
through to iaxline4
. They have identical entries, with the exception on the host=
line. Here we assign the appropriate external IP address, that is, 88.88.88.81
for iaxline1
and so on.
Now, we need to declare the IAX channels as follows:
AIXVOIPOUT = IAX2/iaxline1 AIXVOIPOUT1 = IAX2/iaxline2
As expected, we use a macro to manage the call routing. The example below only shows two lines for the sake of brevity.
[macro-voiptrunk] exten => s,1,Noop(Number of Broadband calls) ;Use devstate to test the availability of the trunks. You could put some code in, to use an alternative if they are off line exten => s,2,Noop(Trunk 1 ${DEVSTATE(${AIXVOIPOUT})} -> ${GROUP_COUNT(VOIPTRUNKS@list1)}) exten => s,3,Noop(Trunk 2 ${DEVSTATE(${AIXVOIPOUT1})} -> ${GROUP_COUNT(VOIPTRUNKS@list2)}) exten => s,4,GoToIf($[${GROUP_COUNT(VOIPTRUNKS@list1)} < ${MAXVOIPCALLS}]?10:20) ;Have we exceeded the max calls per trunk? If so, jump to Extension 20 and use second trunk exten => s,10,gotoif($[${DEVSTATE(${AIXVOIPOUT})} = UNAVAILABLE]?20:11) ;Here we test to see if the trunk is available, if it's gone off-line, we use the second trunk exten => s,11,Set(GROUP(list1)=VOIPTRUNKS) ;increment the usage count. exten => s,12,noop(${DEVSTATE(${AIXVOIPOUT})}) exten => s,13,Noop(Trunk 1-> ${GROUP_COUNT(VOIPTRUNKS@list1)}) exten => s,14,Dial(${AIXVOIPOUT}/${MACRO_EXTEN}) exten => s,15,Goto(s-${DIALSTATUS},1) exten => s,20,GoToIf($[${GROUP_COUNT(VOIPTRUNKS@list2)} < ${MAXVOIPCALLS}]?21:40) exten => s,21,Set(GROUP(list2)=VOIPTRUNKS) exten => s,22,Noop(Number of Broadband calls) exten => s,23,Noop(Trunk 2-> ${GROUP_COUNT(VOIPTRUNKS@list2)}) exten => s,24,Dial(${AIXVOIPOUT1}/${MACRO_EXTEN}) exten => s,25,Goto(s-${DIALSTATUS},1) exten => s,40,Congestion(15) ; No more lines left
Priorities "2" and "3" use the DEVSTATE()
function to test the availability of the broadband lines. If a line is down, UNAVAILABLE
will be returned. At "4", we look to see if we've exceeded max calls on this line. If we haven't, we'll place a call on the first router, otherwise go to the next. "11" records the in-use count and increments it for the given router (list1).
What happens, overall, is that the first 5 calls (set by the global MAXVOIPCALLS)
will go via router 1, the sixth will go via router 2. If in the meantime a call is dropped from router 1, the next call placed will go back to router 1, even if other calls are ongoing on router 2.
Finally, we need to add a call to the macro in our dialplan:
[outbound-national] exten => _0Z.,1,NoOp(national call) exten => _0Z.,2,Macro(voiptrunk)
The above technique is scalable. You can add as many broadband lines as you need. The end result is that you can say to your customer, "want more outgoing capacity? Just add another DSL line". However, it must not be forgotten that there may be more stable solutions such as SDSL and leased lines, depending on location.
The above example works really well for outbound calling, but not so well for inbound. If you own the server the customer is connecting to, then you can reverse the logic at your end. If you don't, then all you can do is allocate one router for inbound (register via an inbound router) and the rest for outbound.
The System()
application allows Asterisk to run Linux commands and shell scripts. What we will look at here is a simple hotdesking deployment script for asterisk. This type of deployment method is used by all commercial PBXs and is needed for any enterprise deployment of Asterisk. Hand editing filenames or even configuring phones via their web GUI will not be accepted by a customer or end user.
The dialplan is very simple. The user dials a code from his/her handset and is asked to enter a four-digit number (their hotdesk ID). The dialplan then stores this as a variable. It also sets the caller ID and the IP address for the set and passes these to the script.
[hotdesk_in] exten => s,1,Answer exten => s,n,Playback(privacy-thankyou) exten => s,n,Read(MY_EXTEN,access-code,4) exten => h,1,Set(MY_IP=${SIPPEER(${CALLERID(num)}:ip)}) exten => h,2,system(/usr/local/sbin/exten_in ${MY_EXTEN} ${CALLERID(num)} ${MY_IP})
With these three variables, the script then knows the handset's existing number, IP address, and the number it wants to be.
In addition, the script then performs an Address Resolution Protocol (ARP) lookup on the IP address to find the phone's MAC address. It needs this because, as in the example, we are using the phones config
file in the format of<MAC-ADDRESS>.cfg
, and we configure the sets via TFTP (Trivial File Transfer Protocol). Hence, as we know the MAC address we can copy the config
files to the correct name.
Firstly, we will copy the old<MAC-ADDRESS>.cfg
to a different file name. Then we copy the config
file for the extension number we wish the phone to be using the MY_EXTEN
variable we have passed to the script to define it to our new<MAC-ADDRESS>.cfg
. Now when the set reboots, it will pick up the new file. However, we want this to be automatic and with as many handsets as it can have. The sip notify
command does so when configured in the sip_notify.conf
file, in the case of Aastra handsets, as follows:
[aastra-check-cfg] Event=>check-sync Content-Length=>0
The following command will cause the phone to check the config
file for changes and reboot if any change is found:
/usr/sbin/asterisk -rx "sip notify aastra-check-cfg ${CALLERID}"
When it reboots, it will pick up its new configuration. By using scripts such as the previous one, you can speed up "moves, adds, and changes" and cut out the need for engineers to put out or replace handsets. It can also be used to provide a form of hotdesking with the user dialing a code to set the handset as theirs, and then log out when they leave (copying back the previous config
), thus returning the phone to its previous state.
In summary, we have looked at how to break down your dialplan into small, manageable contexts or objects. These can then be included into the dialplan to create a system with the flexibility to match any commercial PBX. We also looked at improving the security of the dialplan such that it is easy to manage who can dial where in an understandable way.
We looked at the many different ways that variables can be stored in the system and called upon when required, as well as seeing how they can interact with macros to make the dialplan more streamlined. Here we used one macro for many extensions.
We looked at the DEVSTATE()
function and the uses that it can be put to. These are not just (as it initially seems) for checking the status, but also a way to set the status and light a BLF key to show a feature is set.
We looked at time and day call routing, and how it can be used to route calls based on time and day. We also looked at the clever use of date ranges, so that we can future-proof our dialplans for holidays for many years to come.
And finally we looked at the System()
application and how this can be used for easing the deployment of handsets in an enterprise solution. In the next chapter, we shall focus on exploring network considerations.