External Services
External Services
Asterisk is pretty nifty all by itself, but one of the most powerful, industry-changing, revolutionary aspects of Asterisk is the sheer number of wonderful ways it may be connected to external applications and services. This is truly unprecedented in the world of telecom. In this topic, we’ll explore some popular services and applications that you can integrate with your Asterisk system.
There are many more external services that Asterisk can connect to, but these are the ones we feel will give you the best sense of what it takes to integrate an external service with Asterisk.
Calendar Integration
Asterisk can be integrated with several different kinds of calendar formats, such as iCal, CalDAV, MS Exchange (Exchange 2003), and MS Exchange Web Services (Exchange 2007 and later). Integrating Asterisk with your calendar gives you the ability to manip‐
ulate call routing based on your current calendar information. For example, if you’re not going to be in your office for the afternoon, it may make sense for people ringing your desk phone to be routed directly to your voicemail. Another advantage to calendar integration is the ability to originate calls based on calendar information. For example, if you set up a meeting on your conference server, you can arrange to have a reminder call five minutes before the meeting starts, which then places you into the conference room. We think this type of flexibility and integration is pretty nifty and quite useful.
Compiling Calendaring Support into Asterisk
As there are several modules for calendaring support (allowing us to provide support for different backends, such as MS Exchange, CalDAV, iCal, etc.), you’ll need to install the dependencies for the backends you want to support. This modularized setup has the advantage that you only need to install dependencies for the modules you need; also, other backends can easily be integrated with the primary calendaring backend in the future.
Because of the different dependencies of each module, we need to check menuselect for what needs to be installed for each of the calendaring modules we wish to support. All modules require the neon development library. res_calendar_ews (Exchange Web Services) requires version 0.29 or later, which means some distributions will require you to compile the neon library from source instead of using the precompiled package available from the distribution.
While the configuration for all the calendaring modules is similar, we’ll be discussing CalDAV integration specifically since it is widely supported by a range of calendar software and servers.
RHEL dependencies
Since all the modules require the neon library, we’ll install that first:
$ sudo yum install neon-devel
The next step is to install the libical-devel dependency:
$ sudo yum install libical-devel
After installing our dependencies, we can run the configure script in our Asterisk source directory and enable both the res_calendar and res_calendar_caldav modules from within the Resource Modules section of menuselect.
Ubuntu dependencies
Because all the modules require the neon development library, we’re going to install that first. We’re going to install the latest version available to us:
$ sudo apt-get install libneon27-dev
With libneon installed, we can now install the libical-dev package and its dependencies with apt-get:
$ sudo apt-get install libical-dev
After installing our dependencies, we can run the configure script in our Asterisk source directory and enable both the res_calendar and res_calendar_caldav modules from within the Resource Modules section of menuselect.
Configuring Calendar Support for Asterisk
In this section we’re going to discuss how to connect your Asterisk system to a Google calendar. We’re using calendars from Google for the simple reason that they don’t require any other configuration (such as setting up a calendaring server), which gets us up and running far quicker. Of course, once you’re comfortable with configuring calendaring support in Asterisk, you can connect it to any calendaring server you desire.
The first step is to make sure you have a Gmail account with Google, which will get you access to a calendaring server. Once you’ve logged into your Gmail account, there should be a link to your calendar in the upper-left corner. Click on the Calendar link and insert a couple of items occurring within the next hour or two. When we configure our calen dar.conf file, we’ll be instructing Asterisk to check for new events every 15 minutes and pull in 60 minutes’ worth of data.
The next step is to configure our calendar.conf file for polling our calendar server.
The following configuration will connect to the Google calendaring server and poll for new events every 15 minutes, retrieving 60 minutes’ worth of data. Feel free to change these settings as necessary, but be aware that pulling more data (especially if you have multiple calendars for people in your company) will utilize more memory:
$ cat >> calendar.conf
[myGoogleCal]
type=caldav
url=https://www.google.com/calendar/dav/<Gmail Email Address>/events/
user=<Gmail Email Address>
secret=<Gmail Password>
refresh=15
timeframe=60
Ctrl+D
With calendar.conf configured, let’s load the calendaring modules into Asterisk. First we’ll load the res_calendar.so module into memory, then we’ll follow it up by doing a module reload, which will load the sister modules (such as res_calendar_caldav.so) correctly:
$ asterisk -r
*CLI> module load res_calendar.so
*CLI> module reload
After loading the modules we can check to make sure our calendar has connected to the server and been loaded into memory correctly, by executing calendar show calendars:
*CLI> calendar show calendars
Calendar Type Status
——– —- ——
myGoogleCal caldav busy
Our status is currently set to busy (which doesn’t have any bearing on our dialplan at the moment, but simply means we have an event that has marked us as busy in the calendar), and we can see the currently loaded events for our time range by running calendar show calendar <myGoogleCal> from the Asterisk console:
*CLI> calendar show calendar <myGoogleCal>
Name : myGoogleCal
Notify channel :
Notify context :
Notify extension :
Notify applicatio :
Notify appdata :
Refresh time : 15
Timeframe : 60
Autoreminder : 0
Events
——
Summary : Awesome Call With Russell
Description :
Organizer :
Location :
Cartegories :
Priority : 0
UID : hlfhcpi0j360j8fteop49cvk68@google.com
Start : 2010-09-28 08:30:00 AM -0400
End : 2010-09-28 09:00:00 AM -0400
Alarm : 2010-09-28 04:20:00 AM -0400
The first field in the top section is the Name of our calendar. Following that are several Notify fields that are used to dial a destination upon the start of a meeting, which we’ll discuss in more detail shortly. The Refresh time and Timeframe fields are the values we configured for how often to check for new events and how long a range we should look at for data, respectively. The Autoreminder field controls how long prior to an event we should execute the Notify options.
The rest of the screen output is a listing of events available within our Timeframe, along with information about the events. The next steps are to look at some dialplan examples of what we can do now that we have calendaring information in Asterisk, and to con‐ figure dialing notifications for reminders about upcoming meetings.
Triggering Calendar Reminders to Your Phone
In this section we’ll discuss how to configure the calendar.conf file to execute some simple dialplan that will call your phone prior to a calendar event. While the dialplan we’ll provide might not be ready for production, it certainly offers a good taste of the possibilities that exist for triggering calls based on calendar state.
Triggering a wakeup call
In our example, we’re going to call a device and play back a reminder notice for a particular calendar event. It might be useful to get this type of reminder if you’re likely to be napping at your desk when your weekly Monday meeting rolls around. To set up a wakeup call reminder, we simply need to add the following lines to our calendar configuration in calendar.conf:
channel=SIP/0000FFFF0001
app=Playback
appdata=this-is-yr-wakeup-call
After making this change, reload the res_calendar.so module from the Asterisk console:
*CLI> module reload res_calendar.so
When the event rolls around, Asterisk will generate a call to you and play back the sound file this-is-yr-wakeup-call. The output on the console would look like this:
— Dialing SIP/0000FFFF0001 for notification on calendar myGoogleCal
== Using SIP RTP CoS mark 5
— Called 0000FFFF0001
— SIP/0000FFFF0001-00000001 is ringing
— SIP/0000FFFF0001-00000001 connected line has changed, passing it to Calendar/myGoogleCal-5fd3c52
— SIP/0000FFFF0001-00000001 answered Calendar/myGoogleCal-5fd3c52
— <SIP/0000FFFF0001-00000001> Playing ‘this-is-yr-wakeup-call.ulaw’ (language ‘en’)
Remember that our refresh rate is set to 15 minutes, and we’re gathering 60 minutes’ worth of events. You might have to adjust these numbers if you wish to test this out on your development server. Scheduling calls between two participants
In this example, we’re going to show how you can use a combination of some simple dialplan and the CALENDAR_EVENT() dialplan function to generate a call between two participants based on the information in the location field. We’re going to fill in the
location field with 0000FFFF0002 , which is the SIP device we wish to call after answering our reminder.
We add the following dialplan to our extensions.conf file:
[AutomatedMeetingSetup]
exten => start,1,Verbose(2,Triggering meeting setup for two participants)
same => n,Set(DeviceToDial=${FILTER(0-9A-Za-z,${CALENDAR_EVENT(location)})})
same => n,Dial(SIP/${DeviceToDial},30)
same => n,Hangup()
When the event time arrives, our device will receive a call, and when that call is answered another call will be placed to the endpoint with which we wish to have our meeting. The console output looks like the following: This is where our calendar triggers a call to our device
— Dialing SIP/0000FFFF0001 for notification on calendar myGoogleCal
== Using SIP RTP CoS mark 5
— Called 0000FFFF0001
— SIP/0000FFFF0001-00000004 is ringing And now we have answered the call from Asterisk triggered by an event
— SIP/0000FFFF0001-00000004 connected line has changed, passing it to Calendar/myGoogleCal-347ec99
— SIP/0000FFFF0001-00000004 answered Calendar/myGoogleCal-347ec99 Upon answer, we trigger some dialplan that looks up the endpoint to call
— Executing [start@AutomatedMeetingSetup:1] Verbose(“SIP/0000FFFF0001-00000004”, “2, Triggering meeting setup for two participants”) in new stack
== Triggering meeting setup for two participants
This is where we used CALENDAR_EVENT(location) to get the remote device
— Executing [start@AutomatedMeetingSetup:2] Set(“SIP/0000FFFF0001-00000004”, “DeviceToDial=0000FFFF0002”) in new stack
And now we’re dialing that endpoint
— Executing [start@AutomatedMeetingSetup:3] Dial(“SIP/0000FFFF0001-00000004”, “SIP/0000FFFF0002,30”) in new stack
== Using SIP RTP CoS mark 5
— Called 0000FFFF0002
— SIP/0000FFFF0002-00000005 is ringing
The other end answered the call, and Asterisk bridged us together
— SIP/0000FFFF0002-00000005 answered SIP/0000FFFF0001-00000004
— Locally bridging SIP/0000FFFF0001-00000004 and SIP/0000FFFF0002-00000005
Of course, the dialplan could be expanded to prompt the initial caller to acknowledge being ready for the meeting prior to calling the other party. Likewise, we could add some dialplan that plays a prompt to the other caller that lets her know that she has scheduled a meeting and that if she presses 1 she will be connected with the other party immediately. We could even have created a dialplan that would allow the original party to record a message to be played back to the other caller. Just for fun, we’ll show you an example of the functionality we just described. Feel free to modify it to your heart’s content:
[AutomatedMeetingSetup]
exten => start,1,Verbose(2,Triggering meeting setup for two participants)
; *** This line should not have any line breaks
same => n,Read(CheckMeetingAcceptance,to-confirm-wakeup&press-1&otherwise&press-2,,1)
same => n,GotoIf($[“${CheckMeetingAcceptance}” != “1”]?hangup,1)
same => n,Playback(silence/1&pls-rcrd-name-at-tone&and-prs-pound-whn-finished)
;We set a random number and assign it to the end of the recording
;so that we have a unique filename in case this is used by multiple
;people at the same time.
;We also prefix it with a double underscore because the channel
;variable also needs to be available to the channel we’re going to call
;
same => n,Set(__RandomNumber=${RAND()})
same => n,Record(/tmp/meeting-invite-${RandomNumber}.ulaw)
same => n,Set(DeviceToDial=${FILTER(0-9A-Za-z,${CALENDAR_EVENT(location)})})
same => n,Dial(SIP/${DeviceToDial},30,M(CheckConfirm))
same => n,Hangup()
exten => hangup,1,Verbose(2,Call was rejected)
same => n,Playback(vm-goodbye)
same => n,Hangup()
[macro-CheckConfirm]
exten => s,1,Verbose(2,Allowing called party to accept or reject)
same => n,Playback(/tmp/meeting-invite-${RandomNumber})
; *** This line should not have any line breaks
same => n,Read(CheckMeetingAcceptance,to-confirm-wakeup&press-1&otherwise
&press-2,,1)
same => n,GotoIf($[“${CheckMeetingAcceptance}” != “1”]?hangup,1)
exten => hangup,1,Verbose(2,Call was rejected by called party)
same => n,Playback(vm-goodbye)
same => n,Hangup()
We hope you’ll be able to use this simple dialplan example as a jumping-off point. With a little creativity and some dialplan skills, the possibilities are endless!
Calling meeting participants and placing them into a conference
To expand on the functionality in the previous section, we’re going to delve into the logic problem of how you might be able to place multiple participants into a meeting. Our goal is to use our calendar to call us when the meeting is scheduled to start, and then, when we answer, to place calls to all the other members of the conference. As the other participants answer their phones, they will be placed into a virtual conference room, where they will wait for the meeting organizer to join. After all participants have been dialed and answered (or perhaps not answered), the organizer will be placed into the call, at which point the meeting will start. This type of functionality increases the likelihood that the meeting will start on time, and it means the meeting organizer doesn’t have to continually perform roll call as new participants continue to join after the call is supposed to start (which invariably happens, with people’s schedules typically being fairly busy). The dialplan we’re going to show you isn’t necessarily a polished, production-ready
installation (for example, the data returned from the calendar comes from the description field, only deals with device names, and assumes the technology is SIP). However, we’ve done the hard work for you by developing the Local channel usage, along with
the M() flag (macro) usage with Dial() . With some testing and tweaks, this code could certainly be developed more fully for your particular installation, but we’ve kept it general to allow it to be usable for more people in more situations. The example dialplan looks like this:
[AutomatedMeetingSetup]
exten => start,1,Verbose(2,Calling multiple people and placing into a conference)
; Get information from calendar and save that information. Prefix
; CalLocation with an underscore so it is available to the Local
; channel (variable inheritance).
;
same => n,Set(CalDescription=${CALENDAR_EVENT(description)})
same => n,Set(_CalLocation=${CALENDAR_EVENT(location)})
same => n,Set(X=1)
; Our separator is a caret (^), so the description should be in the
; format of:
0000FFFF0001^0000FFFF0002^etc…
;
same => n,Set(EndPoint=${CUT(CalDescription,^,${X})})
; This loop is used to build the ${ToDial} variable, which contains
; a list of Local channels to be dialed, thereby triggering the multiple
; Originate() actions simultaneously instead of linearly
;
same => n,While($[${EXISTS(${EndPoint})}])
; This statement must be on a single line
same => n,Set(ToDial=${IF($[${ISNULL(${ToDial})}]?
:${ToDial}&)}Local/${EndPoint}@MeetingOriginator)
same => n,Set(X=$[${X} + 1])
same => n,Set(EndPoint=${CUT(CalDescription,^,${X})})
same => n,EndWhile()
; If no values are passed back, then don’t bother dialing
same => n,GotoIf($[${ISNULL(${ToDial})}]?hangup)
same => n,Dial(${ToDial})
; After our Dial() statement returns, we should be placed into
; the conference room. We are marked, so the conference can start
; (which is indicated by the ‘A’ flag to MeetMe).
;
same => n,MeetMe(${CalLocation},dA)
same => n(hangup),Hangup()
[MeetingOriginator]
exten => _[A-Za-z0-9].,1,NoOp()
same => n,Set(Peer=${FILTER(A-Za-z0-9,${EXTEN})})
; Originate calls to a peer as passed to us from the Local channel. Upon
; answer, the called party should execute the dialplan located at the
; _meetme-XXXX extension, where XXXX is the conference room number.
;
same => n,Originate(SIP/${Peer},exten,MeetingOriginator,meetme-${CalLocation},1)
same => n,Hangup()
; Join the meeting; using the ‘w’ flag, which means ‘wait for marked
; user to join before starting’
;
exten => _meetme-XXXX,1,Verbose(2,Joining a meeting)
same => n,Answer()
same => n,MeetMe(${EXTEN:7},dw)
same => n,Hangup()
Controlling Calls Based on Calendar Information
Sometimes it is useful to redirect calls automatically—for example, when you’re in a meeting or on vacation. In this section we’ll be using the CALENDAR_BUSY() dialplan function, which allows us to check the current status of our calendar to determine if we’re busy or not. A simple example of this would be to send all calls to voicemail using the busy message whenever an event that marks us as busy has been scheduled. The following dialplan shows a simple example where we check our calendar for busy status prior to sending a call to a device. Notice that a lot of the information in this example is static; more effort would be required to make it dynamic and suitable for production:
exten => 3000,1,Verbose(2,Simple calendar busy check example)
same => n,Set(CurrentExten=${EXTEN})
same => n,Set(CalendarBusy=${CALENDAR_BUSY(myGoogleCal)})
same => n,GotoIf($[“${CalendarBusy}” = “1”]?voicemail,1)
same => n,Dial(SIP/0000FFFF0002,30)
same => n,Goto(voicemail,1)
exten => voicemail,1,Verbose(2,Caller sent to voicemail)
; *** This line should not have any line breaks
same => n,GotoIf($[“${DIALSTATUS}” = “BUSY” |
“${CalendarBusy}” = “1”]?busy:unavail)
same => n(busy),VoiceMail(${CurrentExten}@shifteight,b)
same => n,Hangup()
same => n(unavail),VoiceMail(${CurrentExten}@shifteight,u)
same => n,Hangup()
And here is a slightly more elaborate section of dialplan that utilizes a few of the toolsvwe’ve learned throughout the book, including DB_EXISTS() , GotoIf() , and the IF() function:
exten => _3XXX,1,Verbose(2,Simple calendar busy check example)
same => n,Set(CurrentExten=${EXTEN})
same => n,GotoIf($[${DB_EXISTS(extension/${CurrentExten}/device)}]? :no_device,1)
same => n,Set(CurrentDevice=${DB_RESULT})
same => n,GotoIf($[${DB_EXISTS(extension/${CurrentExten}/calendar)}]? :no_calendar)
same => n,Set(CalendarBusy=${CALENDAR_BUSY(${DB_RESULT})})
same => n,GotoIf($[${CalendarBusy}]?voicemail,1)
same => n(no_calendar),Verbose(2,No calendar was found for this user)
same => n,Dial(SIP/${CurrentDevice},30)
same => n,Goto(voicemail,1)
exten => voicemail,1,Verbose(2,Sending caller to voicemail)
; *** This line should not have any line breaks
same => n,GotoIf($[${DB_EXISTS(extension/${CurrentExten}/voicemail_context)}]
?:no_voicemail)
same => n,Set(VoiceMailContext=${DB_RESULT})
; *** This line should not have any line breaks
same => n,Set(VoiceMailStatus=${IF($[“${DIALSTATUS}” = “BUSY” |
0${CalendarBusy}]?b:u)})
same => n,VoiceMail(${CurrentExten}@${VoiceMailContext},${VoiceMailStatus})
same => n,Hangup()
same => n(no_voicemail),Playback(number-not-answering)
same => n,Hangup()
exten => no_device,1,Verbose(2,No device found in the DB)
same => n,Playback(invalid)
same => n,Hangup()
Writing Call Information to a Calendar
Using the CALENDAR_WRITE() function opens some other possibilities in terms of calendar integration. From the Asterisk dialplan, we can insert information into a calendar, which can be consumed by other devices and applications. Our next example is a calendar that tracks call logs. For anyone who may be on the phone a fair amount who needs to track time for clients, writing all calls to a calendar for a visual reference can be useful when verifying things at the end of the day. We’re going to utilize the Google web calendar again for this example, but we’re going to create a new, separate calendar just for tracking calls. In order to write to the calendar, we’ll need to set up our calendar.conf file a little bit differently, by using the CalDAV calendar format. First, though, we need to create our new calendar. On the left side of the Google calendar interface will be a link labeled Add. Clicking this will open a new window where we can create the calendar. We’ve called ours “Phone Calls.” Now we need to enable CalDAV calendar syncing for our calendar. Information about how to do this is located at a Google support page. This page notes that only your primary calendar will be synced to the device, but we want to make sure our calls are logged to a separate calendar so we can easily hide them (and so our smartphone doesn’t synchronize the phone’s calls either, which may cause confusion). There are two links near the bottom of the page: one for regular Google calendar users, and the other for GoogleApps users. When we click the appropriate link, our calendars appear. Then we see a page that contains our calendars. We select the Phone Calls calendar and then select Save.
Next up is configuring our calendar.conf file for Asterisk. One of the parameters we need is the link to the CalDAV calendar. There is a Calendar ID value that we need that will identify our calendar specifically. To find the calendar ID, click the down arrow beside the calendar name on the lefthand side of the calendar page and select Calendar Settings. Near the bottom of the calendar settings will be two rows that contain the icons for sharing the calendar (XML, ICAL, HTML). Beside the first set of icons inside the Calendar Address box will be the calendar ID. It will look like this:
(Calendar ID: 2hfb6p5974gds924j61cmg4gfd@group.calendar.google.com)
If you’re setting this up via Google Apps, the calendar ID will be prefixed with your domain name and an underscore (e.g., shifteight.org_ ). Make a note of this string, as we’re going to use it next. Open up the calendar.conf file and add a new calendar section. In our case we’ve called it [phone_call_calendar] . You’ll recognize the formatting of the calendar from earlier, so we won’t go through all the settings here. The key setting to note is the url parameter.
The format of this parameter is:
https://www.google.com/calendar/dav/<calendar_id>/events/
We need to replace the <calendar_id> with the calendar ID we recently made a note of. The full configuration then ends up looking like this:
[phone_call_calendar]
type=caldav
; The URL must be on a single line
url=https://www.google.com/calendar/dav/
shifteight.org_2hfb6p5974gds924j61cmg4gfd@group.calendar.google.com/events/
user = leif@shifteight.org
secret = my_secret_password
refresh=15
timeframe=120
Now that we have our calendar configured, we need to load it into memory, which can be done by reloading the res_calendar.so module:
*CLI> module reload res_calendar.so
Verify that the calendar has been loaded into memory successfully with the calendar show command:
*CLI> calendar show calendars
Calendar Type Status
——– —- ——
phone_call_calendar caldav free
With our calendar successfully loaded into memory, we can write some dialplan around our Dial() command to save our call information to the calendar with the CALENDAR_WRITE() function:
[LocalSets]
exten => _NXXNXXXXXX,1,Verbose(2,Outbound calls)
same => n,Set(CalendarStart=${EPOCH}) ; Used by CALENDAR_WRITE()
same => n,Set(X=${EXTEN}) ; Used by CALENDAR_WRITE()
same => n,Dial(SIP/ITSP/${EXTEN},30)
same => n,NoOp(Handle standard voicemail stuff here)
same => n,Hangup()
exten => h,1,Verbose(2,Call cleanup)
; Everything that follows must be on a single line
same => n,Set(CALENDAR_WRITE(phone_call_calendar,summary,description,
start,end)=
OUTBOUND: ${X},Phone call to ${X} lasted for ${CDR(billsec)} seconds.,
${CalendarStart},${EPOCH})
In our dialplan we’ve created a simple scenario where we place an outbound call through our Internet telephony service provider (ITSP), but prior to placing the call we save the epoch 3 to a channel variable (so we can use it later when we write our calendar entry at the end of the call). After our call, we write our calendar entry to the phone_call_calendar with the CALENDAR_WRITE() dialplan function within the built-in h extension. There are several options we can pass to the calendar, such as the summary, description, and start and end times. All of this information is then saved to the calendar. We’ve also used the CDR() dialplan function in our description to show the number of seconds the answered portion of the call lasted, so we can get a more accurate assessment of whether a call was answered and, if so, how long the answered portion lasted. We could also be clever and only write to the calendar if ${CDR(billsec)} was greater than 0 by wrapping the Set() application in an ExecIf() ; e.g.,
same => n,ExecIf($[${CDR(billsec)} > 0]?Set(CALENDAR_WRITE…)) .
Many possibilities exist for the CALENDAR_WRITE() function; this is just one that we’ve implemented and enjoy.
Additional Features
Other calendar functions are available, such as CALENDAR_QUERY() , which allows you to pull back a list of events within a given time period for a particular calendar, and CALENDAR_QUERY_RESULT() , which allows you to access the specifics of those calendar events. Additionally, you could create functionality that writes events into your calendar with the CALENDAR_WRITE() function: for example, you may wish to develop some dialplan that allows you to set aside blocks of times in your calendar from your phone when you’re on the road without access to your laptop. Many possibilities exist, and all it takes is a little creativity.