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.Net;
using System.Xml;
using System.Text;
using System.Threading;
using System.Collections;

using IVRForBeginners;

/******************************************************************************
 All .Net IVR applications are implemented as classes derived from our base
******************************************************************************/
public class CSWeather : NetClientCall
{
 private static string    prompt;
 private static string    welcome;
 private static string    regrets;
 private static string    baseQuery;
 private static Hashtable states;

 private string          zip;
 private string          weather;
 private IAsyncResult    iar;
 private HttpWebRequest  req;
 private HttpWebResponse resp;

 /******************************************************************************
  This method is called once when the application is loaded. It builds a 
  hash table that maps state abrreviations to their full names.
 ******************************************************************************/
 public new static bool Initialize()
 {
  prompt  = "Please enter a five digit ZIP code or press the pound key to quit.";
  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();

  return true;
 }

 /******************************************************************************
  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");
 }

 /******************************************************************************
  The required Answer() method always comprises the entire Voice User 
  Interface for an IVR application. It is called every time a new call arives
  on the line which this application answers.
 ******************************************************************************/
 public void Answer()
 {
  int           i, millis;
  ISynthesizer  syn;
  StringBuilder buffer;
 
  try
  {
   // If the caller hangs up we want an exception

   hangupThrows(true);

   // Get an instance of the synthesizer and greet the caller
   // We should check for a null object which indicates failure

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

   while ( true )
   {
    // Tell the caller what we want ( 5 keystrokes )

    syn.speak(prompt);

    // And wait for him to give it to us

    if ( !inputWait(5, '#', 20000) )
     break;

    // We have no patience if we got fewer

    if ( inputAvailable() < 5 )
     break;

    // If we have the input tell the user to be patient

    syn.speak("Please wait");

    // Build a ZIP code from the key presses

    buffer = new StringBuilder();
    for ( i = 0; i < 5; i++ )
     buffer.Append( getChar() );
    zip = buffer.ToString();

    // Make an asynchronous request of the web service 

    RequestWeather();

    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);
    }

    // When the request completes we fetch the data and build the forecast

    CompleteRequest();

    // That done we kill the music, add a bit of silence 
    // and then deliver the forecast to the caller 
 
    stopPlaying();
    wait(500);
    syn.speak(weather);

    // While speaking we add an entry to the call detail records

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

    // Then we wait for the forecast to be completely spoken

    syn.wait();
   }

   // Be polite and say good bye before we hang up

   syn.speak("Good bye.");
   syn.wait();
  }

  // If the caller hangs up we will cancel the web service request

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

  // Shouldn't happen, but you never know

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

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

  return;
 }

 /******************************************************************************
  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; 
 }

 /******************************************************************************
  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 );
  }
 }

 /******************************************************************************
  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;
 }

}