Download

/******************************************************************************
 This is the weather by ZIP code demo in which the user enters the ZIP code 
 via the keypad and the HTTP GET transaction is performed asynchronously.
******************************************************************************/
using System;
using System.IO;
using System.Net;
using System.Xml;
using System.Text;
using System.Threading;
using System.Reflection;
using System.Collections;

using IVRForBeginners;

/******************************************************************************
 All .Net IVR applications are implemented as classes derived from our base
******************************************************************************/
public class CSWeatherSR : NetClientCall
{
 private const int ZipGrammarId  = 22; // We use off the wall numbers to make
 private const int QuitGrammarId = 67; // the point that they are arbitrary

 private static string    prompt;
 private static string    welcome;
 private static string    regrets;
 private static string    baseQuery;
 private static String[]  propertyNames;
 private static Hashtable states;

 private string          zip;
 private string          weather;
 private IRecognizer     rec;
 private ISynthesizer    syn;
 private IAsyncResult    iar;
 private HttpWebRequest  req;
 private HttpWebResponse resp;

 /******************************************************************************
  This method is called once when the application is loaded. It opens a 
  configuration file with the same name as the assembly but with a .config 
  extension containing the user name and password for the web service. It would
  be a lot shorter if we wanted to hardcode the credentials. It also builds a 
  couple of hash tables for later use.
 ******************************************************************************/
 public new static bool Initialize()
 {
  prompt  = "Please tell me the ZIP code for the town whose " + 
            "weather forecast you want to hear, or say good bye.";
  welcome = "Welcome to the weather demonstration application.";
  regrets = "We are sorry but we had trouble determining the weather for that ZIP code. " +
            "If the ZIP code is valid you can try again later.";

  baseQuery = "http://weather.yahooapis.com/forecastrss?p=";
  BuildStatesTable();
  BuildPropertyNames();

  return true;
 }

 /******************************************************************************
  The required Answer() method always comprises the entire Voice User 
  Interface for an IVR application. 
 ******************************************************************************/
 public void Answer()
 {
  int           i, millis, conf;
  StringBuilder buffer;
 
  try
  {
   // We want an exception if the caller disconnects

   hangupThrows(true);

   // Get an instance of the recognizer
   // Load a grammar that handles the request for the ZIP code
   // Load another that handles the request to quit the application

   rec = getRecognizer();
   rec.loadGrammar("ZIP",  ZipGrammarId);
   rec.loadGrammar("Quit", QuitGrammarId);

   // Get an instance of the synthesizer
   //  and use it to greet the caller

   syn = getSynthesizer();
   syn.speak(welcome);
   syn.wait();

   // The main loop

   while ( true )
   {
    // Start listening first and then prompt the caller

    rec.listen();
    syn.speak(prompt);

    // Wait for the recognizer to tell us how it did

    conf = rec.wait(10000, 30000);

    // Stop listening

    rec.stopListening();

    // Dump the state of the recognition for debugging
   
    rec.dump();

    // On an error or disconnect we are done

    if ( conf < 0 )
     break;

    // Arbitrarily we decide 65% confidence is our threashold

    if ( conf < 6500 )
    {
     NoRecognition();
     continue;
    }

    // If we recognized a phrase in Quit grammar then we are done

    if ( rec.getGrammarId() == QuitGrammarId )
     break;

    else
    {
     // Tell the user to be patient

     syn.speak("Please wait");

     // Build the ZIP code from the parts
     // See the grammar

     buffer = new StringBuilder();
     for ( i = 0; i < 5; i++ )
      buffer.Append( rec.getPropertyText( propertyNames[i] ) );
     zip = buffer.ToString();

     // We need exactly 5 digits

     if ( buffer.Length != 5 )
     {
      NoRecognition();
      continue;
     } 

     // Make an asynchronous request of the web service 

     RequestWeather();

     // Loop while the request remains ongoing
     // Play a message to amuse the caller

     while ( !iar.IsCompleted )
     {
      // Amuse the user with "music on hold" while we wait

      millis = playMessage("strumming"); 

      // Wait for the request to complete, the music to stop,
      // or for the user to hang up. When the music stops we
      // just play it again but we could count the iterations
      // of the loop to enforce a time out.

      wait(iar.AsyncWaitHandle, millis);
     }

     // Request completes, build the forecast

     CompleteRequest();
 
     // Stop the music

     stopPlaying();
     wait(500);

     // Speak the forecast

     syn.speak(weather);
     Console.WriteLine(weather);

     // Log the input and output

     cdrStatusMessage(0, zip);
     cdrStatusMessage(1, weather);

     syn.wait();
    }
   }

   // Say goodbye if the user is still on the line

   if ( !isDisconnected() )
   {
    syn.speak("Good bye.");
    syn.wait();
   }
  }

  // Catch an exception when the caller hangs up
  // Cancel the HTTP transaction when he does

  catch ( NetClientCallTermination e1 )
  {
   CancelRequest();
   Console.WriteLine(e1.Message);
  }

  // For debugging, this should not happen

  catch ( Exception e2 )
  {
   CancelRequest();
   Console.WriteLine(e2.Message);
  }
 }

