Making outbound calls
Today we are going to discuss making outbound calls using Microsoft Speech Server 2007. There are basically two types of calls in Speech Server - inbound calls and outbound calls. I discussed inbound calls in a previous post where I covered the Hello World app. Outbound calls are similar in most ways, with the obvious difference the way the call is made. Outbound calls also require a bit more error detection than inbound calls because you need to handle what happens if the call fails to connect.
To take a look at outbound calls, lets create an outbound version of the Hello World app.
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Threading;
using System.Diagnostics;
using System.Text.RegularExpressions;
using System.Collections.Specialized;
using Microsoft.SpeechServer.Recognition;
using Microsoft.SpeechServer.Synthesis;
using Microsoft.SpeechServer.Recognition.SrgsGrammar;
using Microsoft.SpeechServer.Dialog;
using System.Globalization;
using Microsoft.SpeechServer;
namespace HelloWorld
{
/// <summary>
/// Hello World outbound app
/// </summary>
public class Class1 : IHostedSpeechApplication
{
private IApplicationHost Host;
private string _calledParty = "5551212";
private string _callingParty = "1234567";
/// <summary>
/// Set the lifetime of te application to match that of a call
/// </summary>
public bool IsReusable
{
get { return false; }
}
/// <summary>
/// Startup method for app
/// </summary>
/// <param name="host">The host interface</param>
void IHostedSpeechApplication.Start(IApplicationHost host)
{
Host = host;
Host.TelephonySession.OpenCompleted += new EventHandler<AsyncCompletedEventArgs>(TelephonySession_OpenCompleted);
Host.TelephonySession.OpenAsync(_calledParty, _callingParty);
}
/// <summary>
/// Called when the open has completed
/// </summary>
/// <param name="sender">The sender of the events</param>
/// <param name="e">Information about the event</param>
void TelephonySession_OpenCompleted(object sender, AsyncCompletedEventArgs e)
{
if (e.Error != null)
{
if (e.Error is SipPeerBusyException)
{
SipPeerBusyException spbe = e.Error as SipPeerBusyException;
if (spbe.ResponseCode == 486)
{
// Line was busy, queue this call for a bit later
}
else if (spbe.ResponseCode == 603)
{
// No answer, try again later
}
else
{
// We could not connect, but we don't know why so log it
Host.TelephonySession.LoggingManager.LogApplicationInformation(200, "Could not connect due to response code {0}", spbe.ResponseCode);
}
}
else if (e.Error is SipPeerAddressUnreachableException)
{
// A possible reason is the number does not exist but could be other
// reasons such as the number is not recognizable from the gateway
Host.TelephonySession.LoggingManager.LogApplicationInformation(200, "Address unreachable {0}", _calledParty);
}
else if (e.Error is SipPeerTimeoutException)
{
// The message got as far as the gateway but timed out
Host.TelephonySession.LoggingManager.LogApplicationWarning(300, "SIP Peer timed out while calling {0}: {1}", _calledParty, e.Error.Message);
}
else
{
Host.TelephonySession.LoggingManager.LogApplicationError("An unexpected error occurred in TelephonySession_AcceptCompleted:" + e.Error.ToString());
}
Host.TelephonySession.Close();
}
else
{
Host.TelephonySession.Synthesizer.SpeakCompleted += new EventHandler<SpeakCompletedEventArgs>(Synthesizer_SpeakCompleted);
Host.TelephonySession.Synthesizer.SpeakAsync("Hello World", SynthesisTextFormat.PlainText);
}
}
/// <summary>
/// Called if an exception occurs in your code
/// </summary>
/// <param name="exn">The exception that occurred</param>
public void OnUnhandledException(Exception exn)
{
Host.TelephonySession.LoggingManager.LogApplicationError(555, "An unhandled exception occurred during test case VariableCheck error: " + exn.ToString());
}
/// <summary>
/// Called to indicate that the server is shutting down
/// </summary>
/// <param name="immediate">Indicates this is a final warning and the app should stop immediately</param>
public void Stop(bool immediate)
{
}
/// <summary>
/// Called when the prompt has finished playing
/// </summary>
/// <param name="sender">The object that sent the event</param>
/// <param name="e">Information about the event</param>
private void Synthesizer_SpeakCompleted(object sender, SpeakCompletedEventArgs e)
{
if (e.Error != null)
{
Host.TelephonySession.LoggingManager.LogApplicationError(100, "Error in synthesizer prompt: " + e.Error.ToString());
}
Host.TelephonySession.Close();
}
}
}
Some of these parts will be familiar to you from the inbound app. The way we play the prompt is the same, as are the standard overrides from IHostedSpeechApplication. The main difference is the implementation of Start and the code to handle the opened session.
To create an outbound call, you just need to call OpenAsync on the ITelephonySession object. Of course this method is asynchronous so we need to set our event handler. OpenAsync has three different signatures. The first signature, which is the most commonly used, takes the number to call. The second signature, used here, allows you to overide both the ANI and the DNIS. Why would you need to override the ANI? The primary reason for doing this is in applications where you are calling on behalf of someone. For instance, say you have an app that calls clients on behalf of dental offices to remind them of coming appointments. You may want to have the caller ID of the patient read the phone number of the dental office, rather than your phone number. Note that for this to work it must be supported by the gateway. The final signature allows you to specify a SIP peer address and won't be discussed further here.
Once we reach the event handler, things begin to get interesting. What we need to do here is determine if the call succeeded. To do this, we check the e.Error object. However, unlike previous cases, an error here may not be catastrophic. Several different exceptions may have been returned as the e.Error object.
SipPeerBusyException - This is the exception you will probably most often see. It occurs if the called party does not answer the phone or if the phone is busy. In these cases you will probably want to requeue the call for another time when the party may not be busy. To see whether the called party was busy or didn't answer, just check the SIP response code.
SipPeerAddressUnreachableException - One likely cause of this is the number is no longer in service. However, this is not necessarily the case and could mean that a gateway ran into difficulties. You will likely need to review the response information to determine what actually happened. I currently do not know the exact response codes to look for but if you need this information please add a note and I will add it in a future blog.
SipPeerTimeoutException - This indicates that the message got as far as the gateway but then timed out. You will likely want to log this case to help determine if there are any gateway issues.
Other exceptions can occur, as well as other SipPeerExceptions with different response codes. You will want to log these to ensure your application can handle all the cases it runs into in the field.
Of course, in the best scenario no error occurs and you can continue with the call. From here on, it generally does not make a difference whether the call is an inbound or an outbound app. Any use of the Synthesizer or Recognizer objects will be identical regardless of the call type.
Now that I have covered making and receiving the call, future blogs will talk more about prompting and querying the user.