Data-Dive

Get the device location of Alexa for your custom skill

· mc51

Alexa is Amazon’s voice controlled assistant. It can do really cool things for you. But the best part about it is that creating your own Alexa skills with Python is simple and fun. In a previous post I wrote about that. Recently, it became even more fun. On April 5th 2017, a much anticipated feature has been added to Alexa. Now, it is possible for developers to retrieve the address of a device. This opens up many possibilities for new applications. In the following, I will describe how to use the new address API. In addition, I will show you how to get the corresponding geo-coordinates for the address so that you can start building cool features right away.

Steps for retrieving the device’s address

You should start off reading the official Amazon documentation of the new API. In short, the process can be summed up like this:

  1. Configure your skill in the amazon developer portal to request the permission for obtaining the user device’s address. You find this setting under the Configuration tab of your skill:
Developer Portal setting for requesting permission
Figure 1. Developer Portal setting for requesting permission
  1. If a customer has allowed your skill to obtain address data, the request sent to your skill will contain additional information. The request looks like this. You will need to grab the values of deviceId and consentToken. These are nested inside the context object. Notice that the consentToken field will be empty, if the permission has not been granted.

  2. With those two pieces of information you can now request the address data of the device. You need to send a HTTP GET request to either one of the endpoints /v1/devices/*deviceId*/settings/address/countryAndPostalCode or /v1/devices/*deviceId*/settings/address depending on which type of address you need and permission you have configured. The base URI can either be https://api.amazonalexa.com/ or https://api.eu.amazonalexa.com. Take the one closer to where your skill is hosted. (Both hosts work for me, so I guess it’s just a matter of latency)

  3. The request in step three has to follow a specific format. Above the implementation of the deviceId in the URI, you also have to change the request’s header. The constentToken retrieved in step two has to be included in it. The resulting request should look like this:

Host: api.amazonalexa.com
Accept: application/json
Authorization: Bearer Atc|MQEWY...6fnLok
GET https://api.amazonalexa.com/v1/devices/{deviceId}/settings/address/countryAndPostalCode

A response to a successful request will look like this:

Host: api.amazonalexa.com
X-Amzn-RequestId: xxxx-xxx-xxx
Content-Type: application/json
{
    "stateOrRegion" : "WA",
    "city" : "Seattle",
    "countryCode" : "US",
    "postalCode" : "98109",
    "addressLine1" : "410 Terry Ave North",
    "addressLine2" : "",
    "addressLine3" : "aeiou",
    "districtOrCounty" : ""
}

Notice that Alexa does not have GPS. Hence, the address returned will be the one entered by the user. If the user didn’t enter an address, Alexa will take one from the Amazon account associated with the device.

Example code for a skill that returns the device address

After crossing off the first step in the list above, the following code takes care of the rest:

import logging
import requests
from flask import Flask
from flask_ask import Ask, statement, context

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

def get_alexa_location():
    URL =  "https://api.amazonalexa.com/v1/devices/{}/settings" \
            "/address".format(context.System.device.deviceId)
    TOKEN =  context.System.user.permissions.consentToken
    HEADER = {'Accept': 'application/json',
                'Authorization': 'Bearer {}'.format(TOKEN)}
    r = requests.get(URL, headers=HEADER)
    if r.status_code == 200:
        return(r.json())

@ask.launch
def launch():
    return start()

@ask.intent("WhatIsMyLocation")
def start():
    location = get_alexa_location()
    city = "Your City is {}! ".format(location["city"].encode("utf-8"))    
    address = "Your address is {}! ".format(location["addressLine1"].encode("utf-8")) 
    speech = city + address   
    return statement(speech)

if __name__ == '__main__':
    app.run(debug=True)

If you have already used Flask-Ask, the code above should be mostly clear. One exception being the line from flask_ask import context. The context object is not yet (as of 2017-04-15) documented in Flask-Ask. However, it is analogous to the request, session and version objects that are described here. It refers to the context object in the JSON request that you receive from Alexa (see point 2. in the previous section). Thus, we can obtain the context.System.device.deviceId and context.System.user.permissions.consentToken objects just like this. With them, we construct the URL and the HEADER string to use for the request. The response to the request will look like this:

{u'city': u'Berlin', u'countryCode': u'DE', u'addressLine1': u'42 Unter den Linden',
u'addressLine2': None, u'stateOrRegion': u'Berlin',
u'districtOrCounty': u'Berlin', u'postalCode': u'10117', u'addressLine3': None}

Please notice, that the code provided is a minimal example. In production, you should check whether consentToken is empty. Additionally, you should implement a fall-back option for cases where the user does not share his address.

Converting the address to location coordinates

In many cases, the address alone will not be very helpful for your skill. You might rather need the corresponding coordinates. With those, you can search for objects in the same area for example. Here is how to do it using the handy geopy library. First, install the library in your (virtual) environment:

(venv) flopp@falcon:~/Coding/alexa/repo$ pip install geopy
Collecting geopy
Installing collected packages: geopy
Successfully installed geopy-1.11.0

Next, make sure to import geopy in your code:

from geopy.geocoders import Nominatim

Then, let this function take care of the conversion from address to coordinates:

def get_coordinates(location):
    geolocator = Nominatim()    # Set provider of geo-data 
    address = "{}, {}".format(location["addressLine1"].encode("utf-8"),
                                location["city"].encode("utf-8"))
    coordinates = geolocator.geocode(address)
    print(address)
    print(coordinates.latitude, coordinates.longitude)

For my example above, this is the result:

(venv) flopp@falcon:~/Coding/alexa/repo$ python geo.py 
42 Unter den Linden, Berlin
(52.517102, 13.3859461)

Entering these coordinates in Google Maps takes me to this location:

The coordinates returned by geopy resolve back to the original address
Figure 2. The coordinates returned by geopy resolve back to the original address

In this case, the conversion has worked flawlessly. With the device’s coordinates at hand you have numerous possibilities to build amazing new skills. Use your knowledge and create something exciting!