 /******************************************************************************
  We use this hash table to translate state abbreviations we get from the web
  service to more easily pronounceable stuff.
 ******************************************************************************/
 private static void BuildStatesTable()
 {
  states = new Hashtable();

  states.Add("AL", "Alabama"); 
  states.Add("AK", "Alaska");
  states.Add("AZ", "Arizona");
  states.Add("AR", "Arkansas"); 
  states.Add("CA", "California");
  states.Add("CO", "Colorado");
  states.Add("CT", "Connecticut");
  states.Add("DE", "Delaware");
  states.Add("DC", "D C");
  states.Add("FL", "Florida");
  states.Add("GA", "Georgia");
  states.Add("HI", "Hawaii");
  states.Add("ID", "Idaho");
  states.Add("IL", "Illinois");
  states.Add("IN", "Indiana");
  states.Add("IA", "Iowa");
  states.Add("KS", "Kansas");
  states.Add("KY", "Kentucky");
  states.Add("LA", "Louisiana");
  states.Add("ME", "Maine");
  states.Add("MD", "Maryland");
  states.Add("MA", "Massachusetts");
  states.Add("MI", "Michigan");
  states.Add("MN", "Minnesota");
  states.Add("MS", "Mississippi");
  states.Add("MO", "Missouri"); 
  states.Add("MT", "Montana");
  states.Add("NE", "Nebraska");
  states.Add("NV", "Nevada");
  states.Add("NH", "New Hampshire");
  states.Add("NJ", "New Jersey");
  states.Add("NM", "New Mexico");
  states.Add("NY", "New York");
  states.Add("NC", "North Carolina");
  states.Add("ND", "North Dakota");
  states.Add("OH", "Ohio");
  states.Add("OK", "Oklahoma");
  states.Add("OR", "Oregon");
  states.Add("PA", "Pennsylvania");
  states.Add("PR", "Puerto Rico");
  states.Add("RI", "Rhode Island");
  states.Add("SC", "South Carolina");
  states.Add("SD", "South Dakota");
  states.Add("TN", "Tennessee");
  states.Add("TX", "Texas");
  states.Add("UT", "Utah");
  states.Add("VT", "Vermont");
  states.Add("VA", "Virginia");
  states.Add("WA", "Washington");
  states.Add("WV", "West Virginia");
  states.Add("WI", "Wisconsin");
  states.Add("WY", "Wyoming");
 }

 /******************************************************************************
 ******************************************************************************/
 private static void BuildPropertyNames()
 {
  propertyNames = new String[5];

  propertyNames[0] = "Digit1";
  propertyNames[1] = "Digit2";
  propertyNames[2] = "Digit3";
  propertyNames[3] = "Digit4";
  propertyNames[4] = "Digit5";
 }

 /******************************************************************************
  Pulls the city from the XML document
 ******************************************************************************/
 private string GetCity(XmlDocument doc, XmlNamespaceManager mgr)
 {
  string  city, temp1, temp2;
  XmlNode node;

  // The city and the state reside in the same node ...

  node = doc.SelectSingleNode("//channel/yweather:location", mgr);

  // ... as distinct attributes 

  temp1 = node.Attributes["city"].InnerText;
  temp2 = node.Attributes["region"].InnerText;

  // Expand the state abbreviation if we know it ...

  if ( states.Contains(temp2) )
   city = temp1 + " " + states[temp2];

  // ... or take it as is if we don't

  else 
   city = temp1 + " " + temp2;

  return city;
 }

 /******************************************************************************
  Pulls the temperature from the XML document
 ******************************************************************************/
 private string GetTemperature(XmlDocument doc, XmlNamespaceManager mgr)
 {
  string  temp;
  XmlNode node;

  node = doc.SelectSingleNode("//channel/item/yweather:condition", mgr);
  temp = node.Attributes["temp"].InnerText;
  temp = temp + " degrees Fahrenheit";

  return temp;
 }

 /******************************************************************************
  Pulls the current condition from the XML document
 ******************************************************************************/
 private string GetOutlook(XmlDocument doc, XmlNamespaceManager mgr)
 {
  string  outlook;
  XmlNode node;

  node = doc.SelectSingleNode("//channel/item/yweather:condition", mgr);
  outlook = node.Attributes["text"].InnerText;

  return outlook;
 }

