Deeper into the Dialplan(Asterisk)

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:

exten => 321,1,NoOp()
same  => n,Answer()
same  => n,Set(COUNT=3)
same  => n,Set(NEWCOUNT=$[${COUNT} + 1])
same  => n,SayNumber(${NEWCOUNT})

In the second priority, we assign the value of 3 to the variable named COUNT .

In the third 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:
same => n,Set(NEWCOUNT=$[3 + 1])
2. Asterisk evaluates the expression, adding 1 to 3 , and replaces it with its computed value of 4 :
same => 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’sconsistent 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.  The regular expression is anchored to the beginning of the string with an implicit ^ .
If the pattern contains no subexpression, the number of matched characters is returned. This will be 0 if the match failed. If the pattern contains a subexpression — \(…\) — the string corresponding to \1 is returned. If the match fails, the empty string is returned.

expr1 =~ expr2
This operator works the same as the : operator, except that it is not anchored to the beginning. 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 closingcounterparts 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,NoOp()
same  => n,Set(TEST=example)
same  => n,Playback(silence/1)
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,NoOp()
same => n,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()

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,NoOp()
same => n,Answer()
same => n,Set(TEST=1)
same => n,GotoIf($[${TEST} = 1]?weasels,1:iguanas,1) ; now we’re going to
; extension,priority

exten => weasels,1,
same => n,Playback(weasels-eaten-phonesys)                   ; this is NOT a label.
; It is a different extension
same => n,Hangup()
exten => iguanas,1,NoOp()

same => n,Playback(office-iguanas)
same => n,Hangup()

By changing the value assigned to TEST in the seconds 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,NoOp()
same => n,Answer()
same => n,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 second 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,NoOp()
same => n,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, GotoIf Time() 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.

days_of_week
This is a list of one or more days of the week. The days should be specified as mon ,tue , wed , thu , fri , sat , and/or sun . Monday through Friday would be expressed as mon-fri . Tuesday and Thursday would be expressed as tue&thu .

days_of_month
This is a list of the numerical days of the month. Days are specified by the numbers 1 through 31 . The 7th through the 12th would be expressed as 7-12 , and the 15th and 30th of the month would be written as 15&30 .

months
This is a list of one or more months of the year. The months should be written as jan – apr for a range, and separated with ampersands when wanting to include non‐sequential 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 * 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:in for that argument.

exten => s,1,NoOp()
same => n,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,NoOp()
same => n,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)

Macros

Macros 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,NoOp()
same => n,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.

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,NoOp()
same => n,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 n th 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,NoOp()
same => n,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

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,NoOp()
same => n,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: 4

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,NoOp()
same => n,Dial(${ARG1},20)
same => n,Goto(s-${DIALSTATUS},1)
exten => s-NOANSWER,1,VoiceMail(${MACRO_EXTEN}@default,u)
same => n,Goto(incoming,s,1)
exten => s-BUSY,1,VoiceMail(${MACRO_EXTEN}@default,b)
same => n,Goto(incoming,s,1)

exten => _s-.,1,NoOp()
same => n,Goto(s-NOANSWER,1)

This macro depends on a nice side effect of the Dial() application: when you use the Dial() application, it sets the DIALSTATUS variable to indicate whether the call was successful or not. In this case, we’re handling the NOANSWER and BUSY cases, and treating all other result codes as a NOANSWER .

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.

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 like a sensible convention).

Here is a simple example of how we might define a subroutine in Asterisk:
[subVoicemail]

Here
is how it is defined for use with Macro() :
[macro-voicemail]
exten => s,1,NoOp()
same => n,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,NoOp()
same => n,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 ).

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, let’s take a look at how we call it using GoSub() :
exten => 101,1,GoSub(subVoicemail,start,1())

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,NoOp()
same => n,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,NoOp()

same => n,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 oursubroutine 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 Re turn() 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,NoOp()
same => n,Dial(${ARG1},${ARG2})
same => n,Return()
[subVoicemail]
exten => start,1,NoOp()
same => n,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, ammnd ${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,NoOp()
same => n,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 lineof 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 de fault 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,NoOp()
same => n,Dial(${ARG1},${ARG2})
same => n,Return(${DIALSTATUS})

[subVoicemail]
exten => start,1,NoOp()
same => n,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,NoOp()
same => n,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.

Local Channels

Local channels are a method of executing other areas of the dialplan from the Dial() application (as opposed to sending the call out a channel). 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,NoOp()
same => n,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, as 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 1.

e1

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.

Table 1. Delayed dialing using Local channels

e2

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 di‐ aling period ended when the master timeout 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, for various purposes. Local channels allow you to execute dialplan logic from applications that normally expect to directly connect to a channel. For example, you can assign a Local channel as a queue member, and run all sorts of fancy dialplan logic whenever the queue attempts to deliver a call to an agent.

Additional scenarios and information about Local channels and the modifier flags ( /n , /j , /m , /b ) are available at the Asterisk wiki. If you will be making any sort of regular use of Local channels, that is a very important document to read.