Working with applications

Introduction

The UAS runs applications that are written by you in Python. The applications control the call logic for one primary call which can be either inbound or outbound. The applications can also have the ability to use up to three extra channels which can be used to place outbound calls; they cannot be used to wait for inbound calls.

The application call logic is written using the API provided.

Applications are saved in a file with the extension .py; the files must be uploaded to the UAS using the controls provided in the Management Console. Once uploaded the applications will reside in a directory called Applications which is directly below the UAS’s operating directory.

An inbound call to a UAS will target a particular inbound application, the UAS will activate a copy of the application. Multiple copies of a given application can run simultaneously. The UAS can be triggered to run an outbound application via the web services API. Sample applications are provided that illustrate how this API is used. Multiple copies of an outbound application can run simultaneously.

For an application to be targeted by an inbound call, or by the web services API it must first be registered against a service on the Aculab Cloud Web Portal.

The lifetime of an application is not the same as the lifetime of the call. An application can continue to run after the primary call has hung up. In fact, an outbound application does not need to have a call associated with it at all. The UAS will never terminate an application, the application will be allowed to end naturally.

Applications are not restricted to the API provided, the application is free to do whatever other tasks you wish it to do, you may, for instance, want to access an external database.

Applications and the Cloud Web Portal

For an application on a UAS to be invoked by in incoming call, or by the web services API, it must first be linked to an inbound or outbound service on the Cloud Web Portal. To register inbound or outbound services on the Cloud Web Portal please log into your account and visit the Inbound Services or Outbound Services pages under the Manage tab.

To place an outbound PSTN call you also must have a validated Caller ID. You can register a Caller ID on the Caller ID page under the Manage tab. Please see the documentation for placing outbound calls.

If this all seems a bit daunting, please log in to your Aculab Cloud account and follow the Quickstart guide which will help you to get your first application running quickly.

Importing applications

When the UAS starts up, it will import all the .py files that are present in the Applications directory. It needs to import the applications in order to run them when required. As the UAS imports them, it will perform some simple checks on the application files, for example, it will check that the function main is defined and that it has the required number of arguments; it will also check that the global variables __uas_version__ and __uas_identify__ have been defined. Applications that do not pass the checks will not be available to be run by the UAS and will not be listed by the management console. Application templates and further information is given below ... keep reading.

Please also note that application file names must not begin with a digit or contain a - (minus). This is a python restriction.

Importing other files

Sometimes it will be desirable to have a .py source file in the Applications directory which is not an application. For instance, two or more applications may want to import a common file that defines shared methods. Naturally, such a common file might not define a main function. The UAS distinguishes between application files and other .py files by looking at the variable __uas_identify__. For applications this variable will be "application", for other files it will be "common". If a file has identified itself as being common, the UAS knows not to check it for things that are required for application files. If the __uas_identify__ variable is missing, the file will fail the checks and not be imported.

Modifying applications

It is possible to modify an application while the UAS is running, and have it pick up the new file without having to be restarted. This is accomplished via the UAS management console. After modifying an application file, it can be uploaded to the UAS and re-imported via the UAS management console.

When an application file is being re-imported, there is a brief period (a matter of milliseconds) during which the application is not available to the UAS. During this brief period any telephone calls that require the application will fail.

Modifying other files

As with applications, other python source files (files that identify themselves as common) can be modified and then uploaded to a running UAS via the UAS management console. Uploading a common file will cause the UAS to re-import the applications, so that any that require the new or modified behaviour defined in the common file will pick that up.

Please be aware that during the brief time that the common file and the applications are being updated, some errors may occur in calls that require these applications at the same time.

Application return codes

All applications should return an integer to indicate success or failure. The return value will be logged in the application data record (ADR), please see the ADR section for more information on return codes.

The most recent ADRs for failed application runs are also shown on the Management Console’s ADRs page.

The Error exception

There are several situations in which the python API will raise an Error exception. For instance, if a call channel’s play function is called whilst the call channel is connected to another call, or if connect is called on a call channel that is already connected. If this happens, it is an indication of an error in the application which needs to be corrected. The application writer might also want to raise an Error exception if something bad happens.

note:Applications should catch all exceptions and handle them appropriately, see the examples for more on this.

Example:

from prosody.uas import Error

# raising and logging an error string
try:
    raise Error('something bad has happened')
except Error as exc:
    my_log.error(str(exc))

The Hangup exception

This exception does not indicate an error in the application, it simply means that the call that is being worked with has gone to IDLE. Because a call can be hung-up at any time, most functions can raise this exception; the application must be ready to catch it and deal with the hangup appropriately. The application writer might also want to raise a Hangup exception if, for instance, the call is in an unwanted state and the application wants to force a hang up.

note: Due to the way in which the PSTN works, if the called party of an outbound PSTN call hangs up, the Aculab Cloud will not necessarily be informed of this and, therefore, might not be able to inform your application. So, we strongly recommend that you write your applications to interact with the called party and, in the event of repeated no response, hang up the call.

note: Applications should catch all exceptions and handle them appropriately, see the examples for more on this.

Example:

from prosody.uas import Hangup

# raising and logging a hangup string
try:
    raise Hangup('call has hung up')
except Hangup as exc:
    my_log.info(str(exc))

The ToneManager class

The tone manager is required for call transfer, including transfer to a conference. The application will create a tone manager and pass it to the channel to create a tone player.

Example:

from prosody.uas import ToneManager

# create a tone manager
tone_manager = ToneManager()
# create a tone player on the channel
channel.create_tone_player(tone_manager)

The main function

Each application must have a main function. The UAS will attempt to execute main with a number of arguments. The arguments to the application are provided by the Aculab Cloud.

For outbound applications main takes six arguments:

def main(channel, application_instance_id, file_man, my_log, application_parameters, outbound_parameters):

For inbound applications main takes five arguments:

def main(channel, application_instance_id, file_man, my_log, application_parameters):
The arguments
  • channel

    is the call channel that is handling the call.

  • application_instance_id

    is a unique id for this instance of the application.

  • file_man

    is a reference to a file manager that provides some basic file based operations.

  • my_log

    is a reference to a logger.

  • application_parameters

    is a list of application arguments.

  • outbound_parameters

    is a list of application arguments relevant to outbound calls only. Typically, this will be the destination address of the outbound call.

application_parameters

The application_parameters argument is passed into inbound and outbound applications. It is a string which the user sets in the Cloud Web Portal Inbound/Outbound Service Registration pages (the Application Parameters field) and is passed unchanged to the application.

outbound_parameters

The outbound_parameters argument is passed into outbound applications only. It is a string which the user sets when using the Web Services API to invoke an outbound service. Typically this string will be used to provide the destination address of the outbound call. See the web services API and the web services samples<ws_example_code> for information on how to invoke an outbound application.

The call channel class

A reference to the channel class is passed into the main function as the first parameter, channel in the example above. This class provides all the call control functions that the application will require.

In addition to the call control functions, the channel can have a reference to extra channels that can be used to place outbound calls. Access to the extra channels is through a public property called ExtraChannel, which is a list. To get a reference to an extra channel do the following:

out_channel = channel.ExtraChannel[0]

The number of extra channels you want to allocate to your application is set on the Cloud Web Portal Inbound/Outbound Service Registration pages.

Please see the tutorial for more information on using the extra channels.

For information on using the channel class API please go here.

The file manager class

A reference to the file manager class is passed into the main function as the third parameter, file_man in the example above. This class provides some basic file management functions.

The logger class

A reference to the logger is passed into the main function as the fourth parameter, my_log in the example above. This class provides logging functionality.

Use the my_log parameter to write your trace to the applications log file. For example my_log.debug('Hello world.') will write your text to the log file at debug level. An example for each log level is given below:

my_log.debug('Hello world.')
my_log.trace('Hello world.')
my_log.info('Hello world.')
my_log.report('Hello world.')
my_log.warning('Hello world.')
my_log.error('Hello world.')
my_log.exception('Hello world.')

Exception level logging will always be logged and, in addition to your text, will log some stack trace as well.

The unique application ID

The unique application ID is constructed from two other IDs, an Aculab Cloud ID (which represents the socket connection) and an application ID. The two IDs are joined with a dot to form a unique ID for the application instance.

Text to Speech

The Aculab Cloud 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. Several TTS voices are supported, please see the TTS documentation on the Aculab cloud web portal. The default voice to use is US English Female.

The TTS engine suppports the Speech Synthesis Markup Language (SSML). This is a very flexible way of expressing how you’d like it to speak and is documented fully at http://www.w3.org/TR/speech-synthesis . 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>")

SSML can also be used to choose between voices:

channel.FilePlayer.say("<voice name='Marta-8kHz'>Hola, esta es Marta.</voice>")

Marta is the US Spanish voice. To use unicode characters in your TTS application you must ensure that the application or common file is saved as a utf-8 encoded file and that the first line of the file is:

# -*- coding: utf-8 -*-

Any text that contains unicode characters must be identified as unicode for example:

u"<voice name='Marta-8kHz'>¿Cae la lluvia en España principalmente en la llanura?.</voice>"

Application templates

The applications need to adhere to a few rules:

  1. Each application must define a global variable __uas_version__ to hold a version string.
  2. Each application must define a global variable __uas_identify__ to hold the string "application".
  3. Each application must define the function main with the appropriate arguments.
  4. To use the Hangup or Error exceptions, an application must import them from prosody.uas.
  5. To use the tone manager (required for call transfer), an application must import ToneManager from prosody.uas.
  6. Applications should return a positive integer (>= 0) to indicate success.
  7. Applications should return a negative integer (<= -100) to indicate failure (-1 to -99 are reserved for the UAS).
  8. Applications should catch all possible exceptions and handle them appropriately.

Inbound application template:

import traceback
from prosody.uas import Hangup
from prosody.uas import Error
from prosody.uas import ToneManager

__uas_version__  = "0.0.1"
__uas_identify__ = "application"

def main(channel, application_instance_id, file_man, my_log, application_parameters):
    return_code = 0
    try:
        # call handling code here
        pass

    except Hangup as exc:
        # a call has hung up, this is not an error
        my_log.info("Hangup exception reports: {0}".format(exc))

    except Error as exc:
        # an error has occurred, return a negative value
        my_log.error("Error exception reports: {0}".format(exc))
        return_code = -102

    except Exception as exc:
        # an unexpected exception, return a negative value.
        # for these exceptions it can be useful to log some traceback.
        # and this is echieved by logging at exception level.
        my_log.exception("Got unexpected exception {0}".format(exc))
        return_code = -101

    finally:
        # the finally clause will always happen, return a value here
        if channel.state() != channel.State.IDLE:
            channel.hang_up()
        return return_code

An outbound application will be the same, except that main will take one additional paramter:

def main(channel, application_instance_id, file_man, my_log, application_parameters, outbound_parameters):

Common file template

Common files need to adhere to a few rules:

  1. Each common file must define a global variable __uas_identify__ to hold the string "common".
  2. To use the Hangup or Error exceptions, a common file must import them from prosody.uas.

Common file template:

from prosody.uas import Hangup
from prosody.uas import Error

__uas_version__  = "0.0.1"
__uas_identify__ = "common"

Unlike application files, common files do not have to define a version string, but it is not prohibited.

Global variables

Please note that a global variable in an application instance will be global across all instances of that application. It is highly recommended that you do NOT use global variables as this will probably result in unwanted interactions between your application instances. However, if you do want to use a global variable (possibly to interact with other instances of the same application) please be extremely careful.

Working with media files

The Aculab Cloud accesses media files via a highly reliable distributed storage system. 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. For more information on this please read up on Eventual Consistency.

Recording, replacing and deleting

When overwriting an existing file the new version 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 an existing file the change will take up to 15 minutes to become apparent across the distributed storage system. Until this has been accomplished some telephone calls may continue to access the deleted file.

When writing a new file via the Cloud Web Portal or Web Services API, it will be available for access within 60 seconds from the completion of the write.

When recording a new file within a telephone call, it will be available for use immediately in that call, and within 60 seconds in other calls, the Cloud Web Portal and the Web Services API.

In some cases a delay of up to 15 minutes may be acceptable, such as when updating your Hello caller, what would you like to do? file via the Cloud Web Portal or Web Services API. But such a delay will not be acceptable in applications such as voicemail. New voicemail messages should be available quickly and this is achieved by using a new filename for each message - easily done, for example, by embedding the date and time within it.

Directories

It’s generally a good idea to organise your media 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.