GoSub()

The GoSub() dialplan application is similar to the Macro() application, in that the purpose is to allow you to call a block of dialplan functionality, pass information to that block, and return from it (optionally with a return value). GoSub() works in a different manner from Macro(), though, in that it doesn’t have the stack space requirements, so it nests effectively. Essentially, GoSub() acts like Goto() with a memory of where it came from.

In this section we’re going to reimplement what we learned in the section called “Macros”. If necessary, you might want to review that section: it explains why we might use a subroutine, and the goal we’re trying to accomplish.

Defining Subroutines

Unlike with Macro(), there are no special naming requirements when using GoSub() in the dialplan. In fact, you can use GoSub() within the same context and extension if you want to. In most cases, however, GoSub() is used in a similar fashion to Macro(), so defining a new context is common. When creating the context, we like to prepend the name with sub so we know the context is typically called from the GoSub() application (of course, there is no requirement that you do so, but it seems a sensible convention).

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

[subVoicemail]

Let’s take our example from the section called “Macros” and convert it to a subroutine. Here is how it is defined for use with Macro():

[macro-voicemail]
exten => s,1,Dial(${JOHN},10)
   same => n,GotoIf($["${DIALSTATUS}" = "BUSY"]?busy:unavail)
   same => n(unavail),VoiceMail(101@default,u)
   same => n,Hangup()
   same => n(busy),VoiceMail(101@default,b)
   same => n,Hangup()

If we were going to convert this to be used for a subroutine, it might look like this:

[subVoicemail]
exten => start,1,Dial(${JOHN},10)
   same => n,GotoIf($["${DIALSTATUS}" = "BUSY"]?busy:unavail)
   same => n(unavail),VoiceMail(101@default,u)
   same => n,Hangup()
   same => n(busy),VoiceMail(101@default,b)
   same => n,Hangup()

Not much of a change, right? All we’ve altered in this example is the context name, from [macro-voicemail] to [subVoicemail], and the extension, from s to start (since there is no requirement that the extension be called anything in particular, unlike with Macro(), which expects the extension to be s).

Of course, as in the example in the section the section called “Macros”, we haven’t passed any arguments to the subroutine, so whenever we call [subVoicemail], ${JOHN} will always be called, and the voicemail box 101 will get used. In the following sections, we’ll dig a little deeper. First we’ll look at how we would call a subroutine, and then we’ll learn how to pass arguments.

Calling Subroutines from the Dialplan

Subroutines are called from the dialplan using the GoSub() application. The arguments to GoSub() differ slightly than those for Macro(), because GoSub() has no naming requirements for the context or extension (or priority) that gets used. Additionally, no special channel variables are set when calling a subroutine, other than the passed arguments, which are saved to ${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 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.