Do you live on the coastline in where tropical storms could come and may change your plans? On the go and want to get information about next weeks trip to the beach? Maybe you just want to learn about storm history, and have an Echo handy. If any of these fit, then the Hurricane Center skill is for you!
First, to understand the key components required for using Alexa, it's good to understand what the Echo does using the Alexa Voice Service (AVS) versus what the Alexa skill is that is invoked by the device. The Echo is what handles user interaction, both the voice translation and invoking the skill. When building an application, you need to configure aspects within the Amazon developer console, and establish the location of the skill.
The Alexa Skill is an API that you write that sits somewhere in the Cloud. It's easiest to write this as a Lambda function as I have in this project, however you can do this as any restful service hosted anywhere. The sections below highlight how each part of this works in more detail.
This breaks the problem down into three parts.
Part 1 - Building the Alexa Voice Service (AVS)Setting up the voice service requires establishing rules for leveraging the voice/text capabilities of Alexa, and then establishing where the Alexa Skill has been deployed. First, you'll need to setup a schema that describes a list of intents which tie AVS to the Skill. You can almost think of intents as different operations within the API.
{
"intents": [
{
"intent": "ListStormNames"
},
{
"intent": "SetOceanPreference",
"slots": [
{
"name": "Ocean",
"type": "LIST_OF_OCEANS"
}
]
},
{
"intent": "StormsFromPriorYears",
"slots": [
{
"name": "Date",
"type": "AMAZON.DATE"
}
]
},
{
"intent": "ThisYearsStorms"
},
{
"intent": "CompleteListOfStorms"
},
{
"intent": "GetStormDetail",
"slots": [
{
"name": "Storm",
"type": "LIST_OF_STORMS"
}
]
},
{
"intent": "AMAZON.StartOverIntent"
},
{
"intent": "AMAZON.HelpIntent"
},
{
"intent": "AMAZON.StopIntent"
},
{
"intent": "AMAZON.CancelIntent"
}
]
}
Then, you will need to configure the AVS to translate different voice "utterances" that will invoke the intents outlined in the schema. Here are the entries for how the GetStormDetail intent could be invoked, and this file must have a comprehensive list of all different combinations. Voice interaction is different than a visual interface, so being effective at d
GetStormDetail tell me about {Storm}
GetStormDetail tell me more about {Storm}
GetStormDetail tell me about Hurricane {Storm}
GetStormDetail give me more info on {Storm}
GetStormDetail give me more details on {Storm}
GetStormDetail I want info about {Storm}
GetStormDetail what about Hurricane {Storm}
In the above example, we can also create a variable called a slot that contains a dictionary of choices. In this list of utterances, {Storm} is the list of potential storm names. AVS then tries to recognize this list and helps in the translation of voice to text.
Alex
Arthur
Bertha
Bill
Cindy
There is additional configuration within the
Part 2 - Writing the Alexa SkillThe AVS will invoke the Alexa skill and pass a standard json object. Here's an example of what the request looks like, and The interface contract between the skill and the API is a json object, and here's the basic model.
{
"session": {
"sessionId": "SessionId.879a6fa1-1543-4e4c-84e7-497e21b6e3fb",
"application": {
"applicationId": "amzn1.echo-sdk-ams.app.this-is-unique-to-your-app"
},
"user": {
"userId": "amzn1.ask.account.VERYLONGHASH"
},
"new": true
},
"request": {
"type": "IntentRequest",
"requestId": "EdwRequestId.20dfbe19-db47-491a-a1c2-5ac8c4c6bc20",
"timestamp": "2016-04-30T19:50:36Z",
"intent": {
"name": "IntentInvokedByASV",
"slots": {}
},
"locale": "en-US"
},
"version": "1.0"
}
The response that must come back and be received by the AVS looks like this.
{
"version": "1.0",
"response": {
"outputSpeech": {
"type": "PlainText",
"text": "This is the speech area and is what the voice will read."
},
"card": {
"content": "This shows up as text on the app and can be verbose",
"title": "Title on App",
"type": "Simple"
},
"reprompt": {
"outputSpeech": {
"type": "PlainText",
"text": "This will be used in case there is a delay in responding"
}
},
"shouldEndSession": false
},
"sessionAttributes": {
"userPromptedToContinue": true
}
}
There are several attributes in the response object - here's a little information about each.
- outputSpeech - this is what AVS will dictate back to the user to deliver the response using voice.
- card - this is text that can be displayed on a app. there are different types - "Simple" is text only, and there are others that include images.
- reprompt - this is additional voice dialog that will come back to the user in case a response is not received.
- shouldEndSession - this is a boolean letting AVS know if the session is to continue, or if it can be ended.
- sessionAttributes - this is data passed back to the AVS that is being managed for the ongoing dialog.
The logic in the skill is responsible for the business rules converting request to response. For my application, it's a matter of getting details about current or prior tropical storms. I chose to write this in NodeJS, and deploy it as a Lambda function in AWS. In the github repo attached to this project, this is the lambda.js file.
The core logic is driven by the intent name, and for each intent, there is a different function call that processes the business logic, and creating the proper response. Below is the diagram mapping out what functionality is available in the skill.
Also note - setting the ocean preference is a key part to the skill, and once this is set, it needs to be stored as part of the session so it can be retrieved in later requests.
Part 3 - Managing storm data read by the Alexa SkillIn the Alexa Skill, I'm separating out the actual business rule logic from the data itself given that there are many years of storm data, and I don't want it embedded within the function. When tropical storms and hurricanes are active, I also don't want to to change the lambda function, rather have it as a data object. To accomplish this, I created a S3 bucket, and modeled the data into a number of json objects that are used to store the information, including the current storm detail. To allow access to the data, the execution role for the Lambda function must also be allowed to read the S3 bucket.
The weather site http://www.wunderground.com provides API's for gathering weather data, and they have an API that gathers hurricane information. Here are the details.
http://api.wunderground.com/api/your-api-key-here/currenthurricane/view.json
Periodically pulling the data from the API through a scheduled Lambda function enables me to automate maintaining the currStorms.json object that is used by the skill. The challenge with wunderground is that the data provided is rather sparse and not conducive to language. For example, the API provides excellent detail on quantifying the storm speed and current grid location, but other than a cardinal direction it is going in, nothing around a forecast. For reference, during a recent storm here is the data being stored in the currStorms.json object.
{
"activeStorms": true,
"latestUpdate": "Saturday, May 28th, 2016 at 2:00PM Eastern Daylight Time",
"nextUpdate": "Saturday, May 28th, 2016 at 5:00PM Eastern Daylight Time",
"stormYear": 2016,
"storms": [
{
"formed": true,
"hazards": {
"rainfall": "1 to 3 inches from eastern South Carolina through southeastern North Carolina",
"stormSurge": "1 to 2 feet above ground level",
"surf": "life-threatening surf and rip current conditions along portions of the southeastern United States coast through the weekend"
},
"landfall": false,
"landfallPredict": "the Carolina coast Saturday evening, or early Sunday morning",
"location": {
"lat": "30.7 North",
"long": "79.0 West",
"proximity": "225 miles south of Cape Fear North Carolina"
},
"movement": {
"direction": "Northwest",
"forecast": "continue for the next 24 hours. A reduction of the forward speed is expected by Saturday night as the system nears the coast.",
"speed": 13
},
"ocean": "Atlantic",
"peakWinds": 35,
"pressure": 1009,
"stormName": "Two",
"stormType": "Tropical Depression",
"tropStormLocation": "the coast of South Carolina from the Savannah River northeastward to Little River Inlet",
"tropStormStart": "None",
"tropStormWarning": true
}
]
}
The National Weather Service does have an RSS feed to use that does have all of this rich content, and can be accessed here.
This provides detail, however the DOM nodes are really poorly modeled, and not only do I need to be able to translate the XML, there are huge blocks of text that will require parsing to map back to the object.
For now I've just created a Lambda function that updates the currStorms.json object, with the attributes being manually set before running. It's also in the repo above, and is the loadData.js file.
Comments