UAS API - Get Us Weather Sample Application
-
Filename:
samples/get_us_weather.py
Description:
This is an inbound application that prompts the caller to enter a US Zipcode and then reads the weather for that location to the caller. It demonstrates the use of external operations to acquire data for the phone application.
The call is answered and the TTS function
channel.FilePlayer.say()
is used to ask the caller for a U.S. Zipcode, ending with a # or *. The functionchannel.DTMFDetector.get_digits
is used to collect the DTMF digits, specifying theend
argument which is a list of DTMF digits that indicates to the Cloud which characters to use to end DTMF collection.If the correct number of digits has been entered, the
GetUSWeather()
function is called. This sends an enquiry to a Yahoo Weather website with the Zipcode that was entered. Depending on the status code of the request, the weather data is extracted using XML. A string with the weather summary is returned by the function.Finally, TTS is used in the main function to say the text. Then the call is hung up.
Code:
""" An inbound application that prompts the caller to enter a US Zipcode and then reads the weather for that location to the caller. It gets the weather forecast using the Yahoo query API. """ __uas_identify__ = "application" __uas_version__ = "1.0b3" import sys, os if sys.version > '3': import http.client else: import httplib from prosody.uas import Hangup, Error, ICanRun from urllib import quote import json # expand US state abbreviation to full name def us_state(abbreviation): return { 'AK': 'Alaska', 'AL': 'Alabama', 'AR': 'Arkansas', 'AS': 'American Samoa', 'AZ': 'Arizona', 'CA': 'California', 'CO': 'Colorado', 'CT': 'Connecticut', 'DC': 'District of Columbia', 'DE': 'Delaware', 'FL': 'Florida', 'GA': 'Georgia', 'GU': 'Guam', 'HI': 'Hawaii', 'IA': 'Iowa', 'ID': 'Idaho', 'IL': 'Illinois', 'IN': 'Indiana', 'KS': 'Kansas', 'KY': 'Kentucky', 'LA': 'Louisiana', 'MA': 'Massachusetts', 'MD': 'Maryland', 'ME': 'Maine', 'MI': 'Michigan', 'MN': 'Minnesota', 'MO': 'Missouri', 'MP': 'Northern Mariana Islands', 'MS': 'Mississippi', 'MT': 'Montana', 'NA': 'National', 'NC': 'North Carolina', 'ND': 'North Dakota', 'NE': 'Nebraska', 'NH': 'New Hampshire', 'NJ': 'New Jersey', 'NM': 'New Mexico', 'NV': 'Nevada', 'NY': 'New York', 'OH': 'Ohio', 'OK': 'Oklahoma', 'OR': 'Oregon', 'PA': 'Pennsylvania', 'PR': 'Puerto Rico', 'RI': 'Rhode Island', 'SC': 'South Carolina', 'SD': 'South Dakota', 'TN': 'Tennessee', 'TX': 'Texas', 'UT': 'Utah', 'VA': 'Virginia', 'VI': 'Virgin Islands', 'VT': 'Vermont', 'WA': 'Washington', 'WI': 'Wisconsin', 'WV': 'West Virginia', 'WY': 'Wyoming' }.get(abbreviation.strip(), abbreviation) def get_woeid(zip_code): # Get the "where on earth ID" from the zip code if sys.version > '3': http_connect = http.client.HTTPConnection("query.yahooapis.com") else: http_connect = httplib.HTTPConnection("query.yahooapis.com") query = quote('select woeid from geo.places(1) where text = "{0}"'.format(zip_code)) request = '/v1/public/yql?q={0}&format=json'.format(query) http_connect.request("GET", request) response = http_connect.getresponse() if response.status == 200: data = json.loads(response.read()) print("W: {0}".format(data["query"]["results"])) woeid = data["query"]["results"]["place"]["woeid"] return woeid else: return None def get_weather(woeid, my_log=None): if sys.version > '3': http_connect = http.client.HTTPConnection("query.yahooapis.com") else: http_connect = httplib.HTTPConnection("query.yahooapis.com") query = quote('select * from weather.forecast where woeid = {0}'.format(woeid)) request = '/v1/public/yql?q={0}&format=json'.format(query) http_connect.request("GET", request) response = http_connect.getresponse() if response.status == 200: data = json.loads(response.read()) results = data["query"]["results"]["channel"] country = results["location"]["country"] if country == "United States": region = us_state(results["location"]["region"]) else: region = country location = results["location"]["city"] + ", " + region forecasts = results["item"]["forecast"] high_f = float(forecasts[0]["high"]) high_c = int((high_f - 32.0) * 5.0/9.0) temp = "{0} Fahrenheit, {1} Celsius".format(high_f, high_c) weather = "{0}. Today's weather is {1}, with a high of {2}.".format(location, forecasts[0]["text"], temp) high_f = float(forecasts[1]["high"]) high_c = int((high_f - 32.0) * 5.0/9.0) temp = "{0} Fahrenheit, {1} Celsius".format(high_f, high_c) weather = weather + " Tomorrow will be {0}, with a high of {1}.".format(forecasts[1]["text"], temp) if my_log: my_log.info("{0}".format(weather)) print("{0}".format(weather)) return weather else: print("R: {0}".format(response.status)) return None def i_say(channel, to_say): cause = channel.FilePlayer.say(to_say, barge_in=True) if cause != channel.FilePlayer.Cause.NORMAL and cause != channel.FilePlayer.Cause.BARGEIN: raise Error("Say failed: cause is {0}".format(cause)) def main(channel, application_instance_id, file_man, my_log, application_parameters): my_log.info("Get US weather started") dtmf = "" try: channel.ring(2) channel.answer() i_say(channel, "Hello, please enter a five digit US zipcode, followed by the pound sign.") # use ICanRun to prevent accidental infinite loop i_can_run = ICanRun(channel) while i_can_run: dtmf = channel.DTMFDetector.get_digits(end='#*', seconds_predigits_timeout=30) cause = channel.DTMFDetector.cause() if cause == channel.DTMFDetector.Cause.END: dtmf = dtmf[:-1] # check results if len(dtmf) == 5: break channel.DTMFDetector.clear_digits() i_say(channel, "Five digits are required, please try again.") my_log.info("Zip code entered is {0}".format(dtmf)) woeid = get_woeid(dtmf) if not woeid: i_say(channel, "Sorry but I can't find a region for that zip code.") else: try: weather = get_weather(woeid, my_log) except: weather = "I cannot find the weather for that location." i_say(channel, weather) i_say(channel, "Thank you for calling, goodbye") channel.hang_up() my_log.info("Get weather completed") return 0 except Hangup as exc: my_log.info("Get weather completed with Hangup") return 0 except Error as exc: my_log.error("Get weather completed with Error exception! {0}".format(exc)) return -101 except Exception as exc: my_log.exception("Get weather completed with exception! {0}".format(exc)) return -102 finally: if channel.state() != channel.State.IDLE: channel.hang_up()
-
Filename:
Samples\C#\GetUSWeather\GetUSWeather.cs
Description:
The call is answered and some TTS is played, prompting the user to enter the US Zipcode of the location for which a weather update is required. We use an overloaded version of the
DtmfDetector.GetDigits
method, specifying two possible end digits, instead of a maximum number of digits. We specifyfalse
for theclear
parameter to allow 'type-ahead' of the digits and we set the maximum time to wait for the first digit to 10 seconds and the maximum interval between successive digits to 30 seconds. The detected DTMF values are stored in thezipCode
string.At this stage we do some error checking against the possible causes as to why the method finished. We can also check the length of the resultant string, to ensure the correct number of digits have been entered. This is useful if you need to do some validity checking of keypad entry, or if the number of digits is not always fixed in your application.
The
GetUSWeather
method contacts an external website with the Zipcode our caller has entered as part of the URL. The resultant XML data is processed by the method, extracting theyweather:condition
node and extrapolating certain data, such as the date, general description of the current weather and the temperature, which is then read back to the caller using TTS.The application then signs off and hangs up the call.
Code:
using System; using AMSClassLibrary; using UASAppAPI; using System.Net; using System.IO; using System.Xml; using System.Text; // An inbound application that answers the call, prompts the caller to enter // a 5 digit US Zipcode then reads the weather for that location to the caller. // e.g. // 90212 - Beverley Hills CA // 10036 - Times Square, NY // 33139 - South Beach, FL // 94129 - San Francisco, CA // 20011 - Washington, DC // 90028 - Hollywood, CA namespace GetUSWeather { // The application class. // This must have the same name as the assembly and must inherit from either // UASInboundApplication or UASOutboundApplication. // It must override the Run method. public class GetUSWeather : UASInboundApplication { // Possible return codes enum ReturnCode { // Success Codes: Success = 0, // ... any positive integer // Fail Codes: // -1 to -99 reserved ExceptionThrown = -100 } string zipCode = ""; // This is the entry point for the application public override int Run(UASCallChannel channel, string applicationParameters) { this.Trace.TraceInfo("Started"); ReturnCode reply = ReturnCode.Success; try { // Answer the call CallState state = channel.Answer(); if (state == CallState.Answered) { // Call answered this.Trace.TraceInfo("Call answered - callFrom: [{0}]", channel.CallDetails.CallFrom); // Prompt the caller to enter a 5 digit zip code FilePlayerCause playCause = channel.FilePlayer.Say( "Hello, please enter a 5 digit zipcode.", true); do { DtmfDetectorCause detCause = channel.DtmfDetector.GetDigits( 5, null, out zipCode, false, 20, 10); if (detCause == DtmfDetectorCause.HangUp) { break; } if (detCause == DtmfDetectorCause.Count) { break; } channel.DtmfDetector.ClearDigits(); channel.FilePlayer.Say("5 digits are required, please try again.", true); } while (true); if (channel.State == CallState.Answered) { this.Trace.TraceInfo("zipCode entered is [{0}]", zipCode); // Get the weather forecast string weatherText = GetWeather(zipCode); if (weatherText == null) { channel.FilePlayer.Say("I could not obtain weather information for zipcode {0}. " + "Thank you for calling, goodbye.", zipCode); } else { channel.DtmfPlayer.Play("A"); channel.FilePlayer.Say(weatherText); channel.FilePlayer.Say("Thank you for calling, goodbye."); } } } } catch (Exception e) { this.Trace.TraceError("Exception thrown {0}", e.Message); reply = ReturnCode.ExceptionThrown; } finally { channel.HangUp(); } this.Trace.TraceInfo("Completed"); return (int)reply; } // Scrape a us weather rss feed for brief details by zip code private string GetWeather(string zipCode) { StringBuilder weather = new StringBuilder(); string query = String.Format("select * from weather.forecast where woeid in (select woeid from geo.places(1) where text=\"{0}\")", zipCode); string url = String.Format("https://query.yahooapis.com/v1/public/yql?q={0}&format=xml", query); WebRequest req = WebRequest.Create(url); if (req != null) { using (StreamReader streamReader = new StreamReader(req.GetResponse().GetResponseStream())) { XmlDocument doc = new XmlDocument(); doc.Load(streamReader); XmlNamespaceManager nsMgr = new XmlNamespaceManager(doc.NameTable); nsMgr.AddNamespace("yweather", "http://xml.weather.yahoo.com/ns/rss/1.0"); XmlNode node = doc.SelectSingleNode("//yweather:location", nsMgr); string location = node.Attributes.GetNamedItem("city").Value; weather.Append("The weather for "); weather.Append(location); weather.Append(" is "); node = doc.SelectSingleNode("//yweather:condition", nsMgr); weather.Append(node.Attributes.GetNamedItem("temp").Value); weather.Append(" degrees Fahrenheit, "); weather.Append(node.Attributes.GetNamedItem("text").Value); weather.Append("."); this.Trace.TraceInfo(weather.ToString()); } } return weather.ToString(); } } }
-
Filename:
Samples\VB\GetUSWeather\GetUSWeather.vb
Description:
The call is answered and some TTS is played, prompting the user to enter the US Zipcode of the location for which a weather update is required. We use an overloaded version of the
DtmfDetector.GetDigits
method, specifying two possible end digits, instead of a maximum number of digits. We specifyfalse
for theclear
parameter to allow 'type-ahead' of the digits and we set the maximum time to wait for the first digit to 10 seconds and the maximum interval between successive digits to 30 seconds. The detected DTMF values are stored in thezipCode
string.At this stage we do some error checking against the possible causes as to why the method finished. We can also check the length of the resultant string, to ensure the correct number of digits have been entered. This is useful if you need to do some validity checking of keypad entry, or if the number of digits is not always fixed in your application.
The
GetUSWeather
method contacts an external website with the Zipcode our caller has entered as part of the URL. The resultant XML data is processed by the method, extracting theyweather:condition
node and extrapolating certain data, such as the date, general description of the current weather and the temperature, which is then read back to the caller using TTS.The application then signs off and hangs up the call.
Code:
Imports AMSClassLibrary Imports UASAppAPI Imports System.Net Imports System.IO Imports System.Xml Imports System.Text ' An inbound application that answers the call, prompts the caller to enter ' a 5 digit US Zipcode then reads the weather for that location to the caller. ' e.g. ' 90212 - Beverley Hills CA ' 10036 - Times Square, NY ' 33139 - South Beach, FL ' 94129 - San Francisco, CA ' 20011 - Washington, DC ' 90028 - Hollywood, CA Namespace GetUSWeather ' The application class. ' This must have the same name as the assembly and must inherit from either ' UASInboundApplication or UASOutboundApplication. ' It must override the Run method. Public Class GetUSWeather Inherits UASInboundApplication ' Possible return codes Enum ReturnCode ' Success Codes: Success = 0 ' ... any positive integer ' Fail Codes: ' -1 to -99 reserved ExceptionThrown = -100 End Enum Private zipCode = "" ' This is the entry point for the application Overrides Function Run(ByVal channel As UASCallChannel, ByVal applicationParameters As String) _ As Integer Me.Trace.TraceInfo("Started") Dim reply As ReturnCode = ReturnCode.Success Try ' Answer the call Dim state As CallState state = channel.Answer() If state = CallState.Answered Then Me.Trace.TraceInfo("Call answered - callFrom: [{0}]", channel.CallDetails.CallFrom) ' Prompt the caller to enter a 5 digit zip code Dim playCause = channel.FilePlayer.Say("Hello, please enter a 5 digit zipcode.", True) Do Dim detCause = channel.DtmfDetector.GetDigits(5, Nothing, zipCode, False, 20, 10) If detCause = DtmfDetectorCause.HangUp Then Exit Do End If If detCause = DtmfDetectorCause.Count Then Exit Do End If channel.DtmfDetector.ClearDigits() channel.FilePlayer.Say("5 digits are required, please try again.", True) Loop If channel.State = CallState.Answered Then Me.Trace.TraceInfo("zipCode entered is [{0}]", zipCode) ' Get the weather forecast Dim weatherText = GetWeather(zipCode) If weatherText Is Nothing Then channel.FilePlayer.Say("I could not obtain weather information for zipcode {0}. " + "Thank you for calling, goodbye.", zipCode) Else channel.DtmfPlayer.Play("A") channel.FilePlayer.Say(weatherText) channel.FilePlayer.Say("Thank you for calling, goodbye.") End If End If End If Catch ex As Exception Me.Trace.TraceError("Exception thrown {0}", ex.Message) reply = ReturnCode.ExceptionThrown Finally channel.HangUp() End Try Me.Trace.TraceInfo("Completed") Return reply End Function ' Scrape a us weather rss feed for brief details by zip code Private Function GetWeather(ByVal zipCode As String) As String Dim weather As StringBuilder = New StringBuilder Dim query = String.Format("select * from weather.forecast where woeid in (select woeid from geo.places(1) where text=""{0}"")", zipCode) Dim url = String.Format("https://query.yahooapis.com/v1/public/yql?q={0}&format=xml", query) Dim req = WebRequest.Create(url) If Not req Is Nothing Then Using StreamReader = New StreamReader(req.GetResponse().GetResponseStream()) Dim doc = New XmlDocument() doc.Load(StreamReader) Dim nsMgr = New XmlNamespaceManager(doc.NameTable) nsMgr.AddNamespace("yweather", "http://xml.weather.yahoo.com/ns/rss/1.0") Dim node = doc.SelectSingleNode("//yweather:location", nsMgr) Dim location = node.Attributes.GetNamedItem("city").Value weather.Append("The weather for ") weather.Append(location) weather.Append(" is ") node = doc.SelectSingleNode("//yweather:condition", nsMgr) weather.Append(node.Attributes.GetNamedItem("temp").Value) weather.Append(" degrees Fahrenheit, ") weather.Append(node.Attributes.GetNamedItem("text").Value) weather.Append(".") Me.Trace.TraceInfo(weather.ToString()) End Using End If Return weather.ToString() End Function End Class End Namespace