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