Programmable Voice

  1. Home
  2. Docs
  3. Programmable Voice
  4. Tutorials
  5. Faxing

Faxing

Download

This tutorial is a walkthrough explanation of the Faxing sample solution that can be downloaded from your demo dashboard.  If you haven’t already, sign up for a demo account and get 100 minutes of free call time for 30 days with the Voice Elements servers.

This tutorial covers how to write faxing applications using Voice Elements. It is important to note that this project is set up to run either as a windows service or as a Windows form.  While you are debugging, you will want to simply start the application using the default Windows form.  Once you are ready to deploy for production, we recommend that you install as a Windows Service. See the documentation for .net windows services here. Once you have the project downloaded, unzip it and open Fax.sln with Visual Studio.

This application is already complete and ready to go, so you can test it out to see what it does before you start to read through the code to see how it works.  When you first compile the solution, it will automatically get the Voice Elements Client from NuGet.

Voice Elements MainCode

The core class of this project is IvrApplication. This class contains a lot of logic that sets up the application as a windows service so you can ignore a lot of the code in it for now. The most important method here is MainCode().

When the application is run, it starts a new thread which runs MainCode(). This connects to the Voice Elements servers in the cloud. Then loops indefinitely checking for new tasks to run, and inbound call events.

Note that Log.Write() is used frequently to log call progress and help with debugging. It is recommended that you continue to do this as you program your own Voice Elements applications

The first thing MainCode() does is connect to the Voice Elements servers. This is done by constructing a new TelephonyServer object passing in server ip, username, and password as parameters. These values have already been generated for your account but you can change them in your Settings.settings file.

MainCode() also sets the CacheMode on the TelephonyServer object. ClientSession mode means that the server will stream and cache the files to and from your client machine. These files are flushed after you disconnect. Server mode means that the files reside on the server and will use the full path name to find them there. Note that Server mode can only be used on your own dedicated Voice Elements server.

After connecting to the server and setting its cache mode the new call event should be subscribed to. This sets a method to be called when an incoming call is received. In this example TelephonyServer_NewCall() is the method to be called on new incoming call events.

RegisterDNIS() is then called on the TelephonyServer to tell the server which phone numbers the application will handle. This method can be called with no parameters to instruct Voice Elements to handle calls from all phone numbers on your account. Otherwise you can specify numbers to handle as parameters.

try
{
    Log.Write("Connecting to: {0}", Properties.Settings.Default.PhoneServer);

    m_telephonyServer = new TelephonyServer("gtcp://" + Properties.Settings.Default.PhoneServer,
    Properties.Settings.Default.UserName,
    Properties.Settings.Default.Password);

    // CHANGE YOUR CACHE MODE HERE
    m_telephonyServer.CacheMode = VoiceElements.Interface.CacheMode.ClientSession;

    // SUBSCRIBE to the new call event.
    m_telephonyServer.NewCall += new VoiceElements.Client.NewCall(TelephonyServer_NewCall);
    m_telephonyServer.RegisterDNIS();

    // Subscribe to the connection events to allow you to reconnect if something happens to the internet connection.
    // If you are running your own VE server, this is less likely to happen except when you restart your VE server.
    m_telephonyServer.ConnectionLost += new ConnectionLost(TelephonyServer_ConnectionLost);
    m_telephonyServer.ConnectionRestored += new ConnectionRestored(TelephonyServer_ConnectionRestored);
}

Receiving a Fax

Let’s take a look at the logic for receiving a fax via an inbound call. In the MainCode() of IvrApplication we set TelephonyServer_NewCall() to be called when a new phone call is received. This generates the ChannelResource which you can basically think of as the object that is the phone line. The ChannelResource class contains all of the methods and properties that you would expect to be able to perform with a phone line. The InboundCall class is used for handling all of the logic for inbound phone calls.

TelephonyServer_NewCall() constructs a new InboundCall object for which the TelephonyServer object and the ChannelResource object are provided as parameters. The RunScript() method is then called on the new InboundCall object. This method contains much of the logic for programmable voice on inbound phone calls.

static void TelephonyServer_NewCall(object sender, VoiceElements.Client.NewCallEventArgs e)
{
    try
    {
        Log.Write("NewCall Arrival! DNIS: {0} ANI: {1} Caller ID Name: {2}", e.ChannelResource.Dnis,
        e.ChannelResource.Ani, e.ChannelResource.CallerIdName);

        // Handle The New Call Here
        InboundCall inboundCall = new InboundCall(m_telephonyServer, e.ChannelResource);
        inboundCall.RunScript();
    }
    catch (Exception ex)
    {
        Log.WriteException(ex, "IvrApplication::NewCall");
        e.ChannelResource.Disconnect();
        e.ChannelResource.Dispose();
    }
}

RunScript() contains the majority of the logic for handling the inbound call and receiving the fax. The call is first answered by calling the Answer() method on the ChannelResource object. A FaxResource variable is also initialized as null. The GetFaxResource() method is then called on the ChannelResource object and set to the new FaxResource. If a fax resource is unavailable the method will return. Otherwise the FaxMode property must be set on the FaxResource. A filename is then created and the fax is received by calling the Receive() method on the FaxResource passing in the file name as a parameter.

try
{
    // Answer the call
    Log.WriteWithId(m_channelResource.DeviceName, "Answering...");
    m_channelResource.Answer();

    // Get a fax resource to use
    FaxResource faxResource = null;

    try
    {
        faxResource = m_channelResource.GetFaxResource();
    }
    catch (Exception ex)
    {
        Log.WriteException(ex, "Unable to get fax resource");
    }

    if (faxResource == null)
    {
        m_channelResource.VoiceResource.PlayTTS("No Fax Resources Available");
        Log.WriteWithId(m_channelResource.DeviceName, "No fax resources are available");
        return;
    }

    try
    {
        Log.WriteWithId(m_channelResource.DeviceName, "Received Fax Resource: {0}", faxResource.DeviceName);

        // If using T.38, set the fax mode now. For additional information on T.38, go here: 
        // http://support.voiceelements.com/index.php?title=Faxing_Tips_and_Topics#Selecting_a_Fax_Mode
        faxResource.FaxMode = VoiceElements.Interface.FaxMode.T38;

        // Have the system send stats between pages
        m_channelResource.FaxResource.FaxStatus += fr_FaxStatus;
        faxResource.EnableFaxStatusEvents = true;

        // Use the log file often to help with debugging
        string filename = "RC_" + DateTime.Now.ToString("yyMMddhhmmss") + m_channelResource.DeviceName + ".tif";
        Log.Write("Receiving Fax to " + filename);

        // Receive the fax
        faxResource.Receive(filename);

        Log.WriteWithId(m_channelResource.DeviceName, "CalledSubscriberId Is: {0}", m_channelResource.FaxResource.CalledSubscriberId);
        Log.WriteWithId(m_channelResource.DeviceName, "TransmittingSubscriberId Is: {0}", m_channelResource.FaxResource.TransmittingSubscriberId);
        Log.WriteWithId(m_channelResource.DeviceName, "PageCount Is: {0}", m_channelResource.FaxResource.PageCount);
        Log.WriteWithId(m_channelResource.DeviceName, "FaxResult Is: {0}", m_channelResource.FaxResource.FaxResult);
        Log.WriteWithId(m_channelResource.DeviceName, "PhaseEStatus Is: {0}", m_channelResource.FaxResource.PhaseEStatus);
    }
    finally
    {
        faxResource.EnableFaxStatusEvents = false;
        m_channelResource.FaxResource.FaxStatus -= fr_FaxStatus;
        faxResource.Dispose();
    }
}

Sending a Fax

Let’s look at how to send a fax using Voice Elements. The IvrApplication class contains the method MakeOutboundCall()  which is called when the button is clicked on the GUI. This project uses the OutboundCall class to handle the logic for sending faxes via outbound calls. This method first constructs a new OutboundCall object passing in the TelephonyServer object and the number that is to be called. The constructor creates a new ChannelResource to the TelephonyServer.A new thread is then started to call the RunScript() method on the new OutboundCall object.

public static void MakeOutboundCall(string number)
{
    OutboundCall outbound = new OutboundCall(m_telephonyServer, number);

    // Always spawn calls on new threads
    ThreadStart ts = new ThreadStart(outbound.RunScript);
    Thread t = new Thread(ts);
    t.Name = "Outbound";

    t.Start();
}

The RunScript() method contains all of the logic for sending a fax with an outbound call. The CallProgress property is set to CallProgress.WaitForConnect, this instructs the channel to dial and wait for a connect. The OriginatingPhoneNumber property of the ChannelResource can be set to display any outbound Caller ID phone number if needed. The MaximumTime property can also be set so that the call will automatically fail after a chosen amount of time. It is a good idea to set the T.38 flag on the call for increased reliability with the demo phone banks. You do this by making a SipChannel and setting its OutgoingSipHeaders property to “IBC-T38FaxCarrier: True”. At this point the call is placed by calling the Dial() method on the ChannelResource and setting this equal to a DialResult to help keep track of call progress. This DialResult is now used in a switch statement to determine if the call was successful. If not the method returns. If the call was successful then the fax can begin. The first step to faxing is making a new FaxResource by calling GetFaxResource() on the ChannelResource. At this point the FaxMode can be set on the new FaxResource.

try
{
    // Use WriteWithId to differentiate between separate instances of the class
    Log.WriteWithId(m_channelResource.DeviceName, "OutboundCall Script Starting");
    Log.WriteWithId(m_channelResource.DeviceName, "Dialing {0}", m_numberToCall);

    m_channelResource.CallProgress = CallProgress.WaitForConnect;

    // You can display any outbound Caller ID phone number if needed (this is disabled for testing)
    m_channelResource.OriginatingPhoneNumber = Properties.Settings.Default.TestPhoneNumber;

    // Instruct the server to wait no more then 30 seconds for a connection
    m_channelResource.MaximumTime = 30;


    // For increased reliability with the demo phone banks, set the T.38 flag on the call
    SipChannel sc = m_channelResource as SipChannel;
    sc.OutgoingSipHeaders = new string[] { "IBC-T38FaxCarrier: True" };

    // Place the call
    DialResult dr = m_channelResource.Dial(m_numberToCall);

    Log.WriteWithId(m_channelResource.DeviceName, "The dial result for {0} was: {1}", m_numberToCall, dr);

    switch (dr)
    {
        // Any other result and we can't send the fax
        case DialResult.Connected:
        case DialResult.HumanDetected:
        case DialResult.MachineDetected:
        case DialResult.Successful:
            break;
        default:
            Log.WriteWithId(m_channelResource.DeviceName, "Unexpected dial result, cancelling Call");

            if (dr == DialResult.OperatorIntercept && m_channelResource.GeneralCause == 402)
            Log.WriteWithId(m_channelResource.DeviceName, "You have ran out of minutes. Contact customer support to have more added");

            return;
    }

    // Get a fax resource to use
    FaxResource faxResource = null;

    try
    {
        faxResource = m_channelResource.GetFaxResource();
    }
    catch (Exception ex)
    {
        Log.WriteException(ex, "Unable to get fax resource");
    }

    if (faxResource == null)
    {
        m_channelResource.VoiceResource.PlayTTS("No Fax Resources Available");
        Log.WriteWithId(m_channelResource.DeviceName, "No fax resources are available");
        return;
    }

    try
    {
        Log.WriteWithId(m_channelResource.DeviceName, "Received Fax Resource: {0}", faxResource.DeviceName);

        // If using T.38, set the fax mode now. For additional information on T.38, go here: 
        // http://support.voiceelements.com/index.php?title=Faxing_Tips_and_Topics#Selecting_a_Fax_Mode
        faxResource.FaxMode = VoiceElements.Interface.FaxMode.T38;

        // You can also set a cover page file here if needed
        // faxResource.CoverPage = "..\..\CoverPage.tif";

        // The transmitting subscriber identification is a string of information sent to the far side's fax machine. It typically 
        // has the phone number of the your fax machine and perhaps a username. It is limited to 20 bytes of data.
        faxResource.TransmittingSubscriberId = "888-888-8888";
        faxResource.HeaderText = "Fax Company";

        // If desired, all page header information may be suppressed by passing the value "<NOHEADER>" as the header text:
        // faxResource.HeaderText = "<NOHEADER>";

        // Have the system send stats between pages
        m_channelResource.FaxResource.FaxStatus += fr_FaxStatus;
        faxResource.EnableFaxStatusEvents = true;

        // Use the log file often to help with debugging.
        Log.Write("Sending Fax");

        // Send The Fax
        faxResource.Send(@"..\..\TestFax.tif");

        Log.WriteWithId(m_channelResource.DeviceName, "CalledSubscriberId Is: {0}", m_channelResource.FaxResource.CalledSubscriberId);
        Log.WriteWithId(m_channelResource.DeviceName, "TransmittingSubscriberId Is: {0}", m_channelResource.FaxResource.TransmittingSubscriberId);
        Log.WriteWithId(m_channelResource.DeviceName, "PageCount Is: {0}", m_channelResource.FaxResource.PageCount);
        Log.WriteWithId(m_channelResource.DeviceName, "FaxResult Is: {0}", m_channelResource.FaxResource.FaxResult);
        Log.WriteWithId(m_channelResource.DeviceName, "PhaseEStatus Is: {0}", m_channelResource.FaxResource.PhaseEStatus);
    }
    finally
    {
        faxResource.EnableFaxStatusEvents = false;
        m_channelResource.FaxResource.FaxStatus -= fr_FaxStatus;
        faxResource.Dispose();
    }
}
Was this article helpful to you? Yes 15 No

How can we help?