 /******************************************************************************
  Pulls the wind speed and direction from the XML document
 ******************************************************************************/
 private string GetWind(XmlDocument doc, XmlNamespaceManager mgr)
 {
  int     degrees;
  string  wind, temp1, temp2;
  XmlNode node;

  node = doc.SelectSingleNode("//channel/yweather:wind", mgr);
  temp1 = node.Attributes["direction"].InnerText;
  temp2 = node.Attributes["speed"].InnerText;

  degrees = System.Convert.ToInt32(temp1);
  wind = "from the " + Direction(degrees) + " at " + temp2 + " miles per hour";

  return wind;
 }

 /******************************************************************************
  Translates a compass reading to text
 ******************************************************************************/
 private string Direction(int degrees)
 {
  string dir;

  if ( degrees <= 11 )
   dir = "north";

  else if ( degrees <= 33 )
   dir = "north northeast";

  else if ( degrees <= 56 )
   dir = "northeast";

  else if ( degrees <= 78 )
   dir = "east northeast";

  else if ( degrees <= 101 )
   dir = "east";

  else if ( degrees <= 123 )
   dir = "east southeast";

  else if ( degrees <= 146 )
   dir = "southeast";

  else if ( degrees <= 168 )
   dir = "south southeast";

  else if ( degrees <= 191 )
   dir = "south";

  else if ( degrees <= 213 )
   dir = "south southwest";

  else if ( degrees <= 236 )
   dir = "southwest";

  else if ( degrees <= 258 )
   dir = "west southwest";

  else if ( degrees <= 281 )
   dir = "west";

  else if ( degrees <= 303 )
   dir = "west northwest";

  else if ( degrees <= 326 )
   dir = "northwest";

  else if ( degrees <= 348 )
   dir = "north northwest";

  else 
   dir = "north";

  return dir;
 }

 /******************************************************************************
  If the user hangs up we use this function to cancel the outstanding web 
  service request
 ******************************************************************************/
 private void CancelRequest()
 {
  try
  {
   if ( req != null )
   {
    req.Abort();
    CompleteRequest();
   }
  }

  catch ( Exception e ) 
  {
   Console.WriteLine( e.Message );
  }
 }

 /******************************************************************************
  When the asynchronous HTTP transation completes we fetch the results
 ******************************************************************************/
 private void CompleteRequest()
 {
  bool                isOpen;
  string              city, outlook, temp, wind;
  XmlDocument         doc;
  StringBuilder       msg;
  XmlNamespaceManager mgr;
  
  doc     = null;
  resp    = null;
  isOpen  = false;
  weather = null;

  try
  {
   resp = (HttpWebResponse) req.EndGetResponse(iar);
   isOpen = true;

   // The response from the web service is XML so load it into a document

   doc = new XmlDocument();
   doc.Load( resp.GetResponseStream() );

   // Get a namespace manager and add the namespace

   mgr = new XmlNamespaceManager(doc.NameTable);
   mgr.AddNamespace("yweather", "http://xml.weather.yahoo.com/ns/rss/1.0" );

   // Then pull the city, temperature, current condition and wind speed from it

   city = GetCity(doc, mgr);
   temp = GetTemperature(doc, mgr);
   outlook = GetOutlook(doc, mgr);
   wind = GetWind(doc, mgr);

   // We take those bits of information to build a two sentence forecast

   msg = new StringBuilder();
   msg.Append("In ");
   msg.Append(city);
   msg.Append(", the weather is ");
   msg.Append(outlook);
   msg.Append(" with a temperature of ");
   msg.Append(temp);
   msg.Append(". The winds are ");
   msg.Append(wind);
   msg.Append(".");

   weather = msg.ToString();
  }

  catch ( InvalidOperationException )
  {
   isOpen = false;
  }

  catch ( Exception e )
  {
   Console.WriteLine(e.Message);
  }

  // Make sure we close the connection when we are done

  finally 
  {
   if ( isOpen )
    resp.Close();
  }

  // If we failed to build a forecast we'll offer an apology instead

  if ( weather == null )
   weather = regrets;

  return; 
 }

 /******************************************************************************
  Starts an asynchronous HTTP GET transaction
 ******************************************************************************/
 private void RequestWeather()
 {
  req = (HttpWebRequest) WebRequest.Create( baseQuery + zip );
  iar = req.BeginGetResponse(null, null);

  return;
 }

 /******************************************************************************
  Apologizes to the caller when we can't recognize his speech
 ******************************************************************************/
 private void NoRecognition()
 {
  rec.reset();
  syn.speak("I'm sorry, but I didn't get that.");
  syn.wait();
 }
}