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.
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.
Subroutines are called from the dialplan using the
GoSub()
application. The arguments to
GoSub()
differ slightly than those for
Macro()
, because GoSub()
has no
naming requirements for the context or extension (or priority) that gets
used. Additionally, no special channel variables are set when calling a
subroutine, other than the passed arguments, which are saved to
${ARG
(where the first
argument is n
}${ARG1}
, the second argument is
${ARG2}
, and so forth).
Now that we’ve updated our
voicemail macro to be called as a subroutine, lets take a look at how we
call it using GoSub()
:
exten => 101,1,GoSub(subVoicemail,start,1())
You’ll notice that we’ve placed
a set of opening and closing parentheses within our
GoSub()
application. These are the placeholders
for any arguments we might pass to the subroutine, and while it is
optional for them to exist, it’s a programming style we prefer to
use.
Next, let’s look at how we can pass arguments to our subroutine in order to make it more general.
The ability to use arguments is one of the major features
of using Macro()
or GoSub()
,
because it allows you to abstract out code that would otherwise be
duplicated across your dialplan. Without the need to duplicate the code,
we can better manage it, and we can easily add functionality to large
numbers of users by modifying a single location. You are encouraged to
move code into this form whenever you find yourself creating duplicate
code.
Before we start using our subroutine, we need to update it to accept arguments so that it is generic enough to be used by multiple users:
[subVoicemail] exten => start,1,Dial(${ARG1}
,10) same => n,GotoIf($["${DIALSTATUS}" = "BUSY"]?busy:unavail) same => n(unavail),VoiceMail(${ARG2}
@default,u) same => n,Hangup() same => n(busy),VoiceMail(${ARG2}
@default,b) same => n,Hangup()
Recall that previously we had
hardcoded the channel variable ${JOHN}
as the
location to dial, and mailbox 101
as the voicemail
box to be used if ${JOHN}
wasn’t available. In this
code, we’ve replaced ${JOHN}
and
101
with ${ARG1}
and
${ARG2}
, respectively. In more complex subroutines we
might even assign the variables ${ARG1}
and
${ARG2}
to something like
${DESTINATION}
and ${VMBOX}
, to
make it clear what the ${ARG1}
and ${ARG2}
represent.
Now that we’ve updated our subroutine, we can use it for several extensions:
[LocalSets] exten => 101,1,GoSub(subVoicemail,start,1(${JOHN},${EXTEN})) exten => 102,1,GoSub(subVoicemail,start,1(${JANE},${EXTEN})) exten => 103,1,GoSub(subVoicemail,start,1(${JACK},${EXTEN}))
Again, our dialplan is nice and neat. We could even modify our subroutine down to just three lines:
[subVoicemail] exten => start,1,Dial(${ARG1},10) same => n,VoiceMail(${ARG2}@default,${IF($[${DIALSTATUS} = BUSY]?b:u)}) same => n,Hangup()
One difference to note between
GoSub()
and Macro()
, however, is
that if we left our subroutine like this, we’d never return. In this
particular example that’s not a problem, since after the voicemail is
left, we would expect the caller to hang up anyway. In situations where
we want to do more after the subroutine has executed, though, we need to
implement the Return()
application.
Unlike Macro()
, the
GoSub()
dialplan application does not return
automatically once it is done executing. In order to return from whence
we came, we need to use the Return()
application. Now
that we know how to call a subroutine and pass arguments, we can look at
an example where we might need to return from the subroutine.
Using our previous example, we could break out the dialing portion and the voicemail portion into separate subroutines:
[subDialer] exten => start,1,Dial(${ARG1},${ARG2}) same => n,Return() [subVoicemail] exten => start,1,VoiceMail(${ARG1}@${ARG2},${ARG3}) same => n,Hangup()
The [subDialer]
context created here takes two arguments: ${ARG1}
,
which contains the destination to dial; and ${ARG2}
,
which contains the ring cycle, defined in seconds. We conclude the
[subDialer]
context with the dialplan application
Return()
, which will return to the priority following
the one that called GoSub()
(the next line of the
dialplan).
The
[subVoicemail]
context contains the
VoiceMail()
application, which is using three
arguments passed to it: ${ARG1}
contains the mailbox
number, ${ARG2}
contains the voicemail context, and
${ARG3}
contains a value to indicate which voicemail
message (unavailable or busy) to play to the caller.
Calling these subroutines might look like this:
exten => 101,1,GoSub(subDialer,start,1(${JOHN},30)) same => n,GoSub(subVoicemail,start,1(${EXTEN},default,u))
Here
we’ve used the subDialer
subroutine, which attempts
to call ${JOHN}
, ringing him for 30 seconds. If the
Dial()
application returns (e.g., if the line was
busy, or there was no answer for 30 seconds), we
Return()
from the subroutine and execute the next
line of our dialplan, which calls the subVoicemail
subroutine. From there, we pass the extension that was dialed (e.g.,
101
) as the mailbox number, and pass the values
default
for the voicemail context and the letter
u
to play the unavailable message.
Our example has been hardcoded to
play the unavailable voicemail message, but we can modify the
Return()
application to return the
${DIALSTATUS}
so that we can play the busy message if
its value is BUSY
. To do this, we’ll use the
${GOSUB_RETVAL}
channel variable, which is set
whenever we pass a value to the Return()
application:
[subDialer]
exten => start,1,Dial(${ARG1},${ARG2})
same => n,Return(${DIALSTATUS}
)
[subVoicemail]
exten => start,1,VoiceMail(${ARG1}@${ARG2},${ARG3})
same => n,Hangup()
In this version we’ve made just the
one change: Return()
to
Return(${DIALSTATUS})
.
Now we can modify extension
101
to use the ${GOSUB_RETVAL}
channel variable, which will be set by
Return()
:
exten => 101,1,GoSub(subDialer,start,1(${JOHN},30))same => n,Set(VoicemailMessage=${IF($[${GOSUB_RETVAL} = BUSY]?b:u)})
same => n,GoSub(subVoicemail,start,1(${EXTEN},default,${VoicemailMessage}
))
Our
dialplan now has a new line that sets the
${VoicemailMessage}
channel variable to a value of
u
or b
, using the
IF()
dialplan function and the value of
${GOSUB_RETVAL}
. We then pass the value of
${VoicemailMessage}
as the third argument to our
subVoicemail
subroutine.
Before moving on, you might want to go back and review 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.