Chapter 6. More Dialplan Concepts
For a list of all the ways technology has failed to improve the quality of life, please press three.
Alrighty. You’ve got the basics of dialplans down, but you know there’s more to come. If you don’t have the last chapter sorted out yet, please go back and give it another read. We’re about to get into more advanced topics.
Expressions and Variable Manipulation
As we begin our dive into the deeper aspects of dialplans, it is time to introduce you to a few tools that will greatly add to the power you can exercise in your dialplan. These constructs add incredible intelligence to your dialplan by enabling it to make decisions based on different criteria you want to define. Put on your thinking cap, and let’s get started.
Basic Expressions
Expressions are combinations of variables, operators, and values that you string
together to produce a result. An expression can test values, alter
strings, or perform mathematical calculations. Let’s say we have a
variable called COUNT
. In plain
English, two expressions using that variable might be “COUNT
plus 1” and “COUNT
divided by 2.” Each of these
expressions has a particular result or value, depending on the value
of the given variable.
In Asterisk, expressions always begin with a dollar sign and an opening square bracket and end with a closing square bracket, as shown here:
$[expression
]
Thus, we would write the above two examples like this:
$[${COUNT} + 1] $[${COUNT} / 2]
When Asterisk encounters an expression in a dialplan, it replaces the entire expression with the resulting value. It is important to note that this takes place after variable substitution. To demonstrate, let’s look at the following code:[80]
exten => 321,1,Set(COUNT=3) exten => 321,n,Set(NEWCOUNT=$[${COUNT} + 1]) exten => 321,n,SayNumber(${NEWCOUNT})
In the first priority, we assign the value of 3
to the variable named COUNT
.
In the second priority, only one application—Set()
—is involved, but three things actually happen:
Asterisk substitutes
${COUNT}
with the number3
in the expression. The expression effectively becomes this:exten => 321,n,Set(NEWCOUNT=$[3 + 1])
Asterisk evaluates the expression, adding
1
to3
, and replaces it with its computed value of4
:exten => 321,n,Set(NEWCOUNT=4)
The value
4
is assigned to theNEWCOUNT
variable by theSet()
application.
The third priority simply invokes the SayNumber()
application, which speaks the current value of the variable ${NEWCOUNT}
(set to the value 4
in priority two).
Try it out in your own dialplan.
Operators
When you create an Asterisk dialplan, you’re really writing code in a specialized scripting language. This means that the Asterisk dialplan—like any programming language—recognizes symbols called operators that allow you to manipulate variables. Let’s look at the types of operators that are available in Asterisk:
- Boolean operators
These operators evaluate the “truth” of a statement. In computing terms, that essentially refers to whether the statement is something or nothing (nonzero or zero, true or false, on or off, and so on). The Boolean operators are:
expr1
|
expr2
This operator (called the “or” operator, or “pipe”) returns the evaluation of
expr1
if it is true (neither an empty string nor zero). Otherwise, it returns the evaluation ofexpr2
.expr1
&
expr2
This operator (called “and”) returns the evaluation of
expr1
if both expressions are true (i.e., neither expression evaluates to an empty string or zero). Otherwise, it returns zero.expr1
{=, >, >=, <, <=, !=}
expr2
These operators return the results of an integer comparison if both arguments are integers; otherwise, they return the results of a string comparison. The result of each comparison is
1
if the specified relation is true, or0
if the relation is false. (If you are doing string comparisons, they will be done in a manner that’s consistent with the current local settings of your operating system.)
- Mathematical operators
Want to perform a calculation? You’ll want one of these:
- Regular expression operator
You can also use the regular expression operator in Asterisk:
expr1
:
expr2
This operator matches
expr1
againstexpr2
, whereexpr2
must be a regular expression.[81] The regular expression is anchored to the beginning of the string with an implicit^
.[82]If the match succeeds and the pattern contains at least one regular expression subexpression—
\(
...
\)
—the string corresponding to\1
is returned; otherwise, the matching operator returns the number of characters matched. If the match fails and the pattern contains a regular expression subexpression, the null string is returned; otherwise,0
is returned.
In Asterisk version 1.0 the parser was quite simple, so it required that you put at least one space between the operator and any other values. Consequently, the following might not have worked as expected:
exten => 123,1,Set(TEST=$[2+1])
This would have assigned the variable TEST
to the string “2+1”, instead of the
value 3
. In order to remedy that,
we would put spaces around the operator like so:
exten => 234,1,Set(TEST=$[2 + 1])
This is no longer necessary in Asterisk 1.2 or 1.4 as the expression parser has been made more forgiving in these types of scenarios, however, for readability’s sake, we still recommend the spaces around your operators.
To concatenate text onto the beginning or end of a variable, simply place them together in an expression, like this:
exten => 234,1,Set(NEWTEST=$[blah${TEST}])
Dialplan Functions
Dialplan functions allow you to add more power to your expressions; you can think of them as intelligent variables. Dialplan functions allow you to calculate string lengths, dates and times, MD5 checksums, and so on, all from within a dialplan expression.
Syntax
Dialplan functions have the following basic syntax:
FUNCTION_NAME
(argument
)
Much like variables, you reference a function’s name as above, but you reference a function’s value with the addition of a dollar sign, an opening curly brace, and a closing curly brace:
${FUNCTION_NAME
(argument
)}
Functions can also encapsulate other functions, like so:
${FUNCTION_NAME
(${FUNCTION_NAME
(argument
)})} ^ ^ ^ ^ ^^^^ 1 2 3 4 4321
As you’ve probably already figured out, you must be very careful about making sure you have matching parentheses and braces. In the above example, we have labeled the opening parentheses and curly braces with numbers and their corresponding closing counterparts with the same numbers.
Examples of Dialplan Functions
Functions are often used in conjunction with the Set()
application to either get or set the value of a variable. As a
simple example, let’s look at the LEN()
function. This function calculates the string length of its
argument. Let’s calculate the string length of a variable and read
back the length to the caller:
exten => 123,1,Set(TEST=example) exten => 123,n,SayNumber(${LEN(${TEST})})
The above example would evaluate the string example
as having seven characters, assign
the number of characters to the variable length, and then speak the
number to the user with the SayNumber()
application.
Let’s look at another simple example. If we wanted to set one of
the various channel timeouts, we could use the TIMEOUT()
function. The TIMEOUT()
function accepts one of three arguments: absolute
, digit
, and response
. To set the digit timeout with the
TIMEOUT()
function, we could use
the Set()
application, like
so:
exten => s,1,Set(TIMEOUT(digit)=30)
Notice the lack of ${ }
surrounding the function. Just as if we were assigning a value to a
variable, we assign a value to a function without the use of the
${ }
encapsulation.
A complete list of available functions can be found by typing
core show functions
at the Asterisk
command-line interface. You can also look them up in Appendix F.
Conditional Branching
Now that you’ve learned a bit about expressions and functions, it’s time to put them to use. By using expressions and functions, you can add even more advanced logic to your dialplan. To allow your dialplan to make decisions, you’ll use conditional branching. Let’s take a closer look.
The GotoIf() Application
The key to conditional branching is the GotoIf()
application. GotoIf()
evaluates an expression and sends the caller to a specific destination
based on whether the expression evaluates to true
or false
.
GotoIf()
uses a special
syntax, often called the conditional
syntax:
GotoIf(expression
?destination1
:destination2
)
If the expression evaluates to true, the caller is sent to
destination1
. If the expression evaluates
to false, the caller is sent to the second destination. So, what is
true and what is false? An empty string and the number 0 evaluate as
false. Anything else evaluates as true
.
The destinations can each be one of the following:
A priority label within the same extension, such as
weasels
An extension and a priority label within the same context, such as
123,weasels
A context, extension, and priority label, such as
incoming,123,weasels
Either of the destinations may be omitted, but not both. If the omitted destination is to be followed, Asterisk simply goes on to the next priority in the current extension.
Let’s use GotoIf()
in an
example:
exten => 345,1,Set(TEST=1) exten => 345,n,GotoIf($[${TEST} = 1]?weasels:iguanas) exten => 345,n(weasels),Playback(weasels-eaten-phonesys) exten => 345,n,Hangup() exten => 345,n(iguanas),Playback(office-iguanas) exten => 345,n,Hangup()
Note
You will notice that we have used the Hangup()
application following each
Playback()
application. This is
done so that when we jump to the weasels
label, the call stops before
execution gets to the office-
iguanas
sound file. It is becoming
increasingly common to see extensions broken up in to multiple
components (protected from each other by the Hangup()
command), each one acting as
steps executed following a GotoIf()
.
Typically when you have this type of layout where you end up wanting to limit Asterisk from falling through to the next priority after you’ve performed that jump, it’s probably better to jump to separate extensions instead of priority labels. If anything, it makes it a bit more clear when reading the dialplan. We could rewrite the previous bit of dialplan like this:
exten => 345,1,Set(TEST=1)
exten => 345,n,GotoIf($[${TEST} = 1]?weasels,1:iguanas,1); now we're going to
; extension,priority
exten => weasels,1,Playback(weasels-eaten-phonesys); this is NOT a label.
; It is a different extension
exten => weasels,n,Hangup()
exten => iguanas,1,Playback(office-iguanas)
exten => iguanas,n,Hangup()
By changing the value assigned
to TEST
in the first line, you
should be able to have your Asterisk server play a different
greeting.
Let’s look at another example of conditional branching. This
time, we’ll use both Goto()
and GotoIf()
to count down from 10 and then hang
up:
exten => 123,1,Set(COUNT=10) exten => 123,n(start),GotoIf($[${COUNT} > 0]?:goodbye) exten => 123,n,SayNumber(${COUNT}) exten => 123,n,Set(COUNT=$[${COUNT} - 1]) exten => 123,n,Goto(start) exten => 123,n(goodbye),Hangup()
Let’s analyze this example. In the first priority, we set the
variable COUNT
to 10
. Next, we check to see if COUNT
is greater than 0
. If it is, we move on to the next
priority. (Don’t forget that if we omit a destination in the GotoIf()
application, control goes to the
next priority.) From there we speak the number, subtract 1
from COUNT
, and go back to priority label
start
. If COUNT
is less than or equal to 0
, control goes to priority label goodbye
, and the call is hung up.
The classic example of conditional branching is affectionately known as the anti-girlfriend logic. If the Caller ID number of the incoming call matches the phone number of the recipient’s ex-girlfriend, Asterisk gives a different message than it ordinarily would to any other caller. While somewhat simple and primitive, it’s a good example for learning about conditional branching within the Asterisk dialplan.
This example uses the CALLERID
function, which allows us to
retrieve the Caller ID information on the inbound call. Let’s assume
for the sake of this example that the victim’s phone number is
888-555-1212:
exten => 123,1,GotoIf($[${CALLERID(num)} = 8885551212]?reject:allow) exten => 123,n(allow),Dial(Zap/4) exten => 123,n,Hangup() exten => 123,n(reject),Playback(abandon-all-hope) exten => 123,n,Hangup()
In priority 1, we call the GotoIf()
application. It tells Asterisk to
go to priority label reject
if the
Caller ID number matches 8885551212
, and otherwise to go to priority
label allow
(we could have simply
omitted the label name, causing the GotoIf()
to fall through). If the Caller ID
number matches, control of the call goes to priority label reject
, which plays back an uninspiring
message to the undesired caller. Otherwise, the call attempts to dial
the recipient on channel Zap/4
.
Time-Based Conditional Branching with GotoIfTime()
Another way to use conditional branching in your dialplan is with
the GotoIfTime()
application.
Whereas GotoIf()
evaluates an
expression to decide what to do, GotoIfTime()
looks at the current system
time and uses that to decide whether or not to follow a different
branch in the dialplan.
The most obvious use of this application is to give your callers a different greeting before and after normal business hours.
The syntax for the GotoIfTime()
application looks like
this:
GotoIfTime(times
,days_of_week
,days_of_month
,months
?label
)
In short, GotoIfTime()
sends
the call to the specified label
if the
current date and time match the criteria specified by
times
,
days_of_week
,
days_of_month
, and
months
. Let’s look at each argument in more
detail:
times
This is a list of one or more time ranges, in a 24-hour format. As an example, 9:00 A.M. through 5:00 P.M. would be specified as
09:00-17:00
. The day starts at 0:00 and ends at 23:59.Note
It is worth noting that times will properly wrap around. So if you wish to specify the times your office is closed, you might write
18:00-9:00
in thetimes
parameter, and it will perform as expected. Note that this technique works as well for the other components ofGotoIfTime
. For example, you can writesat-sun
to specify the weekend days.days_of_week
This is a list of one or more days of the week. The days should be specified as
mon
,tue
,wed
,thu
,fri
,sat
, and/orsun
. Monday through Friday would be expressed asmon-fri
. Tuesday and Thursday would be expressed astue&thu
.Note
Note that you can specify a combination of ranges and single days, as in:
sun-mon&wed&fri-sat
, or, more simply:wed&fri-mon
.days_of_month
This is a list of the numerical days of the month. Days are specified by the numbers
1
through31
. The 7th through the 12th would be expressed as7-12
, and the 15th and 30th of the month would be written as15&30
.months
This is a list of one or more months of the year. The months should be written as
jan
-apr
for a range, and separated with ampersands when wanting to include nonsequencial months, such asjan&mar&jun
. You can also combine them like so:jan-apr&jun&oct-dec
.
If you wish to match on all possible values for any of these
arguments, simply put an *
in for that
argument.
The label
argument can be any of the following:
A priority label within the same extension, such as
time_has_passed
An extension and a priority within the same context, such as
123,time_has_passed
A context, extension, and priority, such as
incoming,123,time_has_passed
Now that we’ve covered the syntax, let’s look at a couple of examples. The following example would match from 9:00 A.M. to 5:59 P.M., on Monday through Friday, on any day of the month, in any month of the year:
exten => s,1,GotoIfTime(09:00-17:59,mon-fri,*,*?open,s,1)
If the caller calls during these hours, the call will be sent to
the first priority of the s
extension in the context named open
. If the call is made outside of the
specified times, it will be sent to the next priority of the current
extension. This allows you to easily branch on multiple times, as
shown in the next example (note that you should always put your most
specific time matches before the least specific ones):
; If it's any hour of the day, on any day of the week, ; during the fourth day of the month, in the month of July, ; we're closed exten => s,1,GotoIfTime(*,*,4,jul?closed,s,1) ; During business hours, send calls to the open context exten => s,n,GotoIfTime(09:00-17:59|mon-fri|*|*?open,s,1) exten => s,n,GotoIfTime(09:00-11:59|sat|*|*?open,s,1) ; Otherwise, we're closed exten => s,n,Goto(closed,s,1)
Tip
If you run into the situation where you ask the question, “But
I specified 17:58 and it’s now 17:59. Why is it still doing the same
thing?” it should be noted that the granularity of the GotoIfTime()
application is only to a
two-minute period. So if you specify 18:00 as the ending time of a
period, the system will continue to perform the same way for an
additional minute, until 18:01:59.
Voicemail
One of the most popular (or, arguably, unpopular) features of any modern telephone system is voicemail. Naturally, Asterisk has a reasonably flexible voicemail system. Some of the features of Asterisk’s voicemail system include:
Unlimited password-protected voicemail boxes, each containing mailbox folders for organizing voicemail
Different greetings for busy and unavailable states
Default and custom greetings
The ability to associate phones with more than one mailbox and mailboxes with more than one phone
Email notification of voicemail, with the voicemail optionally attached as a sound file[83]
Voicemail forwarding and broadcasts
Message-waiting indicator (flashing light or stuttered dial tone) on many types of phones
Company directory of employees, based on voicemail boxes
And that’s just the tip of the iceberg! In this section, we’ll introduce you to the fundamentals of a typical voicemail setup.
The voicemail configuration is defined in the configuration file called voicemail.conf. This file contains an assortment of settings that you can use to customize the voicemail system to your needs. Covering all of the available options in voicemail.conf would be beyond the scope of this chapter, but the sample configuration file is well documented and quite easy to follow. For now, look near the bottom of the file, where voicemail contexts and voicemail boxes are defined.
Just as dialplan contexts keep different parts of your dialplan
separate, voicemail contexts allow you to define different sets of
mailboxes that are separate from one another. This allows you to host
voicemail for several different companies or offices on the same server.
Voicemail contexts are defined in the same way as dialplan contexts,
with square brackets surrounding the name of the context. For our
examples, we’ll be using the [default]
voicemail context.
Creating Mailboxes
Inside each voicemail context, we define different mailboxes. The syntax for defining a mailbox is:
mailbox
=>password
,name
[,pager_email
[,options
]]]
Let’s explain what each part of the mailbox definition does:
mailbox
This is the mailbox number. It usually corresponds with the extension number of the associated set.
password
This is the numeric password that the mailbox owner will use to access her voicemail. If the user changes her password, the system will update this field in the voicemail.conf file.
name
This is the name of the mailbox owner. The company directory uses the text in this field to allow callers to spell usernames.
email
This is the email address of the mailbox owner. Asterisk can send voicemail notifications (including the voicemail message itself) to the specified email box.
pager_email
This is the email address of the mailbox owner’s pager or cell phone. Asterisk can send a short voicemail notification message to the specified email address.
options
This field is a list of options that sets the mailbox owner’s time zone and overrides the global voicemail settings. There are nine valid options:
attach
,serveremail
,tz
,saycid
,review
,operator
,callback
,dialout
, andexitcontext
. These options should be inoption
=
value
pairs, separated by the pipe character (|
). Thetz
option sets the user’s time zone to a time zone previously defined in the[zonemessages]
section of voicemail.conf, and the other eight options override the global voicemail settings with the same names.
A typical mailbox definition might look something like this:
101 => 1234,Joe Public,jpublic@somedomain.com,jpublic@pagergateway.net, tz=central|attach=yes
Continuing with our dialplan from the last chapter, let’s set up
voicemail boxes for John and Jane. We’ll give John a password of
1234
and Jane a password of
4444
(remember, these go in
voicemail.conf, not in
extensions.conf):
[default] 101 => 1234,John Doe,john@asteriskdocs.org,jdoe@pagergateway.tld 102 => 4444,Jane Doe,jane@asteriskdocs.org,jane@pagergateway.tld
Adding Voicemail to the Dialplan
Now that we’ve created mailboxes for Jane and John, let’s allow callers
to leave messages for them if they don’t answer the phone. To do this,
we’ll use the VoiceMail()
application.
The VoiceMail()
application
sends the caller to the specified mailbox, so that he
can leave a message. The mailbox should be specified as
mailbox
@
context
, where
context
is the name of the voicemail
context. The option letters b
or
u
can be added to request the type
of greeting. If the letter b
is
used, the caller will hear the mailbox owner’s
busy message. If the letter u
is used, the caller will hear the mailbox
owner’s unavailable message (if one
exists).
Let’s use this in our sample dialplan. Previously, we had a line
like this in our [internal]
context, which allowed us to
call John:
exten => 101,1,Dial(${JOHN})
Next, let’s add an unavailable message that the caller will be
played if John doesn’t answer the phone within 10 seconds. Remember,
the second argument to the Dial()
application is a timeout. If the call is not answered before the
timeout expires, the call is sent to the next priority. Let’s add a
10-second timeout, and a priority to send the caller to voicemail if
John doesn’t answer in time:
exten => 101,1,Dial(${JOHN},10) exten => 101,n,VoiceMail(101@default,u)
Now, let’s change it so that if John is busy (on another call),
it’ll send us to his voicemail, where we’ll hear his busy message. To
do this, we will make use of the ${DIALSTATUS}
variable which contains one of
several status values (see core show
application dial
at the Asterisk console for a listing of
all the possible values):
exten => 101,1,Dial(${JOHN},10)
exten => 101,n,GotoIf($["${DIALSTATUS}" = "BUSY"]?busy:unavail)
exten => 101,n(unavail),Voicemail(101@default,u)
exten => 101,n,Hangup()
exten => 101,n(busy),VoiceMail(101@default,b)
exten => 101,n,Hangup()
Now callers will get John’s voicemail (with the appropriate greeting) if John is either busy or unavailable. A slight problem remains, however, in that John has no way of retrieving his messages. Let’s remedy that.
Accessing Voicemail
Users can retrieve their voicemail messages, change their voicemail options, and record
their voicemail greetings by using the VoiceMailMain()
application. In its typical
form, VoiceMailMain()
is called
without any arguments. Let’s add extension 700 to the [internal]
context of our dialplan so that
internal users can dial it to access their voicemail messages:
exten => 700,1,VoiceMailMain()
Creating a Dial-by-Name Directory
One last feature of the Asterisk voicemail system we should
cover is the dial-by-name directory. This is created with the Directory()
application. This application uses the names defined in the mailboxes
in voicemail.conf to present the caller with a
dial-by-name directory of the users.
Directory()
takes up to three
arguments: the voicemail context from which to read the names, the
optional dialplan context in which to dial the user, and an option
string (which is also optional). By default, Directory()
searches for the user by last
name, but passing the f
option
forces it to search by first name instead. Let’s add two dial-by-name
directories to the [incoming]
context of our sample dialplan, so that callers can search by either
first or last name:
exten => 8,1,Directory(default,incoming,f) exten => 9,1,Directory(default,incoming)
If callers press 8, they’ll get a directory by first name. If they dial 9, they’ll get the directory by last name.
Macros
Macros[84] are a very useful construct designed to avoid repetition in the dialplan. They also help in making changes to the dialplan. To illustrate this point, let’s look at our sample dialplan again. If you remember the changes we made for voicemail, we ended up with the following for John’s extension:
exten => 101,1,Dial(${JOHN},10) exten => 101,n,GotoIf($["${DIALSTATUS}" = "BUSY"]?busy:unavail) exten => 101,n(unavail),Voicemail(101@default,u) exten => 101,n,Hangup() exten => 101,n(busy),VoiceMail(101@default,b) exten => 101,n,Hangup()
Now imagine you have a hundred users on your Asterisk system—setting up the extensions would involve a lot of copying and pasting. Then imagine that you need to make a change to the way your extensions work. That would involve a lot of editing, and you’d be almost certain to have errors.
Instead, you can define a macro that contains a list of steps to take, and then have all of the phone extensions refer to that macro. All you need to change is the macro, and everything in the dialplan that references that macro will change as well.
Tip
If you’re familiar with computer programming, you’ll recognize that macros are similar to subroutines in many modern programming languages. If you’re not familiar with computer programming, don’t worry—we’ll walk you through creating a macro.
The best way to appreciate macros is to see one in action, so let’s move right along.
Defining Macros
Let’s take the dialplan logic we used above to set up voicemail for John and turn it into a macro. Then we’ll use the macro to give John and Jane (and the rest of their coworkers) the same functionality.
Macro definitions look a lot like contexts. (In fact, you could
argue that they really are small, limited contexts.) You define a
macro by placing macro-
and the
name of your macro in square brackets, like this:
[macro-voicemail]
Macro names must start with macro-
. This distinguishes them from regular
contexts. The commands within the macro are built almost identically
to anything else in the dialplan; the only limiting factor is that
macros use only the s
extension.
Let’s add our voicemail logic to the macro, changing the extension to
s
as we go:
[macro-voicemail] exten => s,1,Dial(${JOHN},10) exten => s,n,GotoIf($["${DIALSTATUS}" = "BUSY"]?busy:unavail) exten => s,n(unavail),Voicemail(101@default,u) exten => s,n,Hangup() exten => s,n(busy),VoiceMail(101@default,b) exten => s,n,Hangup()
That’s a start, but it’s not perfect, as it’s still specific to John and his mailbox number. To make the macro generic so that it will work not only for John but also for all of his coworkers, we’ll take advantage of another property of macros: arguments. But first, let’s see how we call macros in our dialplan.
Calling Macros from the Dialplan
To use a macro in our dialplan, we use the Macro()
application. This application calls
the specified macro and passes it any arguments. For example, to call
our voicemail macro from our dialplan, we can do the following:
exten => 101,1,Macro(voicemail)
The Macro()
application also defines several special variables for our use.
They include:
As we explained earlier, the way we initially defined our macro
was hardcoded for John, instead of being generic. Let’s change our
macro to use ${MACRO_EXTEN}
instead
of 101
for the mailbox number. That
way, if we call the macro from extension 101 the voicemail messages
will go to mailbox 101, and if we call the macro from extension 102
messages will go to mailbox 102, and so on:
[macro-voicemail] exten => s,1,Dial(${JOHN},10) exten => s,n,GotoIf($["${DIALSTATUS}" = "BUSY"]?busy:unavail) exten => s,n(unavail),Voicemail(${MACRO_EXTEN}@default,u) exten => s,n,Hangup() exten => s,n(busy),VoiceMail(${MACRO_EXTEN}@default,b) exten => s,n,Hangup()
Using Arguments in Macros
Okay, now we’re getting closer to having the macro the way we want it,
but we still have one thing left to change; we need to pass in the
channel to dial, as it’s currently still hardcoded for ${JOHN}
(remember that we defined the
variable JOHN
as the channel to
call when we want to reach John). Let’s pass in the channel as an
argument, and then our first macro will be complete:
[macro-voicemail] exten => s,1,Dial(${ARG1},10) exten => s,n,GotoIf($["${DIALSTATUS}" = "BUSY"]?busy:unavail) exten => s,n(unavail),Voicemail(${MACRO_EXTEN}@default,u) exten => s,n,Hangup() exten => s,n(busy),VoiceMail(${MACRO_EXTEN}@default,b) exten => s,n,Hangup()
Now that our macro is done, we can use it in our dialplan. Here’s how we can call our macro to provide voicemail to John, Jane, and Jack:
exten => 101,1,Macro(voicemail,${JOHN}) exten => 102,1,Macro(voicemail,${JANE}) exten => 103,1,Macro(voicemail,${JACK})
With 50 or more users, this dialplan will still look neat and
organized; we’ll simply have one line per user, referencing a macro
that can be as complicated as required. We could even have a few
different macros for various user types, such as executives
, courtesy_phones
, call_center_agents
, analog_sets
, sales_department
, and so on.
A more advanced version of the macro might look something like this:
[macro-voicemail] exten => s,1,Dial(${ARG1},20) exten => s,n,Goto(s-${DIALSTATUS},1) exten => s-NOANSWER,1,Voicemail(${MACRO_EXTEN},u) exten => s-NOANSWER,n,Goto(incoming,s,1) exten => s-BUSY,1,Voicemail(${MACRO_EXTEN},b) exten => s-BUSY,n,Goto(incoming,s,1) exten => _s-.,1,Goto(s-NOANSWER,1)
This macro depends on a nice side effect of the Dial()
application: when you use the
Dial()
application, it sets the
DIALSTATUS
variable to indicate
whether the call was successful or not. In this case, we’re handling
the NOANSWER
and BUSY
cases, and treating all other result
codes as a NOANSWER
.
Using the Asterisk Database (AstDB)
Having fun yet? It gets even better!
Asterisk provides a powerful mechanism for storing values called the Asterisk database (AstDB). The AstDB provides a simple way to store data for use within your dialplan.
Tip
For those of you with experience using relational databases such as PostgreSQL or MySQL, the Asterisk database is not a traditional relational database. It is a Berkeley DB Version 1 database. There are several ways to store data from Asterisk in a relational database. Check out Chapter 12 for a more about relational databases.
The Asterisk database stores its data in groupings called families, with values
identified by keys. Within a family, a key may be
used only once. For example, if we had a family called test
, we could store only one value with a key
called count
. Each stored value must
be associated with a family.
Storing Data in the AstDB
To store a new value in the Asterisk database, we use the
Set()
application,[85] but instead of using it to set a channel variable, we
use it to set an AstDB variable. For example, to assign the count
key in the test
family with the value of 1
, write the following:
exten => 456,1,Set(DB(test/count)=1)
If a key named count
already
exists in the test
family, its
value will be overwritten with the new value. You can also store
values from the Asterisk command line, by running the command database put
family key
value
. For our example, you would type database put test count 1
.
Retrieving Data from the AstDB
To retrieve a value from the Asterisk database and assign it to
a variable, we use the Set()
application again. Let’s retrieve the value of count
(again, from the test
family), assign it to a variable called
COUNT
, and then speak the value to
the caller:
exten => 456,1,Set(DB(test/count)=1)
exten => 456,n,Set(COUNT=${DB(test/count)})
exten => 456,n,SayNumber(${COUNT})
You may also check the value of a given key from the Asterisk
command line by running the command database
get
family key
. To view the
entire contents of the AstDB, use the database show
command.
Deleting Data from the AstDB
There are two ways to delete data from the Asterisk database. To
delete a key, you can use the DB_DELETE()
application. It takes the path
to the key as its arguments, like this:
; deletes the key and returns its value in one step exten => 457,1,Verbose(0, The value was ${DB_DELETE(test/count)})
You can also delete an entire key family by using the DBdeltree()
application. The DBdeltree()
application takes a single
argument―the name of the key family―to delete. To delete the entire
test
family, do the
following:
exten => 457,1,DBdeltree(test)
To delete keys and key families from the AstDB via the
command-line interface, use the database
del
key
and database deltree
family
commands, respectively.
Using the AstDB in the Dialplan
There are an infinite number of ways to use the Asterisk
database in a dialplan. To introduce the AstDB, we’ll show two simple
examples. The first is a simple counting example to show that the
Asterisk database is persistent (meaning that it survives system
reboots). In the second example, we’ll use the BLACKLIST()
function to evaluate whether or
not a number is on the blacklist and should be blocked.
To begin the counting example, let’s first retrieve a number
(the value of the count
key) from
the database and assign it to a variable named COUNT
. If the key doesn’t exist, DB()
will return NULL (no value). In order
to verify if a value exists in the database or not, we will introduce
the ISNULL()
function that will
verify whether a value was returned, and if not, we will initialize
the AstDB with the Set()
application, where we will set the value in the database to 1
. The next priority will send us back to
priority 1. This will happen the very first time we dial this
extension:
exten => 678,1,Set(COUNT=${DB(test/count)}) exten => 678,n,GotoIf($[${ISNULL(${COUNT})}]?:continue) exten => 678,n,Set(DB(test/count)=1) exten => 678,n,Goto(1) exten => 678,n(continue),NoOp()
Next, we’ll say the current value of COUNT
, and then increment COUNT
:
exten => 678,1,Set(COUNT=${DB(test/count)}) exten => 678,n,GotoIf($[${ISNULL(${COUNT})}]?:continue) exten => 678,n,Set(DB(test/count)=1) exten => 678,n,Goto(1) exten => 678,n(continue),NoOp() exten => 678,n,SayNumber(${COUNT})exten => 678,n,Set(COUNT=$[${COUNT} + 1])
Now that we’ve incremented COUNT
, let’s put the new value back into the
database. Remember that storing a value for an existing key overwrites
the previous value:
exten => 678,1,Set(COUNT=${DB(test/count)})
exten => 678,n,GotoIf($[${ISNULL(${COUNT})}]?:continue)
exten => 678,n,Set(DB(test/count)=1)
exten => 678,n,Goto(1)
exten => 678,n(continue),NoOp()
exten => 678,n,SayNumber(${COUNT})
exten => 678,n,Set(COUNT=$[${COUNT} + 1])
exten => 678,n,Set(DB(test/count)=${COUNT})
Finally, we’ll loop back to the first priority. This way, the application will continue counting:
exten => 678,1,Set(COUNT=${DB(test/count)}) exten => 678,n,GotoIf($[${ISNULL(${COUNT})}]?:continue) exten => 678,n,Set(DB(test/count)=1) exten => 678,n,Goto(1) exten => 678,n(continue),NoOp() exten => 678,n,SayNumber(${COUNT}) exten => 678,n,Set(COUNT=$[${COUNT} + 1] exten => 678,n,Set(DB(test/count)=${COUNT}) exten => 678,n,Goto(1)
Go ahead and try this example. Listen to it count for a while, and then hang up. When you dial this extension again, it should continue counting from where it left off. The value stored in the database will be persistent, even across a restart of Asterisk.
In the next example, we’ll create dialplan logic around the
BLACKLIST()
function, which checks
to see if the current Caller ID number exists in the blacklist. (The
blacklist is simply a family called blacklist
in the AstDB.) If BLACKLIST()
finds the number in the
blacklist, it returns the value 1
,
otherwise it will return 0
. We can
use these values in combination with a GotoIf()
to control whether the call will
execute the Dial()
application:
exten => 124,1,GotoIf($[${BLACKLIST()]?blocked,1) exten => 124,n,Dial(${JOHN}) exten => blocked,1,Playback(privacy-you-are-blacklisted) exten => blocked,n,Playback(vm-goodbye) exten => blocked,n,Hangup()
To add a number to the blacklist, run the database put blacklist
number
1
command from the Asterisk command-line interface.
Handy Asterisk Features
Now that we’ve gone over some more of the basics, let’s look at a few popular functions that have been incorporated into Asterisk.
Zapateller()
Zapateller()
is a simple Asterisk application that plays a special
information tone at the beginning of a call, which causes auto-dialers
(usually used by telemarketers) to think that the line has been
disconnected. Not only will they hang up, but their systems will flag
your number as out of service, which could help you avoid all kinds of
telemarketing calls. To use this functionality within your dialplan,
simply call the Zapateller()
application.
We’ll also use the optional nocallerid
option so that the tone will be
played only when there is no Caller ID information on the incoming
call. For example, you might use Zapateller()
in the s
extension of your [incoming]
context, like this:
[incomimg] exten => s,1,Zapateller(nocallerid) exten => s,n,Playback(enter-ext-of-person)
Call Parking
Another handy feature is called call parking. Call parking
allows you to place a call on hold in a “parking lot,” so that it can
be taken off hold from another extension. Parameters for call parking
(such as the extensions to use, the number of spaces, and so on) are
all controlled within the features.conf
configuration file. The [general]
section of the features.conf file contains
four settings related to call parking:
parkext
This is the parking lot extension. Transfer a call to this extension, and the system will tell you which parking position the call is in. By default, the parking extension is
700
.parkpos
This option defines the number of parking slots. For example, setting it to
701-720
creates 20 parking positions, numbered 701 through 720.context
This is the name of the parking context. To be able to park calls, you must include this context.
parkingtime
If set, this option controls how long (in seconds) a call can stay in the parking lot. If the call isn’t picked up within the specified time, the extension that parked the call will be called back.
Tip
You must restart Asterisk after editing
features.conf, as the file is read only on
startup. Running the reload
command will not cause the features.conf file
to be read.
Also note that because the user needs to be able to transfer the
calls to the parking lot extension, you should make sure you’re using
the t
and/or T
options to the Dial()
application.
So, let’s create a simple dialplan to show off call parking:
[incoming] include => parkedcalls exten => 103,1,Dial(SIP/Bob,,tT) exten => 104,1,Dial(SIP/Charlie,,tT)
To illustrate how call parking works, say that Alice calls into the system and dials extension 103 to reach Bob. After a while, Bob transfers the call to extension 700, which tells him that the call from Alice has been parked in position 701. Bob then dials Charlie at extension 104, and tells him that Alice is at extension 701. Charlie then dials extension 701 and begins to talk to Alice. This is a simple and effective way of allowing callers to be transferred between users.
Tip
The t
and T
arguments to Dial()
are not needed on all channel
types. For example, many SIP phones implement this via a softkey or
hardkey and utilize SIP signaling.
Conferencing with MeetMe()
Last but not least, let’s cover setting up an audio conference bridge with the MeetMe()
application.[86] This application allows multiple callers to converse
together, as if they were all in the same physical location. Some of
the main features include:
The ability to create password-protected conferences
Conference administration (mute conference, lock conference, kick participants)
The option of muting all but one participant (useful for company announcements, broadcasts, etc.)
Static or dynamic conference creation
Let’s walk through setting up a basic conference room. The
configuration options for the MeetMe conferencing system are found in
meetme.conf. Inside the configuration file, you define conference rooms and
optional numeric passwords. (If a password is defined here, it will be
required to enter all conferences using that room.) For our example,
let’s set up a conference room at extension 600. First, we’ll set up
the conference room in meetme.conf. We’ll call it
600
, and we won’t assign a password
at this time:
[rooms] conf => 600
Now that the configuration file is complete, we’ll need to
restart Asterisk so that it can reread the
meetme.conf file. Next, we’ll add support for the
conference room to our dialplan with the MeetMe()
application. MeetMe()
takes three arguments: the name of
the conference room (as defined in meetme.conf),
a set of options, and the password the user must enter to join this
conference. Let’s set up a simple conference using room 600
, the i
option (which announces when people enter
and exit the conference), and a password of 54321
:
exten => 600,1,MeetMe(600,i,54321)
That’s all there is to it! When callers enter extension 600,
they will be prompted for the password. If they correctly enter
54321
, they will be added to the
conference. See Appendix B for a list of all
the options supported by the MeetMe()
application.
Another useful application is MeetMeCount()
. As its name suggests, this
application counts the number of users in a particular conference
room. It takes up to two arguments: the conference room in which
to count the number of participants, and optionally a variable name to
assign the count to. If the variable name is not passed as the second
argument, the count is read to the caller:
exten => 601,1,Playback(conf-thereare) exten => 601,n,MeetMeCount(600) exten => 601,n,Playback(conf-peopleinconf)
If you pass a variable as the second argument to MeetMeCount()
, the count is assigned to the variable, and playback of the
count is skipped. You might use this to limit the number of
participants, like this:
; limit the conference room to 10 participants exten => 600,1,MeetMeCount(600,CONFCOUNT) exten => 600,n,GotoIf($[${CONFCOUNT} <= 10]?meetme:conf_full,1) exten => 600,n(meetme),MeetMe(600,i,54321) exten => conf_full,1,Playback(conf-full)
Isn’t Asterisk fun?
Conclusion
In this chapter, we’ve covered a few more of the many applications in the Asterisk dialplan, and hopefully we’ve given you the seeds from which you can explore the creation of your own dialplans. As with the previous chapter, we invite you to go back and reread any sections that require clarification.
The following chapters take us away from Asterisk for a bit, in order to talk about some of the technologies that all telephone systems use. We’ll be referring to Asterisk a lot, but much of what we want to discuss are things that are common to many telecom systems.
[80] Remember that when you reference a variable you can call it by its name, but when you refer to a variable’s value, you have to use the dollar sign and brackets around the variable name.
[81] For more on regular expressions, grab a copy of the ultimate reference, Jeffrey E.F. Friedl’s Mastering Regular Expressions (O’Reilly) or visit http://www.regular-expressions.info.
[82] If you don’t know what a ^
has to do with regular
expressions, you simply must obtain a copy of
Mastering Regular Expressions. It
will change your life!
[83] No, you really don’t have to pay for this—and yes, it really does work.
[84] Although Macro
seems like a
general-purpose dialplan subroutine, it has a stack overflow problem
that means you should not try to nest Macro
calls more than five levels deep. As
of this writing, we do not know whether the Macro
application will be patched for 1.4,
or if it will be rewritten for future versions. If you plan to do a
lot of macros within macros (and call complex functions within
them), you may run into stability problems. You will know you have a
problem with just one test call, so if your dialplan tests out,
you’re good to go. We also recommend that you take a look at the
Gosub
and Return
applications, as a lot of macro
functionality can be implemented without actually using Macro()
. Also, please note that we are not
suggesting that you don’t use Macro()
. It is fantastic and works very
well; it just doesn’t nest efficiently.
[85] Previous versions of Asterisk had applications
called DBput()
and
DBget()
that were used to set
values in and retrieve values from the AstDB. If you’re using an
old version of Asterisk, you’ll want to use those applications
instead.
[86] In the world of legacy PBXes, this type of functionality is very expensive. Either you have to pay big bucks for a dial-in service, or you have to add an expensive conferencing bridge to your proprietary PBX.
Get Asterisk: The Future of Telephony, 2nd Edition now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.