Voicemail Lesson 1

In this tutorial, we are going to create a simple voicemail service. To do this, we will build up the application step by step, successively adding more complex code as we go. This step provides a basic foundation for the rest of the tutorial.

We're going to write an application that can answer the phone, say some text, ask the user to leave a message and then hang up the phone. The data is saved onto the Cloud, so that you can download the recorded wave file, and play it on your local machine. You can find any media files saved by the Cloud in the Media Files menu on cloud.aculab.com.


    • Sample Files:
      • Samples\voicemail_1.py

      Application Execution

      The application is called when an inbound call is detected by the UAS. For Python, the main function is called first. The UAS expects a return error code, which we must set before program termination. Any values at zero or above are classified as success codes, while anything negative is classed as a failure. The UAS system reserves error codes -1 to -99, but we are free to use anything less than these values for our own reference.

      Exceptions

      At any time, a call to the Cloud may result in an exception being fired. For example, if we play some Text-To-Speech (TTS) to a call, and then the caller hangs up, we will get a HangUp exception. Therefore, whenever we write an application for the UAS, we need to write code in a try/catch block.

      Parameters

      Our main function receives a number of parameters: channel, which offers us a channel to the Cloud and application_instance_id, which tells us our unique application identification code. file_man is a file management class, which allows us to alter files already situated on the Cloud, including moving and deleting. my_log gives access to the logging facilities of the UAS, and application_parameters are application parameters which are configured on the Inbound Services page.

    • """
          An inbound application that answers the call; prompts the caller to
          record a message (and to press any key to stop recording); plays a beep
          to indicate that it is recording and then records for a maximum of 60
          seconds.
      
          The recorded filename is
          samples/voicemail1/recordedMessageFrom<callFrom>.wav The application
          will check that the recorded message file exists before exiting.
      
          Actions:
              - check the channel state
              - ring and answer
              - use TTS to play a prompt
              - record a file
              - check that the file exists
              - hang up
      
          This application is part of the online Voice Mail tutorial.
      """
      
      from prosody.uas import Hangup, Error
      
      __uas_version__  = "0.0.1"
      __uas_identify__ = "application"
      
      def main (channel, application_instance_id, file_man, my_log, application_parameters):
          return_code = 0
          try:
              # check the incoming channel state and answer the call
              state = channel.state()
              if state == channel.State.CALL_INCOMING:
                  state = channel.ring()       # this can raise a Hangup exception
                  if state == channel.State.RING_INCOMING:
                      state = channel.answer() # this can raise a Hangup exception
              else:
                  raise Hangup('No inbound call, state is {0}'.format(state))
              if state != channel.State.ANSWERED:
                  raise Hangup('Failed to answer inbound call, state is {0}'.format(state))
      
              my_log.info("Answered an inbound call")
      
              # use text-to-speech to pass instructions to the caller
              channel.FilePlayer.say("Please record a message after the tone. Press any digit to stop the recording.")
      
              # use DTMF to play the tone
              channel.DTMFPlayer.play ('#')
      
              # use the call_from information from the call details to identify this recording
              msg_file_name = "samples/voicemail1/recordedMessageFrom{0}.wav".format(channel.Details.call_from)
              # the recording will stop after three seconds of silence, or after one minute
              # (we don't want people speaking for ages), or if the caller presses a digit (barge_in).
              channel.FileRecorder.record(msg_file_name, milliseconds_max_silence=3000,
                                                  seconds_timeout=60, barge_in=True)
      
              # check whether the file has been saved correctly, does it exist?
              if file_man.exists(msg_file_name):
                  channel.FilePlayer.say("Your message has been saved. Good bye.")
              else:
                  channel.FilePlayer.say("Your message could not be saved. Good bye.")
      
          # catch any Hangup exceptions, this is still regarded as a success but the 100 code
          # identifies that the caller hung up before the application completed
          except Hangup as exc:
              my_log.info ("Hangup exception: {0}.".format(exc))
              return_code = 100
      
          # catch any Error exceptions, these are often caused by application errors which can be fixed
          except Error as exc:
              my_log.error("Error exception caught: {0}".format(exc))
              return_code = -101
      
          # catch any other exceptions, these are almost certainly caused by application errors which should be fixed
          except Exception as exc:
              my_log.exception("Unexpected exception caught: {0}".format(exc))
              return_code = -102
      
          # now, if the caller has not already hung up, we do
          finally:
              if channel.state() != channel.State.IDLE:
                  channel.hang_up()
      
          my_log.info("Example completed")
          return return_code
                                      
    • Sample Files:
      • Samples\C#\Voicemail1\Voicemail1.cs

      Application Execution

      The application is run when specific inbound calls are directed to the UAS. In response, the Run() method is called . The UAS expects a return code that indicates success or failure of the application (as defined by the application author). Zero or above are classified as success codes, while negative values are classed as a failure. The UAS system reserves failure codes -1 to -99. Anything from -100 onwards are available to be defined by the user.

      Exceptions

      Unhandled exceptions that occur within an application will be caught by the UAS and will not interfere with the UAS operation or other applications running in the UAS. However, it is good practice to encapsulate application code within try/catch blocks. This allows for error logging and conversion of exceptions into return error codes that can help with application fault diagnosis.

      Call Hang Up

      At any time, the call may be hung up by the caller. This is indicated by the State of the call going to Idle. Many channel methods return either this state or a cause value that indicates that a feature (e.g. recording) was stopped as the call was hung up.

      Parameters

      The Run() method for an inbound application takes two arguments: channel which gives us information and control of a single telephone call on the Cloud and applicationParameters which is a user-defined string that originates from the Inbound Service entry on the Cloud.

      Using the channel class, we can instruct the UAS to answer the incoming call, remembering that our inbound application is only ever executed when an inbound call has been made. If the call is answered successfully we use the Say method on the FilePlayer object (a property of the channel) to perform TTS and prompt the caller to leave a message. This instructs the Cloud to convert our text into speech data, which is then played to the caller.

      Recording audio from the caller will be saved to a file on the Cloud, which is accessable via the Manage - Media Files menu option, or via a WebServices API. We must therefore create a filename for the recorded voice message located on the Cloud. The root folder will be unique for our cloud account and the path relative to this can contain subfolders using the '/' character. In this case, we have including in the filename the CallDetails.CallFrom property of channel, that typically identifies the caller.

      We start recording voice data using the FileRecorder.Start() method, passing it our recording filename. This method does not block and allows the application to continue executing lines after the recording has started. We signal to the caller that recording has commenced by playing a single DTMF tone using the DtmfPlayer.Play() method. We could alternatively have used TTS to signal the start of the recording.

      The FileRecorder.WaitUntilFinished() method instructs the application to wait until the recording has finished. Here we have specified a maximum of 60 seconds to wait. The recording can finish for several different reasons, including call hang up, timeout or max file size reached. In this case we aren't interested in the reason, but simply check whether a file exists on the cloud of the specified name. To do this we used the FileManager object that is available as a property on the base class of UASInboundApplication.

    • using System;
      using System.Threading;
      using AMSClassLibrary;
      using UASAppAPI;
      
      // An inbound application that answers the call, prompts the caller to
      // record a message (and to press any key to stop recording), plays a
      // beep to indicate it is recording and then waits for a max of 60 seconds.
      // The recorded filename includes the callee address (callFrom)
      // i.e. samples/voicemail1/recordedMessageFrom<>.wav.
      // It checks that a recorded message file exists.
      namespace Voicemail1
      {
          public class Voicemail1 : UASInboundApplication
          {
              // Possible return codes
              enum ReturnCode
              {
                  // Success Codes:
                  Success = 0,
                  // ... any positive integer
                  RecordedAMessage = 1,
      
                  // Fail Codes:
                  // -1 to -99 reserved
                  ExceptionThrown = -100,
                  RecordingFailed = -101
              }
      
              // This is the entry point for the appliction
              public override int Run(UASCallChannel channel,
                                      string applicationParameters)
              {
                  this.Trace.TraceInfo("Started");
                  ReturnCode reply = ReturnCode.Success;
      
                  try
                  {
                      // Answer the call
                      CallState state = channel.Answer();
                      if (state == CallState.Answered)
                      {
                          this.Trace.TraceInfo("Call answered");
      
                          string recordFileName = "samples/voicemail1/recordedMessageFrom" + channel.CallDetails.CallFrom + ".wav";
      
                          // Prompt to leave a message
                          channel.FilePlayer.Say("Please record a message after the tone. " +
                                                 "Press any digit to stop the recording.");
      
                          // Delay before starting the recording and playing the tone (for effect)
                          Thread.Sleep(1000);
      
                          // Start recording
                          this.Trace.TraceInfo("Recording message to file: [{0}]", recordFileName);
      
                          if (channel.FileRecorder.Start(recordFileName, true))
                          {
                              // Play a tone to signal that recording has started
                              channel.DtmfPlayer.Play("0");
      
                              // Wait for a period for recording to complete or
                              // the call to have been hung up
                              channel.FileRecorder.WaitUntilFinished(60);
      
                              // Check there's a file there
                              if (this.FileManager.FileExists(recordFileName))
                              {
                                  this.Trace.TraceInfo("Recorded message to file: [{0}]",
                                                        recordFileName);
                                  // Return a specific pass code
                                  reply = ReturnCode.RecordedAMessage;
                              }
                          }
                          else
                          {
                              // This invocation failed
                              this.Trace.TraceError("Recording failed to start cause = {0}",
                                                    channel.FileRecorder.Cause);
                              reply = ReturnCode.RecordingFailed;
                          }
                      }
                  }
                  catch (Exception e)
                  {
                      this.Trace.TraceError("Exception thrown {0}", e.Message);
                      reply = ReturnCode.ExceptionThrown;
                  }
                  finally
                  {
                      // Ensure call is hung up
                      channel.HangUp();
                  }
      
                  this.Trace.TraceInfo("Completed");
                  return (int)reply;
              }
          }
      }
                                  
    • Sample Files:
      • Samples\VB\Voicemail1\Voicemail1.vb

      Application Execution

      The application is run when specific inbound calls are directed to the UAS. In response, the Run() method is called . The UAS expects a return code that indicates success or failure of the application (as defined by the application author). Zero or above are classified as success codes, while negative values are classed as a failure. The UAS system reserves failure codes -1 to -99. Anything from -100 onwards are available to be defined by the user.

      Exceptions

      Unhandled exceptions that occur within an application will be caught by the UAS and will not interfere with the UAS operation or other applications running in the UAS. However, it is good practice to encapsulate application code within try/catch blocks. This allows for error logging and conversion of exceptions into return error codes that can help with application fault diagnosis.

      Call Hang Up

      At any time, the call may be hung up by the caller. This is indicated by the State of the call going to Idle. Many channel methods return either this state or a cause value that indicates that a feature (e.g. recording) was stopped as the call was hung up.

      Parameters

      The Run() method for an inbound application takes two arguments: channel which gives us information and control of a single telephone call on the Cloud and applicationParameters which is a user-defined string that originates from the Inbound Service entry on the Cloud.

      Using the channel class, we can instruct the UAS to answer the incoming call, remembering that our inbound application is only ever executed when an inbound call has been made. If the call is answered successfully we use the Say method on the FilePlayer object (a property of the channel) to perform TTS and prompt the caller to leave a message. This instructs the Cloud to convert our text into speech data, which is then played to the caller.

      Recording audio from the caller will be saved to a file on the Cloud, which is accessable via the Manage - Media Files menu option, or via a WebServices API. We must therefore create a filename for the recorded voice message located on the Cloud. The root folder will be unique for our cloud account and the path relative to this can contain subfolders using the '/' character. In this case, we have including in the filename the CallDetails.CallFrom property of channel, that typically identifies the caller.

      We start recording voice data using the FileRecorder.Start() method, passing it our recording filename. This method does not block and allows the application to continue executing lines after the recording has started. We signal to the caller that recording has commenced by playing a single DTMF tone using the DtmfPlayer.Play() method. We could alternatively have used TTS to signal the start of the recording.

      The FileRecorder.WaitUntilFinished() method instructs the application to wait until the recording has finished. Here we have specified a maximum of 60 seconds to wait. The recording can finish for several different reasons, including call hang up, timeout or max file size reached. In this case we aren't interested in the reason, but simply check whether a file exists on the cloud of the specified name. To do this we used the FileManager object that is available as a property on the base class of UASInboundApplication.

    • Imports AMSClassLibrary
      Imports UASAppAPI
      
      ' An inbound application that answers the call, prompts the caller to
      ' record a message (and to press any key to stop recording), plays a
      ' beep to indicate it is recording and then waits for a max of 60 seconds.
      ' The recorded filename includes the callee address (callFrom)
      ' i.e. samples/voicemail1/recordedMessageFrom<callFrom>.wav.
      ' It checks that a recorded message file exists.
      Namespace Voicemail1
      
          ' The application class.
          ' This must have the same name as the assembly and must inherit from either
          ' UASInboundApplication or UASOutboundApplication.
          ' It must override the Run method.
          Public Class Voicemail1
              Inherits UASInboundApplication
      
              ' Possible return codes
              Enum ReturnCode
                  ' Success Codes:
                  Success = 0
                  RecordedAMessage = 1
      
                  ' Fail Codes:
                  ' -1 to -99 reserved
                  ExceptionThrown = -100
                  RecordingFailed = -101
              End Enum
      
              ' This is the entry point for the application
              Overrides Function Run(ByVal channel As UASCallChannel, _
                                     ByVal applicationParameters As String) _
                                     As Integer
      
                  Me.Trace.TraceInfo("Started")
                  Dim reply As ReturnCode = ReturnCode.Success
      
                  Try
                      ' Answer the call
                      Dim state As CallState
                      state = channel.Answer()
                      If state = CallState.Answered Then
                          Me.Trace.TraceInfo("Call answered")
      
                          Dim recordFileName = "samples/voicemail1/recordedMessageFrom" + channel.CallDetails.CallFrom + ".wav"
      
                          ' Prompt to leave a message
                          channel.FilePlayer.Say("Please record a message after the tone. " + _
                                                 "Press any digit to stop the recording.")
      
                          ' Delay before starting the recording and playing the tone (for effect)
                          Thread.Sleep(1000)
      
                          ' Start recording
                          Me.Trace.TraceInfo("Recording message to file: [{0}]", recordFileName)
      
                          If channel.FileRecorder.Start(recordFileName, True) Then
                              ' Play a tone to signal that recording has started
                              channel.DtmfPlayer.Play("0")
      
                              ' Wait for a period for recording to complete or
                              ' the call to have been hung up
                              channel.FileRecorder.WaitUntilFinished(60)
      
                              ' Check there's a file there
                              If Me.FileManager.FileExists(recordFileName) Then
                                  Me.Trace.TraceInfo("Recorded message to file: [{0}]", recordFileName)
                                  ' Return a specific pass code
                                  reply = ReturnCode.RecordedAMessage
                              End If
                          Else
                              ' This invocation failed
                              Me.Trace.TraceError("Recording failed to start cause = {0}", _
                                                    channel.FileRecorder.Cause)
                              reply = ReturnCode.RecordingFailed
                          End If
                      End If
      
                  Catch ex As Exception
                      Me.Trace.TraceError("Exception thrown {0}", ex.Message)
                      reply = ReturnCode.ExceptionThrown
                  Finally
                      ' Ensure call is hung up
                      channel.HangUp()
                  End Try
      
                  Me.Trace.TraceInfo("Completed")
                  Return reply
      
              End Function
      
          End Class
      
      End Namespace
                                  

Next time...

Well done! So, by now you're probably wondering what we're going to do with this recorded file. In our next step, we're going to allow the user to listen to the recorded message and then ask if they want to keep the message or not. We're one step closer to having a voicemail system running!

Lesson 2