Chapter 10. Deeper into the Dialplan
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, Dialplan Basics 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 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 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 number3in the expression. The expression effectively becomes this:exten => 321,n,Set(NEWCOUNT=$[3 + 1])
Asterisk evaluates the expression, adding
1to3, and replaces it with its computed value of4:exten => 321,n,Set(NEWCOUNT=4)
The
Set()application assigns the value4to theNEWCOUNTvariable
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|expr2This operator (called the “or” operator, or “pipe”) returns the evaluation of
expr1if it is true (neither an empty string nor zero). Otherwise, it returns the evaluation ofexpr2.expr1&expr2This operator (called “and”) returns the evaluation of
expr1if both expressions are true (i.e., neither expression evaluates to an empty string or zero). Otherwise, it returns zero.expr1{=, >, >=, <, <=, !=}expr2These 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
1if the specified relation is true, or0if 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:
expr1{+, -}expr2These operators return the results of the addition or subtraction of integer-valued arguments.
expr1{*, /, %}expr2These return, respectively, the results of the multiplication, integer division, or remainder of integer-valued arguments.
- Regular expression operator
You can also use the regular expression operator in Asterisk:
expr1:expr2This operator matches
expr1againstexpr2, whereexpr2must 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\1is 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,0is 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
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)
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 4321As 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.
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)
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.
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
weaselsAn extension and a priority label within the same context, such as
123,weaselsA 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)
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()
Note
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.
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:00in thetimesparameter, and it will perform as expected. Note that this technique works as well for the other components ofGotoIfTime(). For example, you can writesat-sunto 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
1through31. 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-aprfor a range, and separated with ampersands when wanting to include nonsequential 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_passedAn extension and a priority within the same context, such as
123,time_has_passedA 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 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)
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 until 18:01:59.
Macros
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.
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 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.
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:
-
${MACRO_CONTEXT} The original context in which the macro was called.
-
${MACRO_EXTEN} The original extension in which the macro was called.
-
${MACRO_PRIORITY} The original priority in which the macro was called.
${ARGn}The
nth argument passed to the macro. For example, the first argument would be${ARG1}, the second${ARG2}, and so on.
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()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)
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)Tip
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.
GoSub()
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 the section called “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.
Defining Subroutines
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 the section called “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 the section called “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.
Calling Subroutines from the Dialplan
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())
Note
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.
Using Arguments in Subroutines
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.
Returning from a Subroutine
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 the section called “Macros” andthe section called “GoSub()”. 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
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, “Time delayed dialing with local channels”.
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, “Delayed dialing using Local channels” 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 the section called “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.
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 16, Relational Database Integration 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.
Storing Data in the AstDB
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.
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)
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.
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 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.
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) same => 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-720creates 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.
Conferencing with MeetMe()
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:
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.
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)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 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 the section called “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.






View 3 comments



