Amazon ALEXA Lists Exercise – Put’em on Paper…
4. Solution Components
Let’s look at each of the components in detail. You may want to create the items in the same order as we did here, particularly with the ALEXA stuff.
4.1 Amazon AWS Lambda glue code
To understand the code, you’d have to make yourself familiar with the following resources:
- ALEXA request and response JSON reference: the request object is passed in python lambda_handler’s „event“ variable, ALEXA custom skills expect the generated response object as python dictionary return ed.
- ALEXA shopping and to-do lists REST API documentation: this page describes REST methods to read and manipulate both the built-in shopping list and the to-do list of an Amazon Account.
And yes, we’re using plain old python spaghetti code (POPSCTM) – no Flask-Ask or similar. After all, this is supposed to be an excercise…
4.1.1 Source Code
from urllib.request import * from urllib.error import HTTPError from urllib.parse import urlencode, quote_plus, quote import json, sys, ssl, socket def lambda_handler(event, context): # ****************************** # *** Do we have a valid event? # ****************************** if not "request" in event: # wrong event passed or not a dict. return { 'version': '1.0', 'response': { 'outputSpeech': { 'type': 'PlainText', 'text': "Sorry, I didn't get your request." } }, "shouldEndSession": True } if event["request"].get("type", '')!='IntentRequest' and event["request"].get("type", '')!='LaunchRequest': # we don't have an intent here return { 'version': '1.0', 'response': { 'outputSpeech': { 'type': 'PlainText', 'text': "Sorry, I didn't get your request." } }, "shouldEndSession": True } # ****************************** # *** Do we have a consent-token? If not->user must approve in APP # ****************************** if not "context" in event: # This should not happen return { 'version': '1.0', 'response': { 'outputSpeech': { 'type': 'PlainText', 'text': "Sorry, I didn't get your request." } }, "shouldEndSession": True } apiEndPoint = event["context"].get("System", {}).get("apiEndpoint", "") apiAccessToken = event["context"].get("System", {}).get("apiAccessToken", "") if apiEndPoint=="" or apiAccessToken=="": # User must grant access to this skill in their Alexa App return { 'version': '1.0', 'response': { 'outputSpeech': { 'type': 'PlainText', 'text': "Please open your ALEXA app and grant both read and write access on your lists to this skill." } }, "shouldEndSession": True } # Determine which one of the two lists the user wanted to print, default=shopping list liste = "shopping list" # this is intended for the German voice interface, obviously a Q&D hack; you'd need to work with IDs to make it language independent if event["request"].get("intent",{}).get("slots",{}).get("listName",{}).get("value",'')=='aufgabenliste' or event["request"].get("intent",{}).get("slots",{}).get("listName",{}).get("value",'')=='aufgabenzettel': # ok, it's the TODO list liste = "todo list" # ****************************** # **** TRY TO LOAD ALEXA LISTS # ****************************** try: req = Request(apiEndPoint+"/v2/householdlists/", data=None, headers={"Authorization": "Bearer "+apiAccessToken, "Content-Type": "json"}, method="GET") resp = urlopen(req) alexaListsJson = resp.read() except: # nah, alexa has a bad day today. return { 'version': '1.0', 'response': { 'outputSpeech': { 'type': 'PlainText', 'text': "Sorry, I could not access your lists." } }, "shouldEndSession": True } # ****************************** # **** TRY TO DECODE ALEXA LISTS # ****************************** try: alexaListsDict = json.loads(alexaListsJson) except: # nah, alexa has a bad day today. return { 'version': '1.0', 'response': { 'outputSpeech': { 'type': 'PlainText', 'text': "Sorry, I received an unexpected list format." } }, "shouldEndSession": True } # ****************************** # *** Fetch list item arguments # ****************************** for currentList in alexaListsDict["lists"]: listState = currentList["state"] listName = currentList["name"] listId = currentList["listId"] listUrl = "" # get 'active' list URL for listUrls in currentList["statusMap"]: if listUrls["status"]=="active": listUrl = listUrls.get("href", "") break if liste=="shopping list" and listName == "Alexa shopping list": break; if liste=="todo list" and listName == "Alexa to-do list": break; if listUrl=="": # Shoot, we don't have a URL for the selected list return { 'version': '1.0', 'response': { 'outputSpeech': { 'type': 'PlainText', 'text': "Sorry, I received an unexpected list format." } }, "shouldEndSession": True } # ok, we now have the correct URL for the particular list listUrl = apiEndPoint + listUrl # ****************************** # *** Fetch list items arguments # ****************************** try: req = Request(listUrl, data=None, headers={"Authorization": "Bearer "+apiAccessToken, "Content-Type": "json"}, method="GET") resp = urlopen(req) alexaListItemsJson = resp.read() except: # We could not retrieve the list's items return { 'version': '1.0', 'response': { 'outputSpeech': { 'type': 'PlainText', 'text': "Sorry, I could not retrieve your "+liste+"." } }, "shouldEndSession": True } try: alexaListItemsDict = json.loads(alexaListItemsJson) except: # item payload has an unexpected format return { 'version': '1.0', 'response': { 'outputSpeech': { 'type': 'PlainText', 'text': "Sorry, your "+liste+" has an unexpected format." } }, "shouldEndSession": True } alexaListItemsList = [] for listItem in alexaListItemsDict["items"]: if listItem.get("value",'')=="": continue alexaListItemsList.append(listItem.get("value",'')) if len(alexaListItemsList) == 0: return { 'version': '1.0', 'response': { 'outputSpeech': { 'type': 'PlainText', 'text': "Sorry, your "+liste+" is empty." } }, "shouldEndSession": True } # ****************************** # *** Print the list items # ****************************** secret = "1234567890__REPLACE__WITH__YOUR__SECRET__1234567890" # obviously, URL-compatible characters only. Or url-encode / quote_plus() it! # ignore self-signed certs ctx = ssl.create_default_context() ctx.check_hostname = False ctx.verify_mode = ssl.CERT_NONE # Encode stuff listeEncoded = quote_plus(liste) listItemsEncoded = quote_plus(json.dumps(alexaListItemsList)) # start the printer... urlString = "https://!!YOURHOSTHERE!!/edge_script_printer.php?listName="+ listeEncoded +"&listItems="+ listItemsEncoded +"&secret="+secret try: req = Request(urlString, data=None, headers={"Content-Type": "json"}, method="GET") resp = urlopen(req, timeout=5, context=ctx) except HTTPError as error: # received an error from EDGE server return { 'version': '1.0', 'response': { 'outputSpeech': { 'type': 'PlainText', 'text': "Sorry, service error at printer service." } }, "shouldEndSession": True } except: # could not connect to EDGE server return { 'version': '1.0', 'response': { 'outputSpeech': { 'type': 'PlainText', 'text': "Sorry, connection error to printer service." } }, "shouldEndSession": True } # ****************************** # *** Clear the respective list # ****************************** if event["request"].get("intent",{}).get("slots",{}).get("deleteFlag",{}).get("value",'')!='': # do not delete the list return { 'version': '1.0', 'response': { 'outputSpeech': { 'type': 'PlainText', 'text': "I printed out your "+liste+"." } }, "shouldEndSession": True } error=False for listItem in alexaListItemsDict["items"]: itemUrl = apiEndPoint + listItem.get("href", "") try: req = Request(itemUrl, data=None, headers={"Authorization": "Bearer "+apiAccessToken, "Content-Type": "application/json"}, method="DELETE") resp = urlopen(req) except: print (sys.exc_info()) print (listItem) error=True if error==True: # at least one item could not be deleted return { 'version': '1.0', 'response': { 'outputSpeech': { 'type': 'PlainText', 'text': "I printed out your "+liste+", but there was an error clearing it." } }, "shouldEndSession": True } # all printed, list emptied! return { 'version': '1.0', 'response': { 'outputSpeech': { 'type': 'PlainText', 'text': "I printed out and cleared your "+liste+"." } }, "shouldEndSession": True }
Nothing fancy in there, but do note that you’re required to change two things:
- line 103: generate your own secret here.
- line 113: replace with your own DynDNS host name.
4.1.2 Creating the Lambda function
In order to create the lambda function:
- log into your Amazon developer console,
- navigate to AWS,
- find the „Lambda“ button,
- create a new lambda function from scratch named printer-glue-code with lambda_basic_execution role running on python 3.6; it can remain named lambda_function.lambda_handler .
- Then, paste the code from above and save the function.
- Also, you should set the timeout to 20 seconds under „Basic Settings“.
Now, add ALEXA as allowed calling source:
- Switch to the tab „Triggers“ (current one is called „configuration“) and press „Add Trigger“
- In the grey box, select „ALEXA Skills Kit“ and press „Submit“
- Never mind the error, just close the box by pressing „cancel“.
- Upon refreshing the triggers, you should see the ALEXA Skills Kit trigger
Lastly, take note of the ARN in the upper right corner:
You can press the „Test“ button and use the default event. It should come up with a green success message.
5 Kommentare zu “Amazon ALEXA Lists Exercise – Put’em on Paper…”
Hi,
great post.
Could please tell which thermal printer you have used?
The amazon link isnt‘ working anymore.
Thanks
hi, Thanks. The one I used isn’t available on Amazon anymore, but any USB receipt printer should work… Just look for what mike24’s ESC/POS library is supporting and buy one of those models… Best, Christoph
Thanks for the reply. This product list on github helps a lot.
One further question: Could you really delete the original shopping list with the skill? In your video you use the original „Einkaufsliste“ so you could also delete this one per API? In your code for the lambda it is „shopping list“ and not the original code snipped?
Danke und Gruß
Tom
hi, well, the Einkaufsliste is used by the ALEXA language model. it translates into „shopping list“ in the skill. Therefore the difference… Cheers.
Hello!
Nice work ! I try to integrate it to home assistant .
Your video link is dead could you update it please?
regards
Ben
Einen Kommentar hinterlassen