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 manipulate 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, available from http://www.webdav.org/neon/. 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.[155]

CentOS dependencies

Since all the modules require the neon library, we’ll install that first. Be sure to append .x86_64 to the end of the package name if installing on a 64-bit machine:

$ sudo yum install neon-devel

Note

If you are planning on compiling the res_calendar_ews module, you will need to have a version of neon greater than or equal to 0.29. Currently CentOS is shipping with 0.25, so you will have to compile the neon library and link to it from the configure script. This can be done via ./configure --with-neon29=<path to neon>.

The next step is to install the libical-devel dependency. Unfortunately, this module is not shipped with CentOS and requires a third-party repository (see Third-Party Repositories). In this case, we need to install libical-devel from the EPEL (Extra Packages for Enterprise Linux) repository:

$ sudo yum --enablerepo=epel 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. On Ubuntu, you will typically be given several different versions (e.g., on 10.04 we have the option of libneon 2.5, 2.6, and 2.7). We’re going to install the latest version available to us:

$ sudo apt-get install libneon27-dev

Note

If you are planning on compiling the res_calendar_ews module, you will need to have neon 0.29 or greater. Currently Ubuntu is shipping with 0.27, so you will have to compile the neon library and link to it from the configure script. This can be done via ./configure --with-neon29=<path to neon>.

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 (http://www.gmail.com) 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 for the next hour or two. When we configure our calendar.conf file we’ll be instructing Asterisk to check for new events every 15 minutes, and pulling in 60 minutes’ worth of data.

Tip

Be sure to verify the time on your server. If the time is not in sync with the rest of the world—e.g., if is not updated via the Network Time Protocol (NTP)—your events may not show, or may show at the wrong times. This tip is the result of running into this very issue while testing and documenting. : )

The next step is to configure our calendar.conf file for polling our calendar server.

Tip

The calendar.conf.sample file has several examples for calendaring servers, such as those supplied by Microsoft Exchange–, iCal-, and CalDAV-based calendar servers.

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 your calendar.conf file 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.[156]

$ 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, which are used to dial a destination upon the start of a meeting, that 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 of 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.

Tip

If you have not configured any of the Notify options but have an alarm set to remind you in the calendar, you may get a WARNING message such as:

WARNING[5196]: res_calendar.c:648 do_notify: Channel should be in 
form Tech/Dest (was '')

The reason for the warning is that an alarm was set for notification about the start of the meeting, but Asterisk was unable to generate a call due to values not being configured to place the call. This warning message can be safely ignored if you don’t plan on placing calls for event notifications.

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 you can do now that you have your calendaring information in Asterisk, and to configure dialing notifications to remind you 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 gives you a good taste of the possibilities that exist for triggering calls based on calendar state.

Triggering a wakeup call

In our first 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

Tip

In your calendar, you need to make sure the event you’re adding has an alarm or reminder associated with it. Otherwise, Asterisk won’t try to generate a 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')

Tip

If you modify the calendar event so it’s just a couple of minutes in the future, you can trigger the events quickly by unloading and then loading the res_calendar_caldav.so module from the Asterisk console. By doing that, you’ll trigger Asterisk to generate the call immediately.

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.

Note

We haven’t specified SIP/0000FFFF0002 directly in the calendar event because we want to be a bit more secure with what we accept. Because we’re going to filter out anything but alphanumeric characters, we won’t be able to accept a forward slash as the separator between the technology and the location (e.g., SIP/0000FFFF0001). We could certainly allow this, but then we would run the risk of people making expensive outbound calls, especially if a user opens his calendar publicly or is compromised. With the method we’re going to employ, we simply limit our risk.

Add the following dialplan to your 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 upon 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 for 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 making use of 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, and additional 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 tools we’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 on this will open a new window where you can create the calendar. Go ahead and do that now. 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 http://www.google.com/support/mobile/bin/answer.py?answer=151674. 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 Google Apps users. Select the appropriate link and open it. You will then be presented with a page that contains your calendars. 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 so:

[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[157] 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 for, so we can get a more accurate assessment of whether a call was answered and, if so, how long the answered portion lasted for. 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.

Conclusion

In this section we’ve learned how to integrate Asterisk with an external calendar server such as that provided by Google, but the concepts for attaching to other calendaring servers remain the same. We explored how to set up a meeting between two participants, and how to set up a multiparty conference using information obtained from the description field in the calendar. We also looked at how to control calls using the CALENDAR_BUSY() function, to redirect calls to voicemail when our current event describes us as busy. By implementing this type of functionality in Asterisk, you can see the power we have to control call flow using external services such as those supplied by a calendar server.

And we didn’t even get to dive into every use of the calendar implementation—there exist other calendar functions, 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.



[155] And because the authors of this book do not have access to Exchange servers for testing. : )

[156] As of this writing, there is a bug in the process of loading of the calendar modules after Asterisk has been started. It was filed as issue 18067 at https://issues.asterisk.org and hopefully will have been resolved by the time you read this. If not, be aware that you may need to restart Asterisk to get the modules loaded into memory correctly.

[157] In Unix, the epoch is the number of seconds that have elapsed since January 1, 1970, not counting leap seconds.