The recipes in this chapter focus on making and controlling phone calls in Asterisk. The rich set of possibilities for controlling calls is part of what makes Asterisk a telephony applications platform and not just another PBX.
Use the GROUP()
and GROUP_COUNT()
dialplan functions:
exten => _1NXXNXXXXXX,1,Set(GROUP(outbound)=myprovider) same => n,Set(COUNT=${GROUP_COUNT(myprovider@outbound)}) same => n,NoOp(There are ${COUNT} calls for myprovider.) same => n,GotoIf($[${COUNT} > 2]?denied:continue) same => n(denied),NoOp(There are too many calls up already. Hang up.) same => n,HangUp() same => n(continue),GoSub(callmyprovider,${EXTEN},1})
In this example solution, we have
shown how you could use the GROUP()
and GROUP_COUNT()
functions to limit
the number of calls sent to a provider to no more than two calls at a
time. You can think of using the GROUP()
function as a way to apply a marker to
a channel. GROUP_COUNT()
is the
method of getting a count of how many active channels are up with a
given mark on them.
The argument provided to the
GROUP()
function is a category. In
our example, the category used is outbound
. The first line sets the channel’s
outbound
group value to myprovider
. On the next
line, we set the COUNT
variable to
the number of channels that are marked as being in the myprovider
group within the outbound
category. The rest of the example
demonstrates using this value in a conditional statement so that the
dialplan can continue down two different paths depending on the number
of channels that matched the GROUP_COUNT()
criteria.
One reason that you might want to use this particular example is if you want to limit your exposure to fraudulent calls if an account on your system were to be compromised. An attacker would likely send as many calls through your system at a time as they could, so by limiting the number of calls that cost you money, you limit how many charges an attacker could rack up on you before you catch it.
GROUP()
and GROUP_COUNT()
can be used in a lot of other
cases, too. By using a very similar approach to this example, you can
also limit the number of calls that a specific user account is able to
make at any given time. Another use would be to set a global call limit
on the system.
The usage of GROUP()
and GROUP_COUNT()
comes up a number of times in
Asterisk: The
Definitive Guide (O’Reilly). References can be found
in Chapters 13 (“Automatic Call Distribution (ACD) Queues”), 14 (“Device
States”), 22 (“Clustering”), 23 (“Distributed Universal Number Discovery
(DUNDi)”), and 26 (“Security”).
As an Asterisk system administrator, you would like to quickly originate a new call from the Asterisk command-line interface.
Use the channel originate CLI command. To connect a channel directly to an application:
*CLI> channel originate SIP/myphone
application Playback demo-congrats
To connect a channel to an extension in the dialplan:
*CLI> channel originate SIP/myphone
extension 1234@DialplanContext
Originating calls is a fairly common
task. The CLI version of this functionality is most useful when doing
quick test calls while writing Asterisk dialplan. The examples above
showed how to originate a call to a phone and connect it to something
Asterisk. When testing, the use of a Local
channel instead of a real phone is
incredibly handy. For the purposes of quickly testing some dialplan
logic, you can just create an extension that runs the Wait()
application:
; /etc/asterisk/extensions.conf [default] exten => wait,1,Answer() same => n,Wait(300) same => n,Hangup() exten => newexten,1,Verbose(1,I wonder if my CUT() works...) same => n,Set(VAR=one-two-three) same => n,Verbose(1,one = ${CUT(VAR,-,1)})
Now, to quickly test whether this bit of dialplan logic is working correctly, you can run the following:
*CLI> channel originate Local/wait@default
extension newexten@default
I wonder if my CUT() works...
one = one
Asterisk provides an application for originating calls from the dialplan. To originate a call and connect it to an application, you would do this:
exten => s,1,Originate(SIP/myphone,app,Playback,all-your-base)
Alternatively, you can originate a call and connect it to an extension in the dialplan:
exten => s,1,Originate(SIP/myphone,exten,default,s,1)
The Originate()
application takes up to 5
arguments. The first two are:
Tech/data
This is the channel technology and associated data that the
call will be originated to. The syntax is the same as is used with
the commonly used Dial()
application.
originate mode
There are two originate modes: app
and exten
. The app
originate mode is used to connect
the originated call to an Asterisk application. If more complex
call processing is desired, the exten
originate mode can be used to
connect the originated call to an extension in the
dialplan.
The rest of the arguments depend on
which originate mode is being used. In the case of the app
originate mode, the arguments are:
application
This is the dialplan application that will be answered when the dialed channel answers.
application arguments
Any other arguments are passed to the application being executed.
If the originate mode is exten
, the rest of the arguments are:
context
This is the context in the dialplan that should be used to find the extension to run. This argument is required.
extension
This is the extension in the dialplan that will be executed.
If this argument is not specified, the s
extension will be used.
priority
This is the priority of the extension to execute. If this is
not specified, dialplan execution will start at priority 1
, which is almost always what you
want.
There are a lot of cases where you
might want to originate a call from the dialplan. One such example is
related to the handling of paging. Perhaps you would like to write an
extension that allows a caller to record and review a message to be
played out through a paging system, but allow the caller to hang up
after recording it, instead of doing the page live or requiring the
caller to stay on the phone while the paging process finishes. The way
you can accomplish this task is by first writing the dialplan that
allows the caller to record something. Afterwards, the Originate()
application will be used to
trigger the paging process to begin. That way, even if the caller hangs
up, the paging process will continue. Let’s get on to the
example:
[globals] PHONES_TO_PAGE=SIP/phoneA&SIP/phoneB&SIP/phoneC [paging] exten => 500,1,Answer() same => n,Record(/tmp/page.wav) same => n,Originate(Local/pageplayback@paging,exten,paging,page,1) same => n,Hangup() exten => page,1,Answer() ; ; This causes Polycom phones to auto-answer the call. ; same => n,SIPAddHeader(Alert-Info: Ring Answer) same => n,Page(${PHONES_TO_PAGE}) same => n,Hangup() exten => pageplayback,1,Answer() same => Playback(/tmp/page.wav)
In this example, a caller would dial
500
to initiate the paging process.
The Record()
application would allow
them to record their announcement to a file. The recording will end and
be saved to disk when the caller presses the # key on their phone. The
paging process can be canceled from this point by just hanging up. Next,
the Originate()
application starts a
new call. On one end of the call is a Local
channel, which is executing a short
extension that just plays back the recording that was just left. The
other end of the call is an extension that uses the Page()
application.
Before executing the Page()
application, the SIPAddHeader()
application is used to set a header that will be added to outbound call
requests to SIP phones since we want the call to be automatically
answered by the phones. This specific example will work for Polycom
phones. For examples of how to get other brands of phones to
automatically answer, see the “Parking and Paging” chapter of Asterisk: The
Definitive Guide.
The “External Services” chapter of
Asterisk: The
Definitive Guide has a section on integrating
Asterisk with calendar systems. One of the features provided by calendar
integration in Asterisk is the ability to have calls originated based on
calendar events. One of the really great examples in that book shows how
to read information out of the calendar event in the dialplan and then
use the Originate()
application to
make calls to all participants in the meeting.
Other recipes related to this one include Originating a Call Using the CLI, Originating a Call From a Call File, and Originating a Call From the Manager Interface.
You would like to programmatically originate a call outside of Asterisk directly from the Asterisk server in the simplest way possible.
Create a call file. Use your favorite text editor to create a file called example.call with the following contents:
Channel: SIP/YourPhone Context: outbound Extension: 12565551212 Priority: 1
Once the file has been created, move it to the Asterisk spool directory:
$
mv example.call /var/spool/asterisk/outgoing/
Call files are a great straightforward way to originate a call from outside of Asterisk. There are some things that are important to understand about call files if you choose to use them, though. First, you must understand the syntax and options that are allowed from within a call file. Beyond that, there are some nuances regarding how Asterisk processes call files that you should be aware of.
Every line in a call file is specified as a key/value pair:
key: value
The one line that is required in every call file is the specification of which channel to originate a call to:
Channel: Tech/data
There are two modes for all of the different methods of originating calls. The first mode is for when you connect a channel directly to an application. The second mode is for when you connect a channel to an extension in the dialplan. For the first mode, connecting to an application, you must specify which application and the arguments to pass to it:
Application:MeetMe
Data:1234
For the other mode, connecting a channel to an extension in the dialplan, you must specify the context, extension, and priority in the call file:
Context:default
Extension:s
Priority:1
The rest of the parameters that may be specified in call files are all optional, but some of them are incredibly useful:
Codecs:
ulaw,
alaw, gsm
By default, Asterisk will
allow the outbound channel created at the start of the call
origination process choose whatever codec(s) it wants. If you
would like to impose some limits on which codecs the channel may
choose, you can specify them as a comma delimited list with the
Codecs
option.
MaxRetries:
2
If the outbound call to the specified channel fails, this is how many times the call will be retried. By default, the call will only be attempted one time.
RetryTime:
60
This option specifies the
number of seconds to wait in between retries. The default is
300
seconds.
WaitTime:
30
This is the number of seconds
to wait for an answer from the outbound call before considering it
a failed attempt. By default, this is set to 45
seconds.
CallerID:
"Russell Bryant" <(256)
555-1212>
Specify the CallerID to use
for the outbound call. By default, no CallerID information is
provided for the outbound called made to Channel
.
Account:
accountcode
Set the accountcode
field on the outbound
channel. By default, this field is not set at all. The
accountcode
is used in both Call Detail Record
(CDR) and Channel Event Logging (CEL) processing. More information
about CDR and CEL can be found in the “Monitoring and Logging”
chapter of Asterisk:
The Definitive Guide.
AlwaysDelete:
Yes
Always delete the call file
when Asterisk is done processing it. Normally, once Asterisk has
finished successfully making the call or has given up after the
configured number of retries, the call file will be deleted. If
this option is set to No
and
the timestamp of the file is modified before Asterisk finishes
processing it to be some point in the future, the file will not be
deleted. The result is that Asterisk will process this file again
when the new time is reached.
Set:
CHANNELVAR=value
Set a channel variable on the outbound channel to a specified value.
Set:
FUNCTION(functionargs)=value
Set a dialplan function on the outbound channel to a specified value.
Archive:
Yes
Archive call files after
processing them. If this option is set to Yes
, instead of deleting the call file
after processing, Asterisk will move it to the /var/spool/asterisk/outgoing_done/
directory. Before the file is moved, Asterisk will add a Status
line to the file to indicate if
the originated call was Completed
, Expired
, or Failed
.
Now that we have covered all of the options that can be specified, the last bit of business is to make you aware of some of the important details about how Asterisk processes call files. Here are two critical issues you must keep in mind:
Never create a call file in the /var/spool/asterisk/outgoing/ directory. Asterisk starts processing these files as soon as they are created. If you create it directly in this directory, Asterisk may read the contents before you are finished writing the file. Instead, always create the file somewhere else on the filesystem and move it into this directory when you are ready to make Asterisk aware of its presence.
#
vi /tmp/example.call
/var/spool/asterisk/outgoing/
The timestamp on a call file is important. If you set the timestamp to be in the future, Asterisk will not process the file until that time is reached.
Use the Originate
action in the Asterisk Manager
Interface (AMI):
Action: Originate Channel:SIP/myphone
Exten:6001
Context:LocalExtensions
Priority:1
Timeout:30000
CallerID:"Asterisk" <6000>
Async:true
The Originate
action in the AMI allows you to send
a request over a TCP connection for Asterisk to make a call. This is the
most popular method for originating calls from custom applications. The
example provided in the solution starts by having Asterisk make a new
call to SIP/myphone
. If the phone does not
answer within 30 seconds, the call will be aborted. If the call is
answered, it is connected to extension 6001
in the LocalExtensions
context in the
dialplan.
Alternatively, the Originate
action can be used to connect a
channel directly to an application. By starting with the example
provided in the solution, we can modify it to connect the called phone
to conference bridge number 1234 without going through the
dialplan:
Action: Originate Channel:SIP/myphone
Application:MeetMe
Data:1234
Timeout:30000
CallerID:"Asterisk" <6000>
Async:true
There are a few more useful optional
headers that can be provided with the Originate
action:
<value>
This is a custom value that will also be included in any responses to this request. It can be helpful in custom applications that may have many outstanding requests at any one time to ensure that responses are associated with the proper request.
NAME=VALUE
This header can be specified
multiple times in an Originate
request. It will set channel variables on the outbound channel.
This can also be used to set dialplan functions. Just use a
function such as CDR(customfield)
in the NAME
portion of the header.
Specify the account code that will be placed in the CDR for this call.
For additional information about the
Originate
manager action included
with your version of Asterisk, use this command at the Asterisk
CLI:
*CLI>
manager show command Originate
For more information about the Asterisk Manager Interface, see the AMI chapter in Asterisk: The Definitive Guide.
For recipes that are directly related to this one, see Originating a Call Using the CLI, Originating a Call Using the Dialplan, and Originating a Call From a Call File.
You would like to call a series of phone numbers to attempt to locate a person when their extension is dialed.
Use the FollowMe()
application. First you must
configure the application in
/etc/asterisk/followme.conf:
; ; Configure what to do when FollowMe() is requested for Russell. ; We are going to try his desk phone first, and then try to call ; his cell phone. ; [russell] ; ; FollowMe() will use this context in the Asterisk dialplan ; to make outbound calls. ; context = trusted ; ; Call this number first. Give up after 20 seconds. ; number = 7101,20 ; ; Call this number second. Give up after 20 seconds. ; number = 12565551212,20 [leif] context = trusted number = 7102,20 number = 12565559898,20
Now create extensions in the
dialplan that will utilize the FollowMe()
application to locate the called
party:
[public_extensions] exten => 7001,1,FollowMe(russell) exten => 7002,1,FollowMe(leif) [trusted] exten => 7101,1,Dial(SIP/russell_desk) exten => 7102,1,Dial(SIP/leif_desk) exten => _1NXXNXXXXXX,1,Dial(SIP/${NUMBER}@outbound_provider)
Admittedly, everything that the
FollowMe()
application does for you
can be implemented directly in the dialplan. However, it takes more
work. If this application does what you want, then save yourself the
extra effort and just use FollowMe()
.
When the FollowMe()
application executes, it is going
to load the list of phone numbers that were configured in followme.conf and dial them in the order they
were specified. When one of the outbound calls is answered, a prompt
will be played requesting that the call be acknowledged by pressing a
key. One of the main reasons for requiring acknowledgment is that it
ensures that if the call is answered by voicemail that FollowMe()
continues and
tries other numbers.
There are some additional options
available in the followme.conf
file, but they are rarely used. For additional information on the
current set of available options, see configs/followme.conf.sample in the Asterisk
source. For some additional information about the syntax of FollowMe()
in the dialplan, check the
documentation built in to Asterisk:
*CLI>
core show application FollowMe
If you are interested in building similar functionality directly in the dialplan, see Building Find-Me-Follow-Me in the Dialplan.
[public_extensions] ; ; Find Russell. ; exten => 7001,1,Progress() same => n,Playback(followme/pls-hold-while-try,noanswer) same => n,Dial(Local/7101@trusted,20,rU(ackcall^s^1)) same => n,Dial(Local/12565551212@trusted,20,rU(ackcall^s^1)) same => n,Playback(followme/sorry,noanswer) same => n,Hangup() ; ; Find Leif. ; exten => 7002,1,Progress() same => n,Playback(followme/pls-hold-while-try,noanswer) same => n,Dial(Local/7102@trusted,20,rU(ackcall^s^1)) same => n,Dial(Local/12565559898@trusted,20,rU(ackcall^s^1)) same => n,Playback(followme/sorry,noanswer) same => n,Hangup() [trusted] exten => 7101,1,Dial(SIP/russell_desk) exten => 7102,1,Dial(SIP/leif_desk) exten => _1NXXNXXXXXX,1,Dial(SIP/${NUMBER}@outbound_provider) [ackcall] exten => s,1,Background(followme/no-recording&followme/options) same => n,WaitExten(5) same => n,Set(GOSUB_RESULT=BUSY) exten => 1,1,NoOp() exten => 2,1,Set(GOSUB_RESULT=BUSY) exten => i,1,Set(GOSUB_RESULT=BUSY)
This example implements the same
functionality provided by the FollowMe()
application
shown in Using the FollowMe() Dialplan Application. The most important
difference is that if you want to tweak any of the behavior, it is much
easier to do so in the Asterisk dialplan as opposed to modifying the C
code of the FollowMe()
application.
Here is what happens when someone dials 7001 to reach Russell:
exten => 7001,1,Progress()
The Progress()
application is used for telephony
signaling. We do not want to answer the call yet, but we want to start
playing audio. This is often referred to as early media in a phone call.
The use of Progress()
in the dialplan
maps to sending a 183 Session
Progress
request in SIP:
same => n,Playback(followme/pls-hold-while-try,noanswer)
Now that we have told the calling channel to expect early media, we are going to play a prompt without answering the call. This prompt says “Please hold while I try to locate the person you are calling”:
same => n,Dial(Local/7102@trusted,20,rU(ackcall^s^1))
There is a lot packed into this
line. First, we are making an outbound call to a Local
channel. This local extension just makes
an outbound call to a single SIP device. We could have simply dialed
that SIP device directly and the behavior would have been the same.
However, the use of the Local
channel
is more in line with how the FollowMe()
application works.
In the followme.conf file,
you configure phone numbers, not devices, for the application to call.
We specify a 20 second timeout for this outbound call attempt. Finally,
we set a couple of options. The first option is r
, which ensures that Asterisk generates a
ringback tone. We want this because we have already indicated to the
caller that we will be providing early media.
An alternative to this would be to use the m
option of Dial()
, which would provide hold music instead
of ringback. Finally, we use the U
option, which executes a GoSub()
routine on the called channel after it answers, but before connecting it
to the inbound channel. The routine we are using is ackcall
, which gives the called channel the
option of whether to accept the call. We will come back to the
implementation of the ackcall
routine
shortly.
Now let’s look at the next step:
same => n,Dial(Local/12565551212@trusted,20,rU(ackcall^s^1))
This step is identical to the last one except that it is calling a different number. You could have as many of these steps as you would like.
The next line is:
same => n,Playback(followme/sorry,noanswer)
If the caller makes it this far in the dialplan, Asterisk was not able to find the called party at any of the configured numbers. The prompt says “I’m sorry, but I was unable to locate the person you were calling”.
Finally, we have:
same => n,Hangup()
As you might have guessed, this
hangs up the call. Now let’s go back to the implementation of the
ackcall
GoSub()
routine that is executed by the
Dial()
application:
[ackcall] exten => s,1,Background(followme/no-recording&followme/options) same => n,WaitExten(5) same => n,Set(GOSUB_RESULT=BUSY) exten => 1,1,NoOp() exten => 2,1,Set(GOSUB_RESULT=BUSY) exten => i,1,Set(GOSUB_RESULT=BUSY)
The execution of this routine
begins at the s
extension. The
Background()
application is going to
play two prompts while also waiting for a digit to be pressed. The
called party will hear “You have an incoming call. Press 1 to accept
this call, or 2 to reject it.” After the prompts finish playing, the
WaitExten()
application will wait an
additional five seconds for a key to be pressed. At this point, there
are four different cases that may occur:
In this case, the s
extension will continue to the next
step and the GOSUB_RESULT
variable will be set to BUSY
.
This makes the Dial()
application act like this outbound call attempt returned a busy
response.
The call will jump to the
1
extension which does nothing.
Control will return to the Dial()
application and the caller and
callee will be bridged together.
The call will jump to the
2
extension which sets the
GOSUB_RESULT
variable to
BUSY
. Dial()
will treat this outbound call
attempt as busy.
Asterisk will look for an
extension that matches the key that was pressed but will not find
one. The call will instead go to the i
extension, which stands for invalid.
The GOSUB_RESULT
variable will
be set to BUSY
and Dial()
will treat this outbound call
attempt as busy.
Consider using the FollowMe()
application as discussed in Using the FollowMe() Dialplan Application if you do not require the level of
customization required by implementing this functionality in the
dialplan.
For more information about
Local
channels, see Chapter 10, “Deeper into the
Dialplan,” in Asterisk: The
Definitive Guide.
After a call has failed due to the destination being busy or otherwise unavailable, you would like to give the caller the option of being automatically called back when the destination becomes available.
Add the following options to the phone configuration sections of /etc/asterisk/sip.conf:
[phone1] ... cc_agent_policy = generic cc_monitor_policy = generic [phone2] ... cc_agent_policy = generic cc_monitor_policy = generic
Next, add the *30
and *31
extensions to your dialplan:
[phones] ; ; The extensions for dialing phones do not need to be changed. ; These are just simple examples of dialing a phone with a ; 20 second timeout. ; exten => 7101,1,Dial(SIP/phone1,20) same => n,Hangup() exten => 7102,1,Dial(SIP/phone2,20) same => n,Hangup() ; ; Dial *30 to request call completion services for the last ; call attempt. ; exten => *30,1,CallCompletionRequest() same => n,Hangup() ; ; Dial *31 to cancel a call completion request. ; exten => *31,1,CallCompletionCancel() same => n,Hangup()
Call Completion Supplementary
Services (CCSS) is a new feature in Asterisk 1.8. It allows you to
request that Asterisk call you back after an unanswered or busy call
attempt. In this example, we have used the generic
agent
and monitor
policies. This method of configuration
is the easiest to get working but only works for calls between phones
connected to the same Asterisk system. The agent
is the part of the system that operates
on behalf of the caller requesting call completion services. The
monitor
is the part of the system
that is in charge of monitoring the device (or devices) that were called
to determine when they become available.
Using this dialplan, let’s go
through an example where CCSS is used. Start by having 7001
call 7002
, but let the call time out after the
configured 20 seconds. In this case, the call has failed due to no
response. The caller, 7001
, can now
request CCSS by dialing *30
. This is
referred to as Call Completion No Response (CCNR). You can verify the
CCNR request at the Asterisk CLI:
*CLI>
cc report status
1 Call completion transactions Core ID Caller Status ---------------------------------------------------------------------------- 20 SIP/phone1 CC accepted by callee |-->7102@phones |-->SIP/phone2(CCNR)
At this point, Asterisk is using its
generic monitor implementation to wait for SIP/phone2
to become available. In the case of
CCNR, it determines availability by waiting for the phone to make a
call. When that call ends, Asterisk will initiate a call between
SIP/phone1
and SIP/phone2
and the CCNR request will have been
completed.
Another scenario is Call Completion Busy Subscriber (CCBS). This is the case when the called party is already on the phone. The process of requesting call completion services in this scenario is the same as before. The generic monitor will determine availability by waiting for the call that device is on to end.
Asterisk also supports extending CCSS across multiple servers using either SIP or ISDN (specifically ISDN in Europe). However, the configuration and operation of the protocol specific methods is outside the scope of this recipe.
The Asterisk project wiki, http://wiki.asterisk.org/, discusses CCSS.
You need people to be able to log in to any device and accept calls at that location, a feature known as hot-desking.
[HotDesking] ; Control extension range using pattern matches ; Login with 71XX will logout existing extension at this location ; and log this device in with new extension. ; Logoff with 7000 from any device. ; exten => 7000,1,Verbose(2,Attempting logoff from device ${CHANNEL(peername)}) same => n,Set(PeerName=${CHANNEL(peername)}) same => n,Set(CurrentExtension=${DB(HotDesk/${PeerName})}) same => n,GoSubIf($[${EXISTS(${CurrentExtension})}]? subDeviceLogoff,1(${PeerName},${CurrentExtension}):loggedoff) same => n,GotoIf($[${GOSUB_RETVAL} = 0]?loggedoff) same => n,Playback(an-error-has-occurred) same => n,Hangup() same => n(loggedoff),Playback(silence/1&agent-loggedoff) same => n,Hangup() exten => _71XX,1,Verbose(2,Attempting to login device ${CHANNEL(peername)} to extension ${EXTEN:1}) same => n,Set(NewPeerName=${CHANNEL(peername)}) same => n,Set(NewExtension=${EXTEN:1}) ; Check if existing extension is logged in for this device (NewPeerName) ; -- If existing extension exists (ExistingExtension) ; -- get existing device name ; -- If no existing device ; -- (login) as we'll overwrite existing extension for this device ; -- If existing device name ; -- logoff ExistingExtension + ExistingDevice ; -- Goto check_device ---------------------------------------+ ; -- If no existing extension exists | ; -- Check if existing device is logged in for this extension | ; (NewExtension) <-----------------------------------------------+ ; -- If existing device exists ; -- Get existing extension ; -- If extension exists ; -- Logoff Device + Extension ; -- Login ; -- If no extension exists ; -- Remove device from AstDB ; -- Login ; -- If no device exists for NewExtension ; -- Login ; Tests: ; * Login 100 to 0000FFFF0001 ; * Login 101 to 0000FFFF0001 (Result: Only 101 logged in) ; * Login 101 to 0000FFFF0002 (Result: Only 101 logged in to new location) ; * Login 100 to 0000FFFF0001 (Result: Both 100 and 101 logged in) ; * Login 100 to 0000FFFF0002 (Result: Only 100 logged into 0000FFFF0002 ; -- change locations) ; * Login 100 to 0000FFFF0001 (Result: Only 100 logged in) same => n,Set(ExistingExtension=${DB(HotDesk/${NewPeerName})}) same => n,GotoIf($[${EXISTS(${ExistingExtension})}]?get_existing_device) same => n(check_device),NoOp() same => n,Set(ExistingDevice=${DB(HotDesk/${NewExtension})}) same => n,GotoIf($[${EXISTS(${ExistingDevice})}]?get_existing_extension) same => n,NoOp(Nothing to logout) same => n,Goto(login) same => n(get_existing_device),NoOp() same => n,Set(ExistingDevice=${DB(HotDesk/${ExistingExtension})}) same => n,GotoIf($[${ISNULL(${ExistingDevice})}]?login) same => n,GoSub(subDeviceLogoff,1(${ExistingDevice},${ExistingExtension})) same => n,GotoIf($[${GOSUB_RETVAL} = 0]?check_device) same => n,Playback(silence/1&an-error-has-occurred) same => n,Hangup() same => n(get_existing_extension),NoOp() same => n,Set(ExistingExtension=${DB(HotDesk/${ExistingDevice})}) same => n,GoSubIf($[${EXISTS(${ExistingExtension})}]? subDeviceLogoff,1(${ExistingDevice},${ExistingExtension}):remove_device) same => n,GotoIf($[${GOSUB_RETVAL} = 0]?loggedoff) same => n,Playback(silence/1&an-error-has-occurred) same => n,Hangup() same => n(remove_device),NoOp() same => n,Set(Result=${DB_DELETE(HotDesk/${ExistingDevice})}) same => n,Goto(loggedoff) same => n(loggedoff),Verbose(2,Existing device and extensions have been logged off prior to login) same => n(login),Verbose(2,Now logging in extension ${NewExtension} to device ${NewPeerName}) same => n,GoSub(subDeviceLogin,1(${NewPeerName},${NewExtension})) same => n,GotoIf($[${GOSUB_RETVAL} = 0]?login_ok) same => n,Playback(silence/1&an-error-has-occurred) same => n,Hangup() same => n(login_ok),Playback(silence/1&agent-loginok) same => n,Hangup() exten => subDeviceLogoff,1,NoOp() same => n,Set(LOCAL(PeerName)=${ARG1}) same => n,Set(LOCAL(Extension)=${ARG2}) same => n,ExecIf($[${ISNULL(${LOCAL(PeerName)})} | ${ISNULL(${LOCAL(Extension)})}]?Return(-1)) same => n,Set(PeerNameResult=${DB_DELETE(HotDesk/${LOCAL(PeerName)})}) same => n,Set(ExtensionResult=${DB_DELETE(HotDesk/${LOCAL(Extension)})}) same => n,Return(0) exten => subDeviceLogin,1,NoOp() same => n,Set(LOCAL(PeerName)=${ARG1}) same => n,Set(LOCAL(Extension)=${ARG2}) same => n,ExecIf($[${ISNULL(${LOCAL(PeerName)})} | ${ISNULL(${LOCAL(Extension)})}]?Return(-1)) same => n,Set(DB(HotDesk/${LOCAL(PeerName)})=${LOCAL(Extension)}) same => n,Set(DB(HotDesk/${LOCAL(Extension)})=${LOCAL(PeerName)}) same => n,Set(ReturnResult=${IF($[${DB_EXISTS(HotDesk/${LOCAL(PeerName)})} & ${DB_EXISTS(HotDesk/${LOCAL(Extension)})}]?0:-1)}) same => n,Return(${ReturnResult})
Hot-desking is a fairly common feature that is gathering increased traction as Asterisk systems are deployed because of the inherent flexibility the dialplan provides. Older, traditional PBX systems apply an extension number to either a line on the system or with a device itself. With Asterisk, we have the ability to apply dialplan logic and information stored in a local database (or external database) to determine where an extension rings. We could easily develop a system where an extension number does nothing but ring a cell phone, or a combination of devices (like in a paging system, or a group of sales agents).
In the dialplan provided for this
example of hot-desking, we’ve allowed people to log in to any device by
dialing 71
where XX
1
is the person’s
extension number in the range 100 through 199. To log out the extension
from the device, the user simply dials XX
7000
from the device to log out from. While it
has made the dialplan and logic more complicated, the dialplan also
takes into account other extensions already logged into a device someone
wants to log in to, and automatically logs them out first. Additionally,
if we were logged into another device previously and didn’t log out
before changing locations, the dialplan will log the extension out from
the other device first before logging it into the new location.
No external scripts or databases[2] have been employed, which helps demonstrate the flexibility of Asterisk. Let’s look at what happens when we log in an extension to a device that has no existing extension logged in.
First we do our checks as described by the logic flow in the comment block in the code. Figure 2-1 shows the visual representation of what we’re checking for before logging the extension in.
You’ll need to be aware we haven’t added any logic to authenticate callers. This may not be necessary, but, if so, you can add some additional logic using one of the caller authentication recipes found in Chapter 1. Additionally, we haven’t added any prompts notifying the callers that an existing extension is logged in prior to logging them out, as we wanted to keep the fundamental logic of the hot-desking application so you have a base to work with.
To log in extension 100, we’ll dial
7100
, which will place the
appropriate information into the AstDB: two rows located within the
HotDesk
family. After you’ve logged
in, you can see the entries in the database by entering database show HotDesk
from the Asterisk
console:
*CLI> database show HotDesk
/HotDesk/0000FFFF0001 : 100
/HotDesk/100 : 0000FFFF0001
2 results found.
The two entries are to provide a link between an extension→device and device→ extension. When logging off a device we’ll need to know what extension to remove from the database, but when dialing an extension we’ll need to know what device to call.[3] The flowchart in Figure 2-1 shows how we’ve performed our checks to help account for various situations. If we log in to a device, we have to make sure there wasn’t another extension already logged in, and if so, log it out first. Another situation is where we were logged into another device and moved somewhere else, and need to move locations, which means we have to log out our extension from the other device first. And because we have multiple entries associated with each login, we have to verify that we’ve removed and modified both entries.
The following tests were performed to validate the logic used:
Using these tests, you can then step through the logic and look at the dialplan to understand all the checks going on, and validate that things are working as they should.
It is possible that if two people
attempt to log in to the same extension at the same time, or if
someone else is logging into a device that was previously logged into
by an extension moving locations (and attempting to log in at the same
time as the other person) that the database could get out of sync. No
locking has been performed here in order to keep the logic as clean
and simple as possible. If there exists a strong possibility of people
changing locations and logging in and out often on top of each other,
then you may wish look into adding dialplan locking, which can be done
using the LOCK()
and UNLOCK()
dialplan functions.
In more than one situation, the promise of hot-desking being just one more feature of the system is what eventually convinced a company to go with Asterisk because hot-desking was the killer application they were looking for.
Authenticating Callers, Counting and Conditionals, Looping in the Dialplan. Many of the concepts used in this dialplan stem directly from Chapter 10, “Deeper Into The Dialplan,” in Asterisk: The Definitive Guide (Expressions and Variable Manipulation, Dialplan Functions, Conditional Branching, GoSub, and Using the Asterisk Database).
[2] Although using an external database may actually have simplified the logic.
[3] Because the AstDB is based on the usage of a family/key relationship, we need two entries and to keep them synchronized. This is a good reason why using an external relational database could actually simplify the hot-desking logic.