Writing UAS Applications

A UAS application is an application that is loaded into and run by a UAS. It is simply a C#, VB, F# or C++ class library assembly that uses the UASAppAPI and adheres to a few rules.

Each time a UAS application is run it instantiates a class that handles a single primary call and optionally a small number of secondary extra call channels. It is presented with the call channel objects that control these calls.

While a call is answered various speech processing features can be performed on it. Once the call is hung up the application should return. The overall result of the application run is returned to the UAS and logged.

See Class Diagram.

Creating a UAS Application Project

Creating a .NET UAS application is a straightforward task using the Visual Studio project templates supplied with the .NET UAS. These include a template each for inbound and outbound applications for the .NET languages C#, VB and F#. Currently templates for Visual Studio 2005, 2008, 2010, 2013 and 2015 are supplied.

To create a new .NET UAS Application project from the Visual Studio New Project dialog:

Select the language you wish to use - Visual C#, Visual Basic or Visual F# (F# is only available from Visual Studio 2010 onwards)
Select Aculab and one of:
  • Aculab UAS Inbound Application
  • Aculab UAS Outbound Application
The name entered in the Name field will determine name of the application, as displayed in the Management Console and entered in the Inbound or Outbound Service Registration pages via cloud.aculab.com your Rapide server.

Ensure that the Target Framework is set to the .Net Framework for your UAS.

The Inbound templates provide code that answers the primary call and then waits for the call to be hung up.

The Outbound templates provide code that use the outboundParameters argument as a destination address to initiate an outgoing call.

C++ Projects

As yet, there are no templates available for creation of C++ UAS applications. A sample inbound and sample outbound application are provided (UASConnectionTest and OutboundReminder) from which other applications can be derived. C++ UAS application projects can also be created from scratch using the Visual Studio Class Library project template. Note that The Common Language Runtime Support should be set to clr:safe.

Inbound/Outbound Applications

A UAS application is either inbound or outbound. This refers to the direction of the primary call to be managed by the application.

An inbound application inherits from UASInboundApplication and is passed a call channel on which an incoming call has been detected and is waiting to be answered (or rejected).

An outbound application inherits from UASOutboundApplication and is passed a call channel on which no call is yet active. The application is expected to make an outbound call attempt within a reasonably short time.


The UASAppAPI

The UASAppAPI is based on the AMS Client API which provides powerful media processing and call control features via a high-level client API. It allows incoming and outgoing telephone calls to be controlled and connected together, wav file playback, wav file record, DTMF playback and detection, and fax transmission and reception.

The UASAppAPI is supplied as two .NET assemblies that are fully Common Language Specification (CLS) compliant. All types are declared within the AMSClassLibrary or UASAppAPI namespaces.

These assemblies contain classes that control individual telephone calls and associated media processes that interact with the call. For example, to play a wav file to the call or record the call to file.

This is a synchronous API. Methods that interact with Aculab Clouda Rapide server send a message and wait for a response before returning control to the application.

Application Rules

A UAS application is written as a .NET class library assembly that adheres to certain programming rules.

For a UAS application:
  • The name of the assembly is the application name.
  • The assembly must include a class with the same name as the assembly that inherits from either UASInboundApplication or UASOutboundApplication.
  • This class must override the abstract Run method.
  • The Run method must return an integer that indicates the overall success of the application invocation.
  • This class may provide a default constructor.
  • This class may provide implementations for OnLoad and OnUnload methods.
The entry point for application is the Run method. Its prototype is:

Inbound:
public override int Run(UASCallChannel channel, string applicationParameters)


Outbound:
public override int Run(UASCallChannel channel, 
                        string applicationParameters, string outboundParameters)


Where:
  • channel - the primary call channel.
  • applicationParameters - a string containing parameters associated with the application (defined in Inbound Services or Outbound Services registration pages.
  • outboundParameters - a string containing parameters related to a specific invocation of the application. Typically this includes the outgoing call destination address.

Return Code:

The return code should adhere to the following restrictions:
  • 0 or any positive integer: the application passed. The exact meaning is application specific
  • -1 to -99: the application failed, reserved for UAS use
    • -1: the application instance has thrown an exception
    • -2: the incoming call was hung up before the application instance was invoked
    • -3: the specified application is not currently loaded
    • -4: the specified application cannot be run
    • -5: the application instance returned a reserved value
    • -6: the specified application is Inbound, but the call is outbound (or vice versa)
    • -7: the UAS is not currently accepting new application requests
    • -9: UAS error while processing application request
  • any other negative integer: the application failed. The exact meaning is application specific

UASApplication Base Class

All UAS applications inherit from UASApplication. This class provides a property ApplicationInstanceId that uniquely identifies a particular application instance within Aculab Cloud the Rapide server (this id is included in diagnostic trace for each instance).

It contains the FileManager and Trace properties that provide media file and diagnostic tracing.

It also has the FaxDocumentToSend and FaxDocumentToReceive properties that are used when sending or receiving faxes. These last two properties will be null unless Fax Sending or Fax Receiving has been enabled within the service invoking a particular application instance.


Application Initialisation

The .Net UAS supports one-time initialisation of an application so that any static resources that an application may use can be initialised. To achieve this a UAS application may optionally implement one or both of the following methods in its application class:
  • public static int OnLoad();
  • public static void OnUnload();
The OnLoad method is called while an application is being loaded. It should return zero to indicate success or non-zero to cancel the loading operation.
The OnUnload method is called by the UAS when an application is unloaded.

Default Constructor

When a UAS is requested to run a loaded UAS application it instantiates the application's class and then calls the Run method. A default constuctor can optionally be implemented. This will be called immediately before Run is called.

Application Configuration

You can provide application configuration values to be used by your application. These can either be
  • inserted into the UASService.exe's config file
  • or
  • supplied by an application-specific config file
Elements added to the existing UASService.exe.config file should be prefixed with your application name. They can be accessed from the application using:

String configValue = ConfigurationManager.AppSettings.Get("MyAppName.MyConfigValueKey");

An application-specific config file (e.g. MyAppName.dll.config) residing in the same location as UASService.exe can be accessed as follows:

ExeConfigurationFileMap configMap = new ExeConfigurationFileMap();
configMap.ExeConfigFilename = 
  System.IO.Path.Combine(Environment.CurrentDirectory, "MyAppName.dll.config");
Configuration config = 
  ConfigurationManager.OpenMappedExeConfiguration(configMap, 
  ConfigurationUserLevel.None);
String configValue = 
  (String)config.AppSettings.Settings["MyConfigValueKey"].Value;

Diagnostic Trace

The Trace property of the UASApplication class from which applications inherit provides the ability to log diagnostic trace to file. The amount of trace is determined by the TraceLevel configuration setting for the UAS (the default level is Error). This can be altered via the UAS Management Console, from where log files can also be downloaded for viewing.

The log files are written to a location specified by the TraceLocation configuration UAS setting. This defaults to Logs under the installation directory. The TraceFileMaxKilobytes and TraceFileCount configuration settings also affect how large the log files are and how many are written.
(see Using The UAS for information on configuration)

By default, up to 10 log files are written of 1MB maximum size. e.g. UAS_000.log - UAS_009.log

In addition to the trace files, Application Data Records (ADRs) are written to the same location as the diagnostic trace files. These have the form UAS_ADR_0000.log.

Debugging

It is possible to debug your UAS application from within Visual Studio by configuring your application to start the UAS service executable in debug mode.

The steps required are:
  • Ensure the Aculab Cloud Rapide server .Net UAS service is stopped.
    Use the Windows Services Control Manager or run <yourUASInstallDir>\UASService.exe -stop to stop it.
  • Copy your UAS application and it's associated .pdb file to the <yourUASInstallDir>\Applications folder.
  • In your UAS application project within Visual Studio, edit its properties (Debug tab) as follows:

    • Set Start external program to <yourUASInstallDir>\UASService.exe
    • Set the Command line arguments to -d (to run it in debug mode as a console application)
    • Set the working directory to <yourUASInstallDir>

  • Set your UAS application project as Startup Project within Visual Studio.
    Start a debug session for your UAS application.
    You should now be able to set breakpoints within your UAS application that will be hit when an instance of your application is started.
Note: When running the UAS service in debug mode for the first time you may need to re-add your cloud connection via the Management Console.

Extra Channels

An application is always supplied with a single primary channel. It is possible to use additional secondary (extra) channels to make outgoing calls which can, for instance, then be connected to the primary call.

When registering an application via Inbound Services or Outbound Services, the number of extra channels required can be defined. An application can access the resultant call channels via the ExtraChannels property of the UASCallChannel class.

For example
UASCallChannel otherChannel = channel.ExtraChannels[0];
if (otherChannel.Call(otherDestinationAddress) == CallState.Answered)
{
    // Connect the calls together
    channel.Connect(otherChannel);
}

Text To Speech (TTS)

Aculab Cloud Rapide supports Text To Speech (TTS), allowing quick and easy application prototyping and, more generally, the ability for your applications to read text to their users.

SSML
The TTS methods support the embedding of Speech Synthesis Markup Language (SSML). This is a very flexible way of expressing how you would like your text to be spoken. Further details on the SSML features and voices supported are available in the Aculab Cloud Rapide TTS documentation.

In brief, SSML allows you to do things like:

channel.FilePlayer.Say(
    "<prosody rate='x-slow'>
        This is me speaking slowly.
     </prosody>");

channel.FilePlayer.Say(
    "<prosody pitch='+50%'>
        This is me speaking with a very high pitch.
     </prosody>");

Automatic Speech Recognition

Aculab Cloud supports Automatic Speech Recognition (ASR) - see Automatic Speech Recognition, giving flexibility to menu and input implementation.

The SpeechDetector class controls the speech recognition, and uses the Grammar class to specify exactly what is to be recognised, using Aculab Speech Grammar Format (ASGF).

In brief, ASGF allows you to specify input such as:

Grammar listenFor = new Grammar("yes|no [please|thanks];");

to listen for "yes" or "no" followed optionally by "please" or "thanks".

Media Files

Media files to be played to a call or recorded from a call reside on Aculab Cloud's media file store the Rapide server.

An application can perform some simple manipulation of these files via the FileManager property of UASApplication from which the application inherits.

This class enables the application to rename and delete files and check for their existence.

For example:
if (this.FileManager.Exists(recordFileName))
{
    this.FileManager.DeleteFile(recordFileName);
}

And encrypted files can be validated which ensures they can be decrypted and are of a supported format before playing:

// Construct the cipher to decrypt the file
AesCbcCipher cipher = new AesCbcCipher(key, initialisationVector);

// If file is validated answer the call and play the file
ValidationResultType result = this.FileManager.FileValidate("wavs/encrypted/encryptedPlay01.wav", cipher);
if (result == ValidationResultType.VALID)
{
    channel.Answer();
    ...
    channel.FilePlayer.Play("wavs/encrypted/encryptedPlay01.wav", true, cipher);
}

Additionally, Aculab Cloud Rapide provides Web Services access to manage files that can be used to download, list upload, rename or delete files. See the ManageFiles Web Service Client Sample.

Aculab Cloud accesses files via a highly reliable, distributed storage system.
Your Rapide server accesses files via a local storage system with some similarities to that on Aculab Cloud.
The way it works will generally be transparent to you as an application writer, but there are a few important things to bear in mind:
  • When recording a new file within a telephone call, it will be available for use immediately in that, and within 60 seconds in other calls, from cloud.aculab.com and from its Web Services API. When recording a new file within a telephone call, it will be available for use immediately in that and other calls and within 20 seconds from your Rapide web site and from its Web Services API.
  • When writing a new file via cloud.aculab.com or the Web Services API, it will be available for access within 60 seconds from the completion of the write. When writing a new file via your Rapide web site or its Web Services API, it will be available for access within 20 seconds from the completion of the write.
  • When deleting or overwriting an existing file via cloud.aculab.com or the Web Services API, the change will take up to 15 minutes to become available across the distributed storage system. Until this has been accomplished some telephone calls may continue to access the old version. When deleting or overwriting an existing file via your Rapide web site or its Web Services API, the change will take up to 15 minutes to become available to your applications. Until this has been accomplished telephone calls will continue to access the old version.
  • While these levels of delay may be fine when you've just published a new version of your 'Hello caller, what would you like to do?' wav file onto the cloud, it may not be so helpful when you've just recorded a voicemail and the intended recipient dials into the system soon after to receive it. your Rapide server, it may not be so helpful when you've just uploaded an updated emergency announcement and the intended recipient dials into the system soon after to receive it. , So, for cases where you need to access new media files quickly, please make them new files by using new filenames.
  • It's generally a good idea to organise your files into directories. The directory delimiter is "/", as in "intro/hello.wav". Directories are created and destroyed on demand by the storage system so, assuming that the 'intro' directory doesn't exist, channel.FileRecorder.record("intro/hello.wav") would create that directory and file_man.delete_file("intro/hello.wav") would remove it along with the file.

Call Transfer

The ability to transfer a call is provided in the form of retrievable transfer. This re-routes the audio to another destination but maintains control over the call, allowing it to be retrieved.

This kind of transfer requires an Extra Channel in the Idle state to be supplied.

The Transfer method makes a call to a supplied target address and transfers the audio connection from the call making the transfer to that target. However it retains control of the lifetime of the call (this is known as Third Party Call Control in the SIP world).

This means that the audio connection can be retrieved at a later time. Additionally, the audio connection returns to the original call automatically, if the target of the transfer hangs up.

Retrievable transfer is often used in scenarios where an operator creates a call that connects two participants together and in conferencing.

Conferencing

Aculab cloud Rapide supports conferencing together two or more calls (parties) (see Conferencing Capabilities) within an entity called a conference room for which you provide an identifying name. Within each conference room a conference entity controls the audio mix that each party hears. A number of options can be set for each party as they enter the conference room (these are shown here in green).


Conference Room

A conference room is Created when the first party enters , either by transferring an existing call or by placing a new call to that room. See: UASCallChannel.

For example:
CallState state = extraChannel.CallConferenceRoom("BobsConferenceRoom");
if (state == CallState.Answered)
{
    channel.Connect(extraChannel);
    ...
or
channel.TransferToConference(
    "BobsConferenceRoom",
    channel.ExtraChannels[0]);

A conference room is Destroyed if a party exits that is configured to Destroy On Exit or if the last remaining party exits. When a conference room is destroyed any remaining parties are automatically hung up. A party exits a conference room UASCallChannel.

For example:
channel.HangUp();
or
CallState state = channel.RetrieveTransferredCall();
if (state == CallState.Answered)
{
    // Call has been retrieved from conference
    ...
}
  • Although the name chosen for a conference room is unique to your user account, you are strongly advised to provide a different name for each one. (This avoids various potential issues, for example, where a caller stays in one conference longer than expected and is suddenly introduced into a supposedly new conference as new callers ring in)
  • When transferringconnecting to a room, a Ringback Tone can be configured to play while the call is ringing
  • When a call is hung up it exits the conference room
  • A party can be configured to exit the room when they hit a specified keypad digit (Exit On DTMF Digit)
  • A party may be a human or an application, such as one that records a conference room

Conference

Each conference room contains a single conference that has two states: Started and Stopped.
It is initially in the Stopped state.

While Started all parties in the conference room can either listen or talk and listen.

While Stopped the parties cannot hear each other. Each party may have an On Conference Stopped File configured to play in this state. An example would be holding music.

A conference Starts:
When a conference room contains at least two parties, at least one talker and at least one party configured to Start On Entry.

A conference Stops:
When the number of parties goes below two or if there are no talkers left in the room. Any parties remaining in the Stopped conference will hear their On Conference Stopped File if configured


Conference Party Options

When entering a conference room, a number of options determine how the party affects the conference and how the conference interacts with the party:

Party Type The type of participation this party can take in the conference - TalkerAndListener or Listener
Lifetime Control The control that this party has on the lifetime of the conference and conference room. One of:
None The party does not directly affect the lifetime of the conference
Start On Entry The conference may start once this party has entered the conference room
Destroy On Exit The conference stops and the conference room is destroyed when this party exits
Full Control The conference may start once this party has entered the conference room and the conference room will be destroyed when this party exits
Ringback Tone The ring tone, if any, that's heard by this party while the call to the room is ringing
On Conference Stopped File An optional name of a media file that is played to the party (looping) while the conference is stopped
Beep On Entry Whether a beep is played to the room when this party enters a started conference
Exit On DTMF Digit A DTMF digit that, if provided, allows this party to exit the conference room by pressing the specified digit (e.g. '*')
Mute On DTMF Digits An optional pair of DTMF digits that allow the party's audio contribution to the conference to be muted and unmuted when one of the specified digits is hit
On Entry Media An optional name of a media file or some TTS that is played to a started conference immediately before the party enters it (and after the Beep On Entry, if configured)
On Exit Media An optional name of a media file or some TTS that is played to a started conference immediately after the party exits it
Prefix Media An optional name of a media file or some TTS that is played to a started conference immediately before the On Entry Media or On Exit Media

For example:
// Create media settings to specify all options
ConferencePartyMediaSettings settings = 
    new ConferencePartyMediaSettings(
        channel.PredefinedToneSequences[PredefinedTones.RingbackUnitedKingdom],
        "onHoldMusic.wav",                        // file to play when stopped
        true,                                     // beep enabled
        '#',                                      // exit on a #
        new ConferencePartyMuteDigits('1', '2'),  // Mute on 1, unmute on 2
        PlayableMedia.TextToSay("Bob "),          // Prefix
        PlayableMedia.TextToSay("has entered the conference"),
        PlayableMedia.TextToSay("has exited the conference"));

See: ConferencePartyType, ConferenceLifetimeControl, ConferencePartyMediaSettings, ConferencePartyMuteDigits.

Fax

Sending and receiving faxes on a call is supported on Aculab cloud Rapide and needs to be enabled via the Enable fax send and/or Enable fax receive options in the Fax tab of the invoking Inbound or Outbound service. Typically faxes are only sent on outgoing calls and received on incoming calls. Note that enabling either of these will result in the application being charged at a different platform rate.

Sending a Fax

Enabling fax send in a service creates an empty FaxDocumentToSend fax document object as a property of the invoked UASApplication. The content of this object can be built up from TIFF files that have been stored on the cloud Rapide server and text can be added to selected pages. It can be sent using the FaxSender property of a call channel.

For example:
 this.FaxDocumentToSend.SetContent("myFaxTxDocument.tif");
 channel.FaxSender.Send(this.FaxDocumentToSend);


Also see the OutboundSendFax application sample.

Receiving a Fax

Enabling fax receive in a service creates a FaxDocumentToReceive fax document object as a property of the invoked UASApplication. The filename of this object must be set, then a fax can be received using the FaxReceiver property of a call channel.

For example:
 this.FaxDocumentToReceive.SetFileName("receivedFax1.tif");
 channel.FaxReceiver.Receive(this.FaxDocumentToReceive);


Also see the InboundReceiveFax application sample.

Advanced Fax Options

Typically faxes can be sent and received without recourse to changing the Fax Options. However, where circumstances require it, the default fax options for a service can be altered from the Fax tab of Inbound Services and Outbound Services.
These defaults may be overriden per application instance via the FaxOptions class that can be passed to the FaxSender or FaxReceiver. Option names and value ranges must match those available on the Service Fax tab.

See Sending & Receiving Faxes for more information.

High Level API

Additional high level functionality on top of the standard UASCallChannel is provided by the CallChannelHelper class.

The CallChannelHelper class has methods to:
  • Make an outbound call and connecting it to an existing call (CallAndConnect)
  • Play a prompt, obtain a speech response or (optionally) digit keypresses (CaptureInput)
  • Play a prompt, obtain a specified number of digit keypresses e.g. pin code, card number etc. (CaptureNumber)
  • Play a prompt, obtain a variable number of digit keypresses up to an end digit e.g. '#' (CaptureNumber)
  • Play a prompt, obtain any specified DTMF keypresses (CaptureValidatedDigits)
  • Present a menu of options to be selected by digit keypress (InvokeMenu)
For example:

 // Make an outbound call and connecting it to an existing call
 CallChannelHelper channelHelper = new CallChannelHelper(this, channel);

 // Establish the target address
 CallTarget callTarget = CallTarget.SipAddressToCall(
   "sip:bob@acompany.com", "sip:bill@anothercompany.com");
channelHelper.CallAndConnect(callTarget, channel.ExtraChannels[0], 120);

// Wait for 10 mins for either call to be hung up
WaitForAnyCallIdle(
  new UASCallChannel[] { channel, channel.ExtraChannels[0] }, 600);


 // Play a prompt, obtain a speech response
 CallChannelHelper channelHelper = new CallChannelHelper(this, channel);
			
 Grammar ukCountries = Grammar.CreateFromAlternatives(
   new String[] { "England", "Wales", "Scotland", "Northen Ireland" });
   
 PlayableMedia prompt = 
   PlayableMedia.TextToSay(
     "Please say the name of a constituent country of the United Kingdom.");
   
  // Capture speech
  CapturedInput capturedInput = channelHelper.CaptureInput(prompt, ukCountries);
  if (capturedInput != null)
  {
    channel.FilePlayer.Say("You said: {0}", capturedInput.Input);
  }
}


 // Play a prompt, obtain a specified number of digit keypresses
 CallChannelHelper channelHelper = new CallChannelHelper(this, channel);
 PlayableMedia prompt = 
     PlayableMedia.TextToSay("Enter a 12 digit number");
 string entry = channelHelper.CaptureNumber(prompt, 12);


 // Present a menu of options to be selected by digit keypress
 CallChannelHelper channelHelper = new CallChannelHelper(this, channel);
 PlayableMedia prompt = 
     PlayableMedia.TextToSay("Press 1 to do something, 
                              press 2 to do something else.");
 char selection = channelHelper.InvokeMenu(prompt, "12");
 if (selection == '\0')
 {
   channel.FilePlayer.Say("You didn't select anything...");
 }
 else
 {
   channel.FilePlayer.Say("You selected: {0}", selection);
   ...
 }

SIP-specific Features

SIP-specific features such as access to SIP headers, SIP INFO and SIP MESSAGE requests are implemented in the SIP, SIPHeaderField, SIPInfo and SIPMessage classes.

Please see Up-coming Functionality for information on Aculab Cloud support for these features.

Referencing External Libraries

3rd-party or other external libraries may be referenced by your applications.
Currently this requires some manual intervention to copy any referenced library files to the [UASInstallFolder]. The applications themselves can be installed in the normal way via the Management Console.

Please note that the UAS service will need to be stopped before any libraries that have been copied in this way can be upgraded.

External library files copied to the [UASInstallFolder] will not be removed by uninstalling the UAS package and will be retained when a new UAS package is installed to the same location.