andreas

Hello, This is your PI System Calling to inform you…

Blog Post created by andreas Employee on Mar 12, 2010

Hello, This is your PI System calling to inform you…

Wouldn’t it be cool to get a call from PI Notifications?

 

Phone calls convey a much higher sense of urgency, and you don’t have to worry about it they were read in time or not! Modern IP telephony provides endless possibilities to relate information in a more timely manner.
PI Notifications comes armed with the perfect delivery channel: web services! Around here, we use Skype(tm) as a convenient and cost effective communication tool, but it could be other tools as well. As an example, let us go ahead and write a little web service that takes a message from PI Notifications and delivers it via Skype.

  1. The first thing to do is create a web service project, so open Microsoft Visual Studio 2008, select File>New>Project>Visual C#>ASP.NET Web Service Application. I decided to call mine MySkypeWebService and I have chosen to write it in C#.
  2. VisualStudio.jpg

  3. Now, we want to call a contact, so we have to create a WAV file and deliver this via Skype. That means we have to add some references to our project:
    1. A .NET reference to PresentationCore and to WindowsBase (MediaPlayer requires this and MediaPlayer is an easy way to get the length of the WAV file).
    2. A .NET reference to System.Speech.Synthesis to make our WAV file.
    3. A COM reference to SKYPE4COMLib for Skype.
      (you first need to download and install the Skype4COM API Wrapper from the Skype Developer Downloads page).

  4. To improve readability of the code, add some “using” statements for the System.Windows.Media, the System.Speech.Synthesis and the System.Speech.AudioFormat namespaces.

  5. Note 1: Making a WAV file and placing a call takes time. For file I/O and other time consuming operations it is recommended to create asynchronous web services. There are various resources available describing how to create an asynchronous Web Service and provide all the necessary information.

  6. So what about the code? Here is how we start with the declaration of the web service:

    using System;

     

    using System.Collections.Generic;

     

    using System.Linq;

     

    using System.Web;

     

    using System.Web.Services;

     

     

     

    // Add .NET Reference to PresentationCore for Media Player

     

    // Add .NET Reference to WindowsBase for Media Player

     

    // Add COM Reference to SKYPE4COMLib for Skype

     

    // Add .NET Reference to System.Speech for the speech engine

     

    using System.Windows.Media;

     

    using System.Speech.Synthesis;

     

    using System.Speech.AudioFormat;

     

     

     

    namespace MySkypeWebService

     

    {

     

        /// <summary>

     

        /// Summary description for Service1

     

        /// </summary>

     

        [WebService(Namespace = "http://tempuri.org/")]

     

        [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]

     

        [System.ComponentModel.ToolboxItem(false)]

     

        // To allow this Web Service to be called from script,

     

        // using ASP.NET AJAX, uncomment the following line.

     

        // [System.Web.Script.Services.ScriptService]

     

        publicclassService1 : System.Web.Services.WebService

     

        {

     

        }

     

    }


  7. Now let us start with the WAV File. The following code consists of a method that will create a WAV file in the appropriate format for Skype to understand. As parameter I have chosen the text and the filename:

    // Creates the WAV from Text

     

    privatevoid Text2WAV(string strOutput, string strFilename)

     

    {

     

        try

     

        {

     

            // The SpeechSynthesizer

     

            SpeechSynthesizer _synth

     

                = newSpeechSynthesizer();

     

            DateTime _time = DateTime.Now;

     

            // Set the format to something SKYPE likes

     

            // 16KHz, 16bit, mono

     

            SpeechAudioFormatInfo _format

     

                = newSpeechAudioFormatInfo(16000,

     

                                            AudioBitsPerSample.Sixteen,

     

                                            AudioChannel.Mono);

     

            _synth.SetOutputToWaveFile(strFilename, _format);

     

            // Slow down a bit, the voice is too fast for me

     

            _synth.Rate = -2;

     

            // Speak!

     

            _synth.Speak(strOutput);

     

            _synth.SetOutputToNull();

     

        }

     

        catch (Exception _ex)

     

        {

     

            System.IO.StreamWriter _StreamWriter

     

                = new System.IO.StreamWriter

     

                    (System.Web.Hosting.HostingEnvironment.MapPath

     

                     ("\\MyWebService_EXCEPTION.TXT"),

     

                     true);

     

     

     

            DateTime _time = DateTime.Now;

     

            _StreamWriter.WriteLine("<!---------------------------------------->");

     

            _StreamWriter.WriteLine("<!-- " + _time.ToLocalTime().ToString() + " -->");

     

            _StreamWriter.WriteLine("<!---------------------------------------->");

     

     

     

            _StreamWriter.WriteLine("<!---------------------------------------->");

     

            _StreamWriter.WriteLine(_ex.ToString());

     

            _StreamWriter.WriteLine("<!---------------------------------------->");

     

     

     

            _StreamWriter.Close();

     

        }

     

    }


  8. Now that we have the WAV file, we will create another method that performs the Skype call. For this we need to know the filename and the Skype contact:

    privatevoid MakeSkypeCall(string strFilename, string strContact)

     

    {

     

        // Need to allow ASP.NET to "run as" system

     

        // and enable IIS to interact with Desktop

     

        try

     

        {

     

            // A workaround to get the length of the audio file

     

            // is to use MediaPlayer

     

            MediaPlayer _mplayer = newMediaPlayer();

     

            Uri _path = newUri(strFilename);

     

            _mplayer.Open(_path);

     

            while (_mplayer.DownloadProgress < 1.0)

     

            {

     

                // Need to wait until everything is settled,

     

                // before to ask for the duration of the file

     

                System.Threading.Thread.Sleep(100);

     

            }

     

            System.Threading.Thread.Sleep(1000);

     

            System.Windows.Duration _duration = _mplayer.NaturalDuration;

     

            _mplayer.Close();

     

     

     

            // Get the Skype Object

     

            SKYPE4COMLib.Skype _oSkype

     

                = new SKYPE4COMLib.Skype();

     

            // Search for the user in the contacts

     

            SKYPE4COMLib.User _oUser

     

                = _oSkype.SearchForUsers(strContact)[1];

     

            // Create the SkypeCall

     

            SKYPE4COMLib.Call _oCall

     

                = new SKYPE4COMLib.Call();

     

            _oCall = _oSkype.PlaceCall(strContact, "", "", "");

     

            // Wait until the call is picked up or 180 seconds

     

            int _i = 0;

     

            while ((_oCall.Status !=

     

                        _oSkype.Convert.TextToCallStatus("INPROGRESS"))

     

                    & (_i < 1800))

     

            {

     

                _i++;

     

                System.Threading.Thread.Sleep(100);

     

            }

     

            if (_i < 1800)

     

            {

     

                System.Threading.Thread.Sleep(1000);

     

                // Play the wav

     

                _oCall.set_InputDevice

     

                    (SKYPE4COMLib.TCallIoDeviceType.callIoDeviceTypeFile,

     

                     strFilename);

     

                // Wait for the WAV file to be finish playing before to hang up

     

                System.Threading.Thread.Sleep

     

                    (Convert.ToInt32(_duration.TimeSpan.TotalMilliseconds) + 500);

     

                // Hang up

     

                _oCall.Finish();

     

            }

     

            else

     

            {

     

                System.IO.StreamWriter _StreamWriter;

     

                _StreamWriter = new System.IO.StreamWriter

     

                    (System.Web.Hosting.HostingEnvironment.MapPath

     

                        ("\\MyWebService_NoAnswer.TXT"),

     

                     true);

     

     

     

                DateTime _time = DateTime.Now;

     

                _StreamWriter.WriteLine("<!---------------------------------------->");

     

                _StreamWriter.WriteLine("<!-- " + _time.ToLocalTime().ToString() + " -->");

     

                _StreamWriter.WriteLine("<!---------------------------------------->");

     

     

     

                _StreamWriter.WriteLine("<!---------------------------------------->");

     

                _StreamWriter.WriteLine(strFilename);

     

                _StreamWriter.WriteLine("<!---------------------------------------->");

     

     

     

                _StreamWriter.Close();

     

            }

     

        }

     

        catch (Exception _ex)

     

        {

     

            System.IO.StreamWriter _StreamWriter;

     

            _StreamWriter = new System.IO.StreamWriter

     

                (System.Web.Hosting.HostingEnvironment.MapPath

     

                    ("\\MyWebService_EXCEPTION.TXT"),

     

                 true);

     

     

     

            DateTime _time = DateTime.Now;

     

            _StreamWriter.WriteLine("<!---------------------------------------->");

     

            _StreamWriter.WriteLine("<!-- " + _time.ToLocalTime().ToString() + " -->");

     

            _StreamWriter.WriteLine("<!---------------------------------------->");

     

     

     

            _StreamWriter.WriteLine("<!---------------------------------------->");

     

            _StreamWriter.WriteLine(_ex.ToString());

     

            _StreamWriter.WriteLine("<!---------------------------------------->");

     

     

     

            _StreamWriter.Close();

     

        }

     

    }


  9. So let us recall here: we need something to send from PI Notifications to this web service. I have chosen to take four parameters: the Element (in fact I will use the Notification instead of the Element as it is more user-friendly than an element including path), the trigger time, a description and finally the Skype contact. The following code consists of a method that creates my WAV file (we can keep it for further documentation), creates a reasonable message from the element, the trigger time and the description, and calls the code to create my WAV file and do the Skype call.

    publicstring Wav2Skype(string strElement,

     

                            string strTime,

     

                            string strDescription,

     

                            string strContact)

     

    {

     

        DateTime _time = DateTime.Now;

     

        string strFilename;

     

        strFilename = System.Web.Hosting.HostingEnvironment.MapPath

     

                        ("\\Note_"

     

                         + _time.ToString("dd_MM_yy_HH_mm_ss")

     

                         + ".wav");

     

        Text2WAV(strElement

     

                 + ", " + strTime

     

                 + ", " + strDescription,

     

                 strFilename);

     

        MakeSkypeCall(strFilename, strContact);

     

        return"DONE";

     

    }


  10. Looks like all the heavy-lifting is done! All we need now is we need to expose a web service. Based on the information from MSDN here, we need a delegate and two web methods as below:

    // Delegate

     

    publicdelegatestringWav2SkypeAsyncStub(string strElement,

     

                                              string strTime,

     

                                              string strDescription,

     

                                              string strContact);

     

     

     

    // Actual method which is exposed as a web service

     

    [WebMethod]

     

    publicIAsyncResult BeginWav2Skype(string strElement,

     

                                       string strTime,

     

                                       string strDescription,

     

                                       string strContact,

     

                                       AsyncCallback asyncCallback,

     

                                       object asyncState)

     

    {

     

        Wav2SkypeAsyncStub _asyncStub

     

            = newWav2SkypeAsyncStub(Wav2Skype);

     

        // Using delegate for asynchronous implementation  

     

        return _asyncStub.BeginInvoke(strElement,

     

                                      strTime,

     

                                      strDescription,

     

                                      strContact,

     

                                      asyncCallback,

     

                                      null);

     

    } // BeginWav2Skype

     

     

     

    [WebMethod]

     

    publicstring EndWav2Skype(IAsyncResult result)

     

    {

     

        return"OK";

     

    } // EndWav2Skype


  11. Build MySkypeWebService using the Build>Build MySkypeWebService menu in Visual Studio.


  12. At this point our web service would now work - well almost. Skype is a nice tool, but keep in mind it is an interactive desktop application which leads to two issues:
    1. We have to open Skype and log in (or set Skype to automatically start and log in at Windows startup).
    2. By default neither IIS nor ASP.NET can interact with the desktop of a user.

    The first issue cannot be solved: you have to be logged in into Skype on the machine providing the web service. The second issue can be solved by doing the following:
    1. In the Services applet, open the properties for the IIS Admin Service service and check the Allow service to interact with desktop option on the Log On tab.
    2. IIS.jpg

    3. Edit the following file
      C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\CONFIG\machine.config
      by locating the line
      <processModel />
      and changing it to
      <processModel autoConfig="true" userName="SYSTEM" password="AutoGenerate"/>


    Note 2: If you want to test your web service with the Echo / Sound Test Service (echo123) you will not hear any echo in your speakers while playing the WAV file.
    Note 3: You will have to set a break point in the web service to not start playing before you are instructed to speak by the Echo / Sound Test Service.

  13. Publish the MySkypeWebService using the Build>Publish MySkypeWebService menu in Visual Studio.


  14. Now we should be free to use the web service in PI Notifications:

    Notifications.jpg

  15. And receive a call from PI Notifications:
  16. ASCalling.jpg


Challenge for the community

  1. The above solution requires someone to be logged in to run Skype. I did not find a way to programmatically log in to Skype to avoid this.
  2. We have the wav file. We deliver it via Skype - how about a voice modem as another option?

Outcomes