Chapter 2. Call Control

Introduction

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.

Creating Call Limits Using Groups

Problem

You would like to implement custom call limits in the Asterisk dialplan.

Solution

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})

Discussion

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.

See Also

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”).

Originating a Call Using the CLI

Problem

As an Asterisk system administrator, you would like to quickly originate a new call from the Asterisk command-line interface.

Solution

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

Discussion

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

Originating a Call Using the Dialplan

Problem

You would like to originate a call from the Asterisk dialplan.

Solution

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)

Discussion

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.

See Also

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.

Originating a Call From a Call File

Problem

You would like to programmatically originate a call outside of Asterisk directly from the Asterisk server in the simplest way possible.

Solution

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/

Discussion

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.

Originating a Call From the Manager Interface

Problem

You would like to programmatically originate a call over a network connection to Asterisk.

Solution

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

Discussion

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:

ActionID: <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.

Variable: 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.

Account

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

See Also

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.

Using the FollowMe() Dialplan Application

Problem

You would like to call a series of phone numbers to attempt to locate a person when their extension is dialed.

Solution

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)

Discussion

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.

See Also

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.

Building Find-Me-Follow-Me in the Dialplan

Problem

You would like to implement find-me-follow-me in the Asterisk dialplan.

Solution

[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)

Discussion

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:

The called party does nothing.

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 called party presses 1 to accept.

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 called party presses 2 to reject.

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.

The caller presses a different key.

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.

See Also

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.

Creating a Callback Service in the Dialplan

Problem

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.

Solution

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()

Discussion

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.

See Also

The Asterisk project wiki, http://wiki.asterisk.org/, discusses CCSS.

Hot-Desking with the Asterisk Database

Problem

You need people to be able to log in to any device and accept calls at that location, a feature known as hot-desking.

Solution

[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})

Discussion

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 71XX where 1XX is the person’s extension number in the range 100 through 199. To log out the extension from the device, the user simply dials 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.

Note

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.

Login check logic
Figure 2-1. Login check logic

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 extensiondevice 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:

  • 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)

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.

Warning

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.

See Also

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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset