Variables are key to making the dialplan and system work in a manner that a user expects. The user would expect the system to know everything they have set on their extension, and not have to enter codes or dial special access numbers.
There are a number of places in which variables can be stored including the dialplan, sip.conf, iax.conf, chan_DAHDI.conf
(in version1.6), and the Asterisk database (AstDB). For example, if we have a number of static dial strings we wish to store for each type of call and carrier we use, and then use them in a number of sections, the [globals]
section of the extensions.conf
file is the obvious place to declare them. If we wish to set a variable when a call is initiated from a SIP device, external caller ID or account codes are a good example, the setvar
command in the sip.conf
file is ideal for that purpose. Just remember that it won't work for calls sent to that device just when the calls are made. Finally, the AstDB is great for variables that are more transient in nature, such as call counts.
On occasion, when using complicated dialplans you may wish for a variable's value to be kept as the call progresses. This is achieved by adding a _
[underscore] or a __
[double underscore] before the variable name.
A single _
will cause that variable to be inherited into the channel that started from the original channel, for example:
Set(_name1=value1)
If you want the variable to be inherited to all child channels indefinitely, then add __
before the variable name. For example:
Set(__name2=value2)
This should not be confused with setting the variable with the g
option, as this sets it as a global variable. Doing so makes the variable available to all channels globally.
So, you may ask "why might we store dial strings as a variable?" The simple reason is that it allows a minimal amount of code for dialing all numbers, but still allows for different classes of restriction, by which we mean allowing different users to have different restrictions in what they can and cannot dial.
To pass these variables we will use a macro. Macros are like a template that we can use for repeated tasks, and they allow the passing of variables in an ordered fashion to the macro context. The call will jump to the s extension. The calling extension, context, and priority are stored in ${MACRO_EXTEN}, ${MACRO_CONTEXT}
, and ${MACRO_PRIORITY}
respectively. Arguments passed are accessed as ${ARG1}, ${ARG2}
, and so on within the Macro. While a Macro is being executed, it becomes the context, so you must be able to handle the h, i
, and t
extensions if required within that context.
Let's build our small macro dialplan. We have a variable defined in the globals
section of the extensions.conf
file as follows:
[globals] INT_CALL=IAX2/username@peer_out/ INT_CALL_ID=01234123456 ; default international callerID INT_CALL_LIMIT=5 ; Limit on the number of calls
In the context that we use for dialing, we have:
; International long distance through trunk exten => _90.,1,Macro(outdial,${INT_CALL})
Here, we have defined the macro we are going to pass the call to, along with a single variable we defined in the globals
section (the value of the calling extension can be retrieved within the macro by using ${MACRO_EXTEN})
.
The macro context looks like this:
[macro-outdial] exten => s,4,Dial(${ARG1}${MACRO_EXTEN:1},180)
This is the same as the dial string:
exten => s,4,Dial(IAX2/username@peer_out/01234123456,180)
We have seen that we can pass one dial string, but let's now pass other variables to the Dial()
application, such as a backup route for outgoing calls, and the caller ID we want to use for the call.
exten => _90.,1,Macro(outdial,${INTCALL},${INT_CALL_ID},${INT_CALL_LIMIT}) [macro-outdial] exten => s,1,Set(GROUP()=OUTBOUND_GROUP) ;Set Group exten => s,2,GotoIf($[${GROUP_COUNT(OUTBOUND_GROUP)} > ${ARG3}]?103) ;Exceeded? exten => s,3,Set(CALLERID(num)=${ARG2}) exten => s,4,Dial(${ARG1}${MACRO_EXTEN:1},180)
Now it's time to bring some .conf
file variables into the mix. Using the setvar
facility in the sip.conf, iax.conf
and chan_dahdi.conf
files, we can set variables specific for every user such as unique caller ID, call limits, whether we want to record the call, account codes. Basically, anything that will help you handle calls more efficiently.
setvar=account_code=2206 setvar=callidnum=01234123456 setvar=tenantID=2
Note
One problem using .conf
files is that the relevant channel module needs to be reloaded after a change, and in the case of DAHDI, Asterisk would need to be restarted. This isn't too much of an issue but the need can be removed by using the AstDB for storing commonly changed settings, such as caller ID and recordings.
You may think that all this variable use is over-complicated, but consider a system that supports multiple tenants. Using these techniques, you will only need one dialplan for multiple tenants instead of one per tenant. Simply set the tenantID
in the relevant .conf
file and then store the tenants' features in the globals
section of the dialplan and in the AstDB, and all calls will go out as that tenant group. The concept is the same for other scenarios, such as departments that require cross charging of telephone costs.
Setting and retrieving variables in the AstDB is very simple and achieved through the use of the Set()
application. Variables can exist in splendid isolation or be grouped into families. The syntax for setting a variable is:
Set(DB(family/variable)=value)
Retrieving the variable's value is equally as simple:
Set(result=${DB(family/variable)})
So, let's have a look at how we can implement a simple multi-tenant dialplan using multiple variable stores:
INT_CALL1=IAX2/username@peer_out_1/ INT_CALL2=IAX2/username@peer_out_1/ INT_CALL_LIMIT1=5 ; Limit on the number of calls INT_CALL_LIMIT2=5 ; Limit on the number of calls exten => _90[1-2]XXXXXXXXX,1,Set(INTCALL=INTCALL${tenantID}) exten => _90[1-2]XXXXXXXXX,n,Set(INT_CALL_LIMIT=INT_CALL_LIMIT${tenantID}) exten => _90[1-2]XXXXXXXXX,n,Macro(outdial,${INTCALL}, ${callidnum},${INT_CALL_LIMIT})
As we can see, we have been able to cut down the amount of code and make it universal for different types of users and systems. Using a macro lets us pass an ordered list of arguments. It is easiest to think of macro arguments as a list of variables since they are handled the same way.
Note
Due to the way macro is implemented, it executes the priorities contained within it via a sub-engine, and a fixed per-thread memory stack allowance. Macros are limited to seven levels of nesting. It can be possible that stack-intensive applications in deeply-nested macros could cause Asterisk to crash. Take this into account and be very careful when nesting macros.