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.

Alice Kahn

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.

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:

  1. Asterisk substitutes ${COUNT} with the number 3 in the expression. The expression effectively becomes this:

    exten => 321,n,Set(NEWCOUNT=$[3 + 1])
  2. Asterisk evaluates the expression, adding 1 to 3, and replaces it with its computed value of 4:

    exten => 321,n,Set(NEWCOUNT=4)
  3. 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).

Try it out in your own dialplan.

Operators

When you create an Asterisk dialplan, you’re really writing code in a specialized scripting language. This means that the Asterisk dialplan—like any programming language—recognizes symbols called operators that allow you to manipulate variables. Let’s look at the types of operators that are available in Asterisk:

Boolean operators

These operators evaluate the “truth” of a statement. In computing terms, that essentially refers to whether the statement is something or nothing (nonzero or zero, true or false, on or off, and so on). The Boolean operators are:

expr1 | expr2

This operator (called the “or” operator, or “pipe”) returns the evaluation of expr1 if it is true (neither an empty string nor zero). Otherwise, it returns the evaluation 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.)

Mathematical operators

Want to perform a calculation? You’ll want one of these:

expr1 {+, -} expr2

These operators return the results of the addition or subtraction of integer-valued arguments.

expr1 {*, /, %} expr2

These 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 : 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

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        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.

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 weasels

  • An extension and a priority label within the same context, such as 123,weasels

  • A context, extension, and priority label, such as incoming,123,weasels

Either of the destinations may be omitted, but not both. If the omitted destination is to be followed, Asterisk simply goes on to the next priority in the current extension.

Let’s use GotoIf() in an example:

exten => 345,1,Set(TEST=1)
   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?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.

Note

It is worth noting that times will properly wrap around. So, if you wish to specify the times your office is closed, you might write 18:00-9:00 in 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.

Note

Note that you can specify a combination of ranges and single days, as in: sun-mon&wed&fri-sat, or, more simply: wed&fri-mon.

days_of_month

This is a list of the numerical days of the month. Days are specified by the numbers 1 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:

  • A priority label within the same extension, such as time_has_passed

  • An extension and a priority within the same context, such as 123,time_has_passed

  • A context, extension, and priority, such as incoming,123,time_has_passed

Now that we’ve covered the syntax, let’s look at a couple of examples. The following example would match from 9:00 A.M. to 5:59 P.M., on Monday through Friday, on any day of the month, in any month of the year:

exten => s,1,GotoIfTime(09:00-17:59,mon-fri,*,*?open,s,1)

If the caller calls during these hours, the call will be sent to the first priority of the s extension in the context named open. If the call is made outside of the specified times, it will be sent to the next priority of the current extension. This allows you to easily branch on multiple times, as shown in the next example (note that you should always put your most specific time matches before the least specific ones):

; If it's any hour of the day, on any day of the week,
; during the fourth day of the month, in the month of July,
; we're closed
exten => s,1,GotoIfTime(*,*,4,jul?closed,s,1)

; During business hours, send calls to the open context
   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.

${ARG n }

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 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 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.

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 ${ARGn} (where the first argument is ${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 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

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

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 illustrates the delay configurations.

Table 10-1. Delayed dialing using Local channels

Time period (in seconds)channel_1channel_2channel_3
0Dial(SIP/0000FFFF0001,20)Wait(10)Wait(15)
5   
10 Dial(DAHDI/g0/14165551212) 
15  Dial(SIP/MyITSP/12565551212,15)
20Hangup()  
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.

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 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-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.

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 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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset