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 Chapter 6 sorted out yet, please go back and give it another read. We’re about to get into more advanced topics.
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 define. Put on your thinking cap, and let’s get started.
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 our 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[91]:
exten => 321,1,Set(COUNT=3) same => n,Set(NEWCOUNT=$[${COUNT} + 1]) same => 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 number 3
in the expression. The expression
effectively becomes this:
exten => 321,n,Set(NEWCOUNT=$[3 + 1])
Asterisk evaluates the
expression, adding 1
to
3
, and replaces it with its
computed value of 4
:
exten => 321,n,Set(NEWCOUNT=4)
The Set()
application assigns the value
4
to the NEWCOUNT
variable
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).
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:
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 of expr2
.
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, or 0
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.)
You can also use the regular expression operator in Asterisk:
expr1
:
expr2
This operator matches
expr1
against
expr2
, where
expr2
must be a regular
expression.[92] The regular expression is anchored to the
beginning of the string with an implicit ^
.[93]
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 current versions of Asterisk, as the expression parser has been made more forgiving in these types of scenarios. However, for readability’s sake, we still recommend including the spaces around your operators.
To concatenate text onto the beginning or end of a variable, simply place them together, like this:
exten => 234,1,Set(NEWTEST=blah${TEST})
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.
Dialplan functions have the following basic syntax:
FUNCTION_NAME
(argument
)
You reference a function’s name the same way as a variable’s name, 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 preceding example, we have labeled the opening parentheses and curly braces with numbers and their corresponding closing counterparts with the same numbers.
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) same => n,SayNumber(${LEN(${TEST})})
This example will first evaluate
$TEST
as example
. The string “example” is then given
to the LEN()
function, which will
evaluate as the length of the string, 7
. Finally, 7
is passed as an argument to 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.
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 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:
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) same => n,GotoIf($[${TEST} = 1]?weasels:iguanas) same => n(weasels),Playback(weasels-eaten-phonesys) same => n,Hangup() same => n(iguanas),Playback(office-iguanas) same => n,Hangup()
You will notice that we have
used the Hangup()
application following each use of the 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 into multiple components (protected from each other by the
Hangup()
command), each one a
distinct sequence of steps executed following a GotoIf()
.
Typically, when you have this type of layout where you end up wanting to prevent 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)
same => 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
same => n,Hangup()
exten => iguanas,1,Playback(office-iguanas)
same => 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) same => n(start),GotoIf($[${COUNT} > 0]?:goodbye) same => n,SayNumber(${COUNT}) same => n,Set(COUNT=$[${COUNT} - 1]) same => n,Goto(start) same => 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) same => n(allow),Dial(DAHDI/4) same => n,Hangup() same => n(reject),Playback(abandon-all-hope) same => 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 DAHDI/4
.
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
?labeliftrue[:labeliffalse]
)
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.
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 the
times
parameter, and it will
perform as expected. Note that this technique works as well
for the other components of GotoIfTime()
. For example, you can
write sat-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/or sun
. Monday through Friday would be
expressed as mon-fri
. Tuesday
and Thursday would be expressed as tue&thu
.
days_of_month
This is a list of the numerical days of the month.
Days are specified by the numbers 1
through 31
. The 7th through the 12th would be
expressed as 7-12
, and the
15th and 30th of the month would be written as 15&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 nonsequential months, such as
jan&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:
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 same => n,GotoIfTime(09:00-17:59,mon-fri,*,*?open,s,1) same => n,GotoIfTime(09:00-11:59,sat,*,*?open,s,1) ; Otherwise, we're closed same => n,Goto(closed,s,1)
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 until
18:01:59.
Macros[94] 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) same => n,GotoIf($["${DIALSTATUS}" = "BUSY"]?busy:unavail) same => n(unavail),VoiceMail(101@default,u) same => n,Hangup() same => n(busy),VoiceMail(101@default,b) same => 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.
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.
Let’s take the dialplan logic we used 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) same => n,GotoIf($["${DIALSTATUS}" = "BUSY"]?busy:unavail) same => n(unavail),VoiceMail(101@default,u) same => n,Hangup() same => n(busy),VoiceMail(101@default,b) same => 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.
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, 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) same => n,GotoIf($["${DIALSTATUS}" = "BUSY"]?busy:unavail) same => n(unavail),VoiceMail(${MACRO_EXTEN}@default,u) same => n,Hangup() same => n(busy),VoiceMail(${MACRO_EXTEN}@default,b) same => n,Hangup()
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) same => n,GotoIf($["${DIALSTATUS}" = "BUSY"]?busy:unavail) same => n(unavail),VoiceMail(${MACRO_EXTEN}@default,u) same => n,Hangup() same => n(busy),VoiceMail(${MACRO_EXTEN}@default,b) same => 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) same => n,Goto(s-${DIALSTATUS},1) exten => s-NOANSWER,1,VoiceMail(${MACRO_EXTEN},u) same => n,Goto(incoming,s,1) exten => s-BUSY,1,VoiceMail(${MACRO_EXTEN},b) same => n,Goto(incoming,s,1) exten => _s-.,1,Goto(s-NOANSWER,1)
Since we know how to use
dialplan functions now as well, here is another way of controlling
which voicemail prompt (unavailable vs. busy) is played to the
caller. In the following example, we’ll be using the IF()
dialplan function:
[macro-voicemail] exten => s,1,Dial(${ARG1},20) same => n,VoiceMail(${MACRO_EXTEN},${IF($[${DIALSTATUS} = BUSY]?b:u)})
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
.
The GoSub()
dialplan
application is similar to the Macro()
application, in that the purpose is to allow you to call a block of
dialplan functionality, pass information to that block, and return from
it (optionally with a return value). GoSub()
works in a different manner from
Macro()
, though, in that it doesn’t
have the stack space requirements, so it nests effectively. Essentially,
GoSub()
acts like Goto()
with a memory of where it came from.
In this section we’re going to reimplement what we learned in Macros. If necessary, you might want to review that section: it explains why we might use a subroutine, and the goal we’re trying to accomplish.
Unlike with Macro()
,
there are no special naming requirements when using GoSub()
in the dialplan. In fact, you can
use GoSub()
within the same context
and extension if you want to. In most cases, however, GoSub()
is used in a similar fashion to
Macro()
, so defining a new context
is common. When creating the context, we like to prepend the name with
sub
so we know the context is
typically called from the GoSub()
application (of course, there is no requirement that you do so, but it
seems a sensible convention).
Here is a simple example of how we might define a subroutine in Asterisk:
[subVoicemail]
Let’s
take our example from Macros and
convert it to a subroutine. Here is how it is defined for use with
Macro()
:
[macro-voicemail] exten => s,1,Dial(${JOHN},10) same => n,GotoIf($["${DIALSTATUS}" = "BUSY"]?busy:unavail) same => n(unavail),VoiceMail(101@default,u) same => n,Hangup() same => n(busy),VoiceMail(101@default,b) same => n,Hangup()
If we were going to convert this to be used for a subroutine, it might look like this:
[subVoicemail] exten => start,1,Dial(${JOHN},10) same => n,GotoIf($["${DIALSTATUS}" = "BUSY"]?busy:unavail) same => n(unavail),VoiceMail(101@default,u) same => n,Hangup() same => n(busy),VoiceMail(101@default,b) same => n,Hangup()
Not much of a change, right? All
we’ve altered in this example is the context name, from [macro-voicemail]
to [subVoicemail]
, and the extension, from
s
to start
(since there is no requirement that
the extension be called anything in particular, unlike with Macro()
, which expects the extension to be
s
).
Of course, as in the example in
the section Macros, we haven’t
passed any arguments to the subroutine, so whenever we call [subVoicemail]
, ${JOHN}
will always be called, and the
voicemail box 101 will get used. In the following sections, we’ll dig
a little deeper. First we’ll look at how we would call a subroutine,
and then we’ll learn how to pass arguments.
Subroutines are called from the dialplan using the
GoSub()
application. The arguments
to GoSub()
differ slightly than
those for Macro()
, because GoSub()
has no naming requirements for the
context or extension (or priority) that gets used. Additionally, no
special channel variables are set when calling a subroutine, other
than the passed arguments, which are saved to ${ARG
(where
the first argument is n
}${ARG1}
, the
second argument is ${ARG2}
, and so
forth).
Now that we’ve updated our
voicemail macro to be called as a subroutine, lets take a look at how
we call it using GoSub()
:
exten => 101,1,GoSub(subVoicemail,start,1())
You’ll notice that we’ve
placed a set of opening and closing parentheses within our
GoSub()
application. These are
the placeholders for any arguments we might pass to the
subroutine, and while it is optional for them to exist, it’s a
programming style we prefer to use.
Next, let’s look at how we can pass arguments to our subroutine in order to make it more general.
The ability to use arguments is one of the major
features of using Macro()
or
GoSub()
, because it allows you to
abstract out code that would otherwise be duplicated across your
dialplan. Without the need to duplicate the code, we can better manage
it, and we can easily add functionality to large numbers of users by
modifying a single location. You are encouraged to move code into this
form whenever you find yourself creating duplicate code.
Before we start using our subroutine, we need to update it to accept arguments so that it is generic enough to be used by multiple users:
[subVoicemail] exten => start,1,Dial(${ARG1}
,10) same => n,GotoIf($["${DIALSTATUS}" = "BUSY"]?busy:unavail) same => n(unavail),VoiceMail(${ARG2}
@default,u) same => n,Hangup() same => n(busy),VoiceMail(${ARG2}
@default,b) same => n,Hangup()
Recall that previously we had
hardcoded the channel variable ${JOHN}
as the location to dial, and mailbox
101
as the voicemail box to be used
if ${JOHN}
wasn’t available. In
this code, we’ve replaced ${JOHN}
and 101
with ${ARG1}
and ${ARG2}
, respectively. In more complex
subroutines we might even assign the variables ${ARG1}
and ${ARG2}
to something like ${DESTINATION}
and ${VMBOX}
, to make it clear what the ${ARG1}
and ${ARG2}
represent.
Now that we’ve updated our subroutine, we can use it for several extensions:
[LocalSets] exten => 101,1,GoSub(subVoicemail,start,1(${JOHN},${EXTEN})) exten => 102,1,GoSub(subVoicemail,start,1(${JANE},${EXTEN})) exten => 103,1,GoSub(subVoicemail,start,1(${JACK},${EXTEN}))
Again, our dialplan is nice and neat. We could even modify our subroutine down to just three lines:
[subVoicemail] exten => start,1,Dial(${ARG1},10) same => n,VoiceMail(${ARG2}@default,${IF($[${DIALSTATUS} = BUSY]?b:u)}) same => n,Hangup()
One difference to note between
GoSub()
and Macro()
, however, is that if we left our
subroutine like this, we’d never return. In this particular example
that’s not a problem, since after the voicemail is left, we would
expect the caller to hang up anyway. In situations where we want to do
more after the subroutine has executed, though, we need to implement
the Return()
application.
Unlike Macro()
, the
GoSub()
dialplan application does
not return automatically once it is done executing. In order to return
from whence we came, we need to use the Return()
application. Now that we know how
to call a subroutine and pass arguments, we can look at an example
where we might need to return from the subroutine.
Using our previous example, we could break out the dialing portion and the voicemail portion into separate subroutines:
[subDialer] exten => start,1,Dial(${ARG1},${ARG2}) same => n,Return() [subVoicemail] exten => start,1,VoiceMail(${ARG1}@${ARG2},${ARG3}) same => n,Hangup()
The [subDialer]
context created here takes two
arguments: ${ARG1}
, which contains
the destination to dial; and ${ARG2}
, which contains the ring cycle,
defined in seconds. We conclude the [subDialer]
context with the dialplan
application Return()
, which will
return to the priority following the one that called GoSub()
(the next line of the
dialplan).
The [subVoicemail]
context contains the VoiceMail()
application, which is using
three arguments passed to it: ${ARG1}
contains the mailbox number,
${ARG2}
contains the voicemail
context, and ${ARG3}
contains a
value to indicate which voicemail message (unavailable or busy) to
play to the caller.
Calling these subroutines might look like this:
exten => 101,1,GoSub(subDialer,start,1(${JOHN},30)) same => n,GoSub(subVoicemail,start,1(${EXTEN},default,u))
Here
we’ve used the subDialer
subroutine, which attempts to call ${JOHN}
, ringing him for 30 seconds. If the
Dial()
application returns (e.g.,
if the line was busy, or there was no answer for 30 seconds), we
Return()
from the subroutine and
execute the next line of our dialplan, which calls the subVoicemail
subroutine. From there, we pass
the extension that was dialed (e.g., 101
) as the mailbox number, and pass the
values default
for the voicemail
context and the letter u
to play
the unavailable message.
Our example has been hardcoded to
play the unavailable voicemail message, but we can modify the Return()
application to return the ${DIALSTATUS}
so that we can play the busy
message if its value is BUSY
. To do
this, we’ll use the ${GOSUB_RETVAL}
channel variable, which is set whenever we pass a value to the
Return()
application:
[subDialer]
exten => start,1,Dial(${ARG1},${ARG2})
same => n,Return(${DIALSTATUS}
)
[subVoicemail]
exten => start,1,VoiceMail(${ARG1}@${ARG2},${ARG3})
same => n,Hangup()
In this version we’ve made just the
one change: Return()
to Return(${DIALSTATUS})
.
Now we can modify extension
101
to use the ${GOSUB_RETVAL}
channel variable, which will
be set by Return()
:
exten => 101,1,GoSub(subDialer,start,1(${JOHN},30))same => n,Set(VoicemailMessage=${IF($[${GOSUB_RETVAL} = BUSY]?b:u)})
same => n,GoSub(subVoicemail,start,1(${EXTEN},default,${VoicemailMessage}
))
Our
dialplan now has a new line that sets the ${VoicemailMessage}
channel variable to a
value of u
or b
, using the IF()
dialplan function and the value of
${GOSUB_RETVAL}
. We then pass the
value of ${VoicemailMessage}
as the
third argument to our subVoicemail
subroutine.
Before moving on, you might want to go back and review Macros andGoSub(). We’ve given you a lot to digest here, but these concepts will save you a lot of work as you start building your dialplans.
Local channels are a method of executing dialplans from
the Dial()
application. They may seem like a bit of a strange concept when you
first start using them, but believe us when we tell you they are a
glorious and extremely useful feature that you will almost certainly
want to make use of when you start writing advanced dialplans. The best
way to illustrate the use of Local channels is through an example. Let’s
suppose we have a situation where we need to ring multiple people, but
we need to provide delays of different lengths before dialing each of
the members. The use of Local channels is the only solution to the
problem.
With the Dial()
application, you can certainly ring
multiple endpoints, but all three channels will ring at the same time,
and for the same length of time. Dialing multiple channels at the same
time is done like so:
[LocalSets] exten => 107,1,Verbose(2,Dialing multiple locations simultaneously) same => n,Dial(SIP/0000FFFF0001&DAHDI/g0/14165551212&SIP/MyITSP/12565551212,30) same => n,Hangup()
This example dials three destinations for a period of 30 seconds. If none of those locations answers the call within 30 seconds, the dialplan continues to the next line and the call is hung up.
However, let’s say we want to introduce some delays, and stop ringing locations at different times. Using Local channels gives us independent control over each of the channels we want to dial, so we can introduce delays and control the period of time for which each channel rings independently. We’re going to show you how this is done in the dialplan, both within a table that shows the delays visually, and all together in a box, like we’ve done for other portions of the dialplan. We’ll be building the dialplan to match the time starts and stops described in Figure 10-1.
First we need to call three Local
channels, which will all execute different parts of the dialplan. We do
this with the Dial()
application,
like so:
[LocalSets] exten => 107,1,Verbose(2,Dialing multiple locations with time delay) ; *** This all needs to be on a single line same => n,Dial(Local/channel_1@TimeDelay&Local/channel_2@TimeDelay &Local/channel_3@TimeDelay,40) same => n,Hangup()
Now our Dial()
application will dial three Local
channels. The destinations will be the channel_1
, channel_2
, and channel_3
extensions located within the
TimeDelay
dialplan context. Remember
that Local channels are a way of executing the dialplan from within the
Dial()
application. Our master
timeout for all the channels is 40 seconds, which means any Local
channel that does not have a shorter timeout configured will be hung up
if it does not answer the call within that period of time.
As promised, Table 10-1 illustrates the delay configurations.
Table 10-1. Delayed dialing using Local channels
Time period (in seconds) | channel_1 | channel_2 | channel_3 |
---|---|---|---|
0 | Dial(SIP/0000FFFF0001,20) | Wait(10) | Wait(15) |
5 | |||
10 | Dial(DAHDI/g0/14165551212) | ||
15 | Dial(SIP/MyITSP/12565551212,15) | ||
20 | Hangup() | ||
25 | |||
30 | Hangup() | ||
35 | |||
40 |
In this table, we can see that
channel_1
started dialing location
SIP/0000FFFF0001
immediately and
waited for a period of 20 seconds. After 20 seconds, that Local channel
hung up. Our channel_2
waited for 10
seconds prior to dialing the endpoint DAHDI/g0/14165551212
. There was no maximum
time associated with this Dial()
, so
its dialing period ended when the master time out of 40 seconds (which
we set when we initially called the Local channels) expired. Finally,
channel_3
waited 15 seconds prior to
dialing, then dialed SIP/MyITSP/12565551212
and waited for a period
of 15 seconds prior to hanging up.
If we put all this together, we end up with the following dialplan:
[LocalSets] exten => 107,1,Verbose(2,Dialing multiple locations with time delay) ; *** This all needs to be on a single line same => n,Dial(Local/channel_1@TimeDelay&Local/channel_2@TimeDelay &Local/channel_3@TimeDelay,40) same => n,Hangup() [TimeDelay] exten => channel_1,1,Verbose(2,Dialing the first channel) same => n,Dial(SIP/0000FFFF0001,20) same => n,Hangup() exten => channel_2,1,Verbose(2,Dialing the second channel with a delay) same => n,Wait(10) same => n,Dial(DAHDI/g0/14165551212) exten => channel_3,1,Verbose(2,Dialing the third channel with a delay) same => n,Wait(15) same => n,Dial(SIP/MyITSP/12565551212,15) same => n,Hangup()
You’ll see Local channels used
throughout this book, for various purposes. Remember that the intention
is simply to perform some dialplan logic from a location where you can
only dial a location, but require some dialplan logic to be executed
prior to dialing the endpoint you eventually want to get to. A good
example of this is with the use of the Queue()
application, which we’ll discuss in
Using Local Channels.
Additional scenarios and information
about Local channels and the modifier flags (/n
, /j
, /m
,
/b
) are available at https://wiki.asterisk.org/wiki/display/AST/Local+Channel.
If you will be making any sort of regular use of Local channels, that is
a very important document to read.
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.
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 16 for 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.
To store a new value in the Asterisk database, we use
the Set()
application,[95] 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
, we would 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.
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)
same => n,Set(COUNT=${DB(test/count)})
same => 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.
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.
There are an infinite number of ways to use the Asterisk
database in a dialplan. To introduce the AstDB, we’ll look at 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). Therefore, we can
use the ISNULL()
function
to verify whether or not a value was returned. 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)}) same => n,GotoIf($[${ISNULL(${COUNT})}]?:continue) same => n,Set(DB(test/count)=1) same => n,Goto(1) same => n(continue),NoOp()
Next, we’ll say the current value
of COUNT
, and then increment
COUNT
:
exten => 678,1,Set(COUNT=${DB(test/count)})
same => n,GotoIf($[${ISNULL(${COUNT})}]?:continue)
same => n,Set(DB(test/count)=1)
same => n,Goto(1)
same => n(continue),NoOp()
same => n,SayNumber(${COUNT})
same => 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)})
same => n,GotoIf($[${ISNULL(${COUNT})}]?:continue)
same => n,Set(DB(test/count)=1)
same => n,Goto(1)
same => n(continue),NoOp()
same => n,SayNumber(${COUNT})
same => n,Set(COUNT=$[${COUNT} + 1])
same => 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)}) same => n,GotoIf($[${ISNULL(${COUNT})}]?:continue) same => n,Set(DB(test/count)=1) same => n,Goto(1) same => n(continue),NoOp() same => n,SayNumber(${COUNT}) same => n,Set(COUNT=$[${COUNT} + 1] same => n,Set(DB(test/count)=${COUNT}) same => 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) same => n,Dial(${JOHN}) exten => blocked,1,Playback(privacy-you-are-blacklisted) same => n,Playback(vm-goodbye) same => n,Hangup()
To add a number to the blacklist, run the database put blacklist <number> 1 command from the Asterisk command-line interface.
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()
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) same => n,Playback(enter-ext-of-person)
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.
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.
Last but not least, let’s cover setting up an audio
conference bridge with the MeetMe()
application.[96] This application allows multiple callers to converse
together, as if they were all in the same physical location. Some of
the main features include:
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.
You can run core show application
MeetMe from the Asterisk CLI 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) same => n,MeetMeCount(600) same => 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) same => n,GotoIf($[${CONFCOUNT} <= 10]?meetme:conf_full,1) same => n(meetme),MeetMe(600,i,54321) exten => conf_full,1,Playback(conf-full)
In this chapter, we’ve covered a few more of the many applications in the Asterisk dialplan, and hopefully we’ve given you some more tools that you can use to further explore the creation of your own dialplans. As with other chapters, we invite you to go back and reread any sections that require clarification.
[91] 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.
[92] 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.
[93] If you don’t know what a ^
has to do with regular
expressions, you simply must read Mastering
Regular Expressions. It will change
your life!
[94] 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.
If you plan to use 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 (see GoSub()), 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.
[95] 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.
[96] In the world of legacy PBXs, 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.