-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
11 changed files
with
355 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,5 @@ | ||
instance | ||
.vscode | ||
gunicorn_config.py | ||
gunicorn_config.py | ||
function.zip | ||
lambda_function/package |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
[[source]] | ||
name = "pypi" | ||
url = "https://pypi.org/simple" | ||
verify_ssl = true | ||
|
||
[dev-packages] | ||
|
||
[packages] | ||
|
||
[requires] | ||
python_version = "3.7" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
# -*- coding: utf-8 -*- | ||
|
||
# Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
# | ||
# Licensed under the Amazon Software License (the "License"). You may not use this file except in | ||
# compliance with the License. A copy of the License is located at | ||
# | ||
# http://aws.amazon.com/asl/ | ||
# | ||
# or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific | ||
# language governing permissions and limitations under the License. | ||
|
||
from .alexa_response import AlexaResponse | ||
from .alexa_utils import get_utc_timestamp |
152 changes: 152 additions & 0 deletions
152
lambda_function/alexa/skills/smarthome/alexa_response.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,152 @@ | ||
# -*- coding: utf-8 -*- | ||
|
||
# Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
# | ||
# Licensed under the Amazon Software License (the "License"). You may not use this file except in | ||
# compliance with the License. A copy of the License is located at | ||
# | ||
# http://aws.amazon.com/asl/ | ||
# | ||
# or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific | ||
# language governing permissions and limitations under the License. | ||
|
||
import random | ||
import uuid | ||
|
||
from .alexa_utils import get_utc_timestamp | ||
|
||
|
||
class AlexaResponse: | ||
def __init__(self, **kwargs): | ||
|
||
self.context_properties = [] | ||
self.payload_endpoints = [] | ||
|
||
# Set up the response structure | ||
self.context = {} | ||
self.event = { | ||
"header": { | ||
"namespace": kwargs.get("namespace", "Alexa"), | ||
"name": kwargs.get("name", "Response"), | ||
"messageId": str(uuid.uuid4()), | ||
"payloadVersion": kwargs.get("payload_version", "3") | ||
# 'correlation_token': kwargs.get('correlation_token', 'INVALID') | ||
}, | ||
"endpoint": { | ||
"scope": { | ||
"type": "BearerToken", | ||
"token": kwargs.get("token", "INVALID"), | ||
}, | ||
"endpointId": kwargs.get("endpoint_id", "INVALID"), | ||
}, | ||
"payload": kwargs.get("payload", {}), | ||
} | ||
|
||
if "correlation_token" in kwargs: | ||
self.event["header"]["correlation_token"] = kwargs.get( | ||
"correlation_token", "INVALID" | ||
) | ||
|
||
if "cookie" in kwargs: | ||
self.event["endpoint"]["cookie"] = kwargs.get("cookie", "{}") | ||
|
||
# No endpoint in an AcceptGrant or Discover request | ||
if ( | ||
self.event["header"]["name"] == "AcceptGrant.Response" | ||
or self.event["header"]["name"] == "Discover.Response" | ||
): | ||
self.event.pop("endpoint") | ||
|
||
def add_context_property(self, **kwargs): | ||
self.context_properties.append(self.create_context_property(**kwargs)) | ||
|
||
def add_cookie(self, key, value): | ||
|
||
if "cookies" in self is None: | ||
self.cookies = {} | ||
|
||
self.cookies[key] = value | ||
|
||
def add_payload_endpoint(self, **kwargs): | ||
self.payload_endpoints.append(self.create_payload_endpoint(**kwargs)) | ||
|
||
def create_context_property(self, **kwargs): | ||
return { | ||
"namespace": kwargs.get("namespace", "Alexa.EndpointHealth"), | ||
"name": kwargs.get("name", "connectivity"), | ||
"value": kwargs.get("value", {"value": "OK"}), | ||
"timeOfSample": get_utc_timestamp(), | ||
"uncertaintyInMilliseconds": kwargs.get("uncertainty_in_milliseconds", 0), | ||
} | ||
|
||
def create_payload_endpoint(self, **kwargs): | ||
# Return the proper structure expected for the endpoint | ||
endpoint = { | ||
"capabilities": kwargs.get("capabilities", []), | ||
"description": kwargs.get("description", "Sample Endpoint Description"), | ||
"displayCategories": kwargs.get("display_categories", ["OTHER"]), | ||
"endpointId": kwargs.get( | ||
"endpoint_id", "endpoint_" + "%0.6d" % random.randint(0, 999999) | ||
), | ||
"friendlyName": kwargs.get("friendly_name", "Sample Endpoint"), | ||
"manufacturerName": kwargs.get("manufacturer_name", "Sample Manufacturer"), | ||
} | ||
|
||
if "cookie" in kwargs: | ||
endpoint["cookie"] = kwargs.get("cookie", {}) | ||
|
||
return endpoint | ||
|
||
def create_payload_endpoint_capability(self, **kwargs): | ||
capability = { | ||
"type": kwargs.get("type", "AlexaInterface"), | ||
"interface": kwargs.get("interface", "Alexa"), | ||
"version": kwargs.get("version", "3"), | ||
} | ||
instance = kwargs.get("instance", None) | ||
if instance: | ||
capability["instance"] = instance | ||
supported = kwargs.get("supported", None) | ||
if supported: | ||
capability["properties"] = {} | ||
capability["properties"]["supported"] = supported | ||
capability["properties"]["proactivelyReported"] = kwargs.get( | ||
"proactively_reported", False | ||
) | ||
capability["properties"]["retrievable"] = kwargs.get("retrievable", False) | ||
semantics = kwargs.get("semantics", None) | ||
if semantics: | ||
capability["semantics"] = semantics | ||
capability_resources = kwargs.get("capability_resources", None) | ||
if capability_resources: | ||
capability["capabilityResources"] = capability_resources | ||
return capability | ||
|
||
def get(self, remove_empty=True): | ||
|
||
response = {"context": self.context, "event": self.event} | ||
|
||
if len(self.context_properties) > 0: | ||
response["context"]["properties"] = self.context_properties | ||
|
||
if len(self.payload_endpoints) > 0: | ||
response["event"]["payload"]["endpoints"] = self.payload_endpoints | ||
|
||
if remove_empty: | ||
if len(response["context"]) < 1: | ||
response.pop("context") | ||
|
||
return response | ||
|
||
def set_payload(self, payload): | ||
self.event["payload"] = payload | ||
|
||
def set_payload_endpoint(self, payload_endpoints): | ||
self.payload_endpoints = payload_endpoints | ||
|
||
def set_payload_endpoints(self, payload_endpoints): | ||
if "endpoints" not in self.event["payload"]: | ||
self.event["payload"]["endpoints"] = [] | ||
|
||
self.event["payload"]["endpoints"] = payload_endpoints |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
# -*- coding: utf-8 -*- | ||
|
||
# Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
# | ||
# Licensed under the Amazon Software License (the "License"). You may not use this file except in | ||
# compliance with the License. A copy of the License is located at | ||
# | ||
# http://aws.amazon.com/asl/ | ||
# | ||
# or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific | ||
# language governing permissions and limitations under the License. | ||
|
||
import time | ||
|
||
|
||
def get_utc_timestamp(seconds=None): | ||
return time.strftime('%Y-%m-%dT%H:%M:%S.00Z', time.gmtime(seconds)) | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,143 @@ | ||
# -*- coding: utf-8 -*- | ||
from alexa.skills.smarthome import AlexaResponse | ||
|
||
import boto3 | ||
import json | ||
import requests | ||
import os | ||
|
||
api_endpoint = os.environ["API_ENDPOINT"] | ||
|
||
|
||
def lambda_handler(request, context): | ||
|
||
# Dump the request for logging - check the CloudWatch logs | ||
print("lambda_handler request -----") | ||
print(json.dumps(request)) | ||
|
||
if context is not None: | ||
print("lambda_handler context -----") | ||
print(context) | ||
|
||
# Validate we have an Alexa directive | ||
if "directive" not in request: | ||
aer = AlexaResponse( | ||
name="ErrorResponse", | ||
payload={ | ||
"type": "INVALID_DIRECTIVE", | ||
"message": "Missing key: directive, Is the request a valid Alexa Directive?", | ||
}, | ||
) | ||
return send_response(aer.get()) | ||
|
||
# Check the payload version | ||
payload_version = request["directive"]["header"]["payloadVersion"] | ||
if payload_version != "3": | ||
aer = AlexaResponse( | ||
name="ErrorResponse", | ||
payload={ | ||
"type": "INTERNAL_ERROR", | ||
"message": "This skill only supports Smart Home API version 3", | ||
}, | ||
) | ||
return send_response(aer.get()) | ||
|
||
# Crack open the request and see what is being requested | ||
name = request["directive"]["header"]["name"] | ||
namespace = request["directive"]["header"]["namespace"] | ||
|
||
# Handle the incoming request from Alexa based on the namespace | ||
|
||
if namespace == "Alexa.Authorization": | ||
if name == "AcceptGrant": | ||
# Note: This sample accepts any grant request | ||
# In your implementation you would use the code and token to get and store access tokens | ||
grant_code = request["directive"]["payload"]["grant"]["code"] | ||
grantee_token = request["directive"]["payload"]["grantee"]["token"] | ||
aar = AlexaResponse( | ||
namespace="Alexa.Authorization", name="AcceptGrant.Response" | ||
) | ||
return send_response(aar.get()) | ||
|
||
if namespace == "Alexa.Discovery": | ||
if name == "Discover": | ||
res = requests.get("{}/blinds".format(api_endpoint)) | ||
|
||
adr = AlexaResponse(namespace="Alexa.Discovery", name="Discover.Response") | ||
capability_alexa = adr.create_payload_endpoint_capability() | ||
capability_alexa_togglecontroller = adr.create_payload_endpoint_capability( | ||
interface="Alexa.ToggleController", | ||
supported=[{"name": "toggleState"}], | ||
instance="Blind.BlindState", | ||
capability_resources={ | ||
"friendlyNames": [ | ||
{ | ||
"@type": "text", | ||
"value": {"text": "tapparella", "locale": "it-IT"}, | ||
} | ||
] | ||
}, | ||
semantics={ | ||
"actionMappings": [ | ||
{ | ||
"@type": "ActionsToDirective", | ||
"actions": ["Alexa.Actions.Close"], | ||
"directive": {"name": "TurnOff", "payload": {}}, | ||
}, | ||
{ | ||
"@type": "ActionsToDirective", | ||
"actions": ["Alexa.Actions.Open"], | ||
"directive": {"name": "TurnOn", "payload": {}}, | ||
}, | ||
], | ||
"stateMappings": [ | ||
{ | ||
"@type": "StatesToValue", | ||
"states": ["Alexa.States.Closed"], | ||
"value": "OFF", | ||
}, | ||
{ | ||
"@type": "StatesToValue", | ||
"states": ["Alexa.States.Open"], | ||
"value": "ON", | ||
}, | ||
], | ||
}, | ||
) | ||
|
||
for blind in res.json(): | ||
adr.add_payload_endpoint( | ||
friendly_name=blind.get("name"), | ||
endpoint_id=blind.get("id"), | ||
description="Remote controlled blind", | ||
capabilities=[capability_alexa, capability_alexa_togglecontroller], | ||
display_categories=["INTERIOR_BLIND"], | ||
) | ||
return send_response(adr.get()) | ||
|
||
if namespace == "Alexa.ToggleController": | ||
# Note: This sample always returns a success response for either a request to TurnOff or TurnOn | ||
device_id = request["directive"]["endpoint"]["endpointId"] | ||
action = "close" if name == "TurnOff" else "open" | ||
print(">>>>> NAME: {}".format(name)) | ||
print(request) | ||
res = requests.get( | ||
"{api_endpoint}/roller/{device_id}/{action}".format( | ||
api_endpoint=api_endpoint, device_id=device_id, action=action | ||
) | ||
) | ||
correlation_token = request["directive"]["header"]["correlationToken"] | ||
|
||
apcr = AlexaResponse(correlation_token=correlation_token) | ||
apcr.add_context_property( | ||
namespace="Alexa.ToggleController", name="toggleState", value="ciccio", | ||
) | ||
return send_response(apcr.get()) | ||
|
||
|
||
def send_response(response): | ||
# TODO Validate the response | ||
print("lambda_handler response -----") | ||
print(json.dumps(response)) | ||
return response | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
requests |