My Experience Building an Alexa Skill

Headline: it is actually pretty easy

Back in November I attended a talk on creating your own Amazon Alexa Skill at Bristech. It’s been something I’ve wanted to play with for a while, so when Amazon had their Black Friday sale I couldn’t resist.

I was pleasantly surprised by Amazon’s own tutorial using Lambda: Alexa Python Tutorial - Build a voice experience in 5 minutes or less. It really was quite simple and quick. Amazon have worked hard to make it easy for you to get a custom skill up and running on real hardware. The code was a little bit grungy with the need to parse and generate JSON though. Also I was a bit disappointed that the natural language definitions are completely separate from the code, even on a different system within the Amazon universe, making it hard to be dynamic and/or modular.

I recommend you follow this tutorial through as it ensures you set the necessary connections up between your Amazon account and the Amazon developer portal. It also lets you select a code template for a skill with a really simple hello world example. The code is hosted on AWS as a Lambda function. This framework ensures you code is brought into existence just in time for servicing and request and then goes away again. It shows you how the basic stuff connects up but leaves you with lots of questions for nexts steps… like how to store state, how to validate inputs, and how to connect to external services.

FYI: It was very quick to get this lambda to call out to an external service using the following code snippet:

import urllib2
urllib2.urlopen("https://mydomain.com", context=ssl._create_unverified_context())

After a little bit of time messing around on AWS with a lambda I gave up on it. Editing, saving and debugging the Lambda was slow and tedious and the raw python, as I mention above, made it clear that you needed a lot of boilerplate code for each function.

After a bit of googling I discovered a python framework called Flask-Ask. Flask-Ask really reduces the amount of boiler plate code for interfacing to the Alexa gateway. It also has templating for formatting standard responses to Intents (Alexa terminology for abstracted spoken phrases for a specific purpose). Flask-Ask can be run on AWS Lambda but that looks like hard work. However it does install and run very easily on any linux distribution and running it yourself makes editing and debugging much easier.

The only problem this introduces is that the Alexa Gateway requires a HTTPS endpoint with a proper certificate. A Flask-Ask tutorial shows how to use Ngrok to create a tunnel from https://ngrok.io to a server running behind a NAT router. This makes it a great way to get started developing a skill.

As I have Polestar IoT Hub running at home so I was very quickly able to flesh out three intents which forward requests to webhooks:

Here is the code:

import logging
import os
import urllib2
import ssl as myssl

from flask import Flask
from flask_ask import Ask, request, session, question, statement

polestarURL="http://127.0.0.1:8080/polestar/"

app = Flask(__name__)
ask = Ask(app, "/")
logging.getLogger('flask_ask').setLevel(logging.DEBUG)


def requestForString(url):
    resp=urllib2.urlopen(url)
    s=resp.read().decode("utf-8")
    return s

@ask.launch
def launch():
    speech_text = 'Welcome to the old schoolhouse!'
    errors=requestForString(polestarURL+"scripts/webhook/93DE573093349054")
    speech_text = 'Welcome to the old schoolhouse. '+errors
    
    return question(speech_text)


@ask.intent('HelloWorldIntent')
def hello_world():

    errors=requestForString(polestarURL+"scripts/webhook/93DE573093349054")
    speech_text = 'Welcome to the old schoolhouse. '+errors
    
    return statement(speech_text)

@ask.intent('AquariumIntent')
def aquariumMode(Mode):
    temp=requestForString(polestarURL+"scripts/webhook/2640F070620F2946?mode="+Mode)
    return question(temp)

@ask.intent('TemperatureIntent')
def getTemperature(Device):
    temp=requestForString(polestarURL+"scripts/webhook/422326BC0D929DB9?device="+Device)
    return question(temp)

@ask.intent('HeatingOnIntent')
def switchOnHeating():
    requestForString(polestarURL+"scripts/webhook/EF41D8A79445188E?remote")
    return question("Central heating turned on.")


@ask.intent('AMAZON.CancelIntent')
def cancel():
    return statement("")

@ask.intent('AMAZON.StopIntent')
def cancel():
    return statement("")

@ask.session_ended
def session_ended():
    return "{}", 200

if __name__ == '__main__':
    if 'ASK_VERIFY_REQUESTS' in os.environ:
        verify = str(os.environ.get('ASK_VERIFY_REQUESTS', '')).lower()
        if verify == 'false':
            app.config['ASK_VERIFY_REQUESTS'] = False
    app.run(debug=True,host='0.0.0.0')

and this is my intent schema:

{
  "intents": [
    {
      "slots": [
        {
          "name": "Device",
          "type": "LIST_OF_DEVICES"
        }
      ],
      "intent": "TemperatureIntent"
    },
    {
      "slots": [
        {
          "name": "Mode",
          "type": "AQUARIUM_MODE"
        }
      ],
      "intent": "AquariumIntent"
    },
    {
      "intent": "HeatingOnIntent"
    },
    {
      "intent": "AMAZON.HelpIntent"
    },
    {
      "intent": "AMAZON.StopIntent"
    },
    {
      "intent": "AMAZON.CancelIntent"
    }
  ]
}

and these are my sample utterances:

TemperatureIntent what is the temperature {Device}
TemperatureIntent what is the temperature of {Device}
TemperatureIntent what is the temperature of the {Device}
TemperatureIntent what the temperature of {Device} is
TemperatureIntent what the temperature of the {Device} is
TemperatureIntent how hot is the {Device}
TemperatureIntent how hot is it {Device}
TemperatureIntent how hot is it in {Device}
TemperatureIntent how hot is it in the {Device}
TemperatureIntent how cold is the {Device}
TemperatureIntent how cold is it {Device}
TemperatureIntent how cold is it in {Device}
TemperatureIntent how cold is it in the {Device}
AquariumIntent aquarium {Mode}
AquariumIntent set aquarium {Mode}
AquariumIntent set aquarium to {Mode}
AquariumIntent {Mode} aquarium
HeatingOnIntent turn heating on
HeatingOnIntent switch heating on
HeatingOnIntent turn on heating
HeatingOnIntent switch on heating
HeatingOnIntent turn central heating on
HeatingOnIntent switch on central heating

Pretty simple!