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