Skip to content

Commit 42b2a6c

Browse files
committed
initial commit
1 parent 5d93310 commit 42b2a6c

File tree

111 files changed

+13490
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

111 files changed

+13490
-0
lines changed

Diff for: .gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
2+
sample_lambda/python/python.zip

Diff for: LICENSE.txt

+96
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
Amazon Software License 1.0
2+
3+
This Amazon Software License ("License") governs your use, reproduction, and
4+
distribution of the accompanying software as specified below.
5+
6+
1. Definitions
7+
8+
"Licensor" means any person or entity that distributes its Work.
9+
10+
"Software" means the original work of authorship made available under this
11+
License.
12+
13+
"Work" means the Software and any additions to or derivative works of the
14+
Software that are made available under this License.
15+
16+
The terms "reproduce," "reproduction," "derivative works," and
17+
"distribution" have the meaning as provided under U.S. copyright law;
18+
provided, however, that for the purposes of this License, derivative works
19+
shall not include works that remain separable from, or merely link (or bind
20+
by name) to the interfaces of, the Work.
21+
22+
Works, including the Software, are "made available" under this License by
23+
including in or with the Work either (a) a copyright notice referencing the
24+
applicability of this License to the Work, or (b) a copy of this License.
25+
26+
2. License Grants
27+
28+
2.1 Copyright Grant. Subject to the terms and conditions of this License,
29+
each Licensor grants to you a perpetual, worldwide, non-exclusive,
30+
royalty-free, copyright license to reproduce, prepare derivative works of,
31+
publicly display, publicly perform, sublicense and distribute its Work and
32+
any resulting derivative works in any form.
33+
34+
2.2 Patent Grant. Subject to the terms and conditions of this License, each
35+
Licensor grants to you a perpetual, worldwide, non-exclusive, royalty-free
36+
patent license to make, have made, use, sell, offer for sale, import, and
37+
otherwise transfer its Work, in whole or in part. The foregoing license
38+
applies only to the patent claims licensable by Licensor that would be
39+
infringed by Licensor's Work (or portion thereof) individually and
40+
excluding any combinations with any other materials or technology.
41+
42+
3. Limitations
43+
44+
3.1 Redistribution. You may reproduce or distribute the Work only if
45+
(a) you do so under this License, (b) you include a complete copy of this
46+
License with your distribution, and (c) you retain without modification
47+
any copyright, patent, trademark, or attribution notices that are present
48+
in the Work.
49+
50+
3.2 Derivative Works. You may specify that additional or different terms
51+
apply to the use, reproduction, and distribution of your derivative works
52+
of the Work ("Your Terms") only if (a) Your Terms provide that the use
53+
limitation in Section 3.3 applies to your derivative works, and (b) you
54+
identify the specific derivative works that are subject to Your Terms.
55+
Notwithstanding Your Terms, this License (including the redistribution
56+
requirements in Section 3.1) will continue to apply to the Work itself.
57+
58+
3.3 Use Limitation. The Work and any derivative works thereof only may be
59+
used or intended for use with the web services, computing platforms or
60+
applications provided by Amazon.com, Inc. or its affiliates, including
61+
Amazon Web Services, Inc.
62+
63+
3.4 Patent Claims. If you bring or threaten to bring a patent claim against
64+
any Licensor (including any claim, cross-claim or counterclaim in a
65+
lawsuit) to enforce any patents that you allege are infringed by any Work,
66+
then your rights under this License from such Licensor (including the
67+
grants in Sections 2.1 and 2.2) will terminate immediately.
68+
69+
3.5 Trademarks. This License does not grant any rights to use any
70+
Licensor's or its affiliates' names, logos, or trademarks, except as
71+
necessary to reproduce the notices described in this License.
72+
73+
3.6 Termination. If you violate any term of this License, then your rights
74+
under this License (including the grants in Sections 2.1 and 2.2) will
75+
terminate immediately.
76+
77+
4. Disclaimer of Warranty.
78+
79+
THE WORK IS PROVIDED "AS IS" WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
80+
EITHER EXPRESS OR IMPLIED, INCLUDING WARRANTIES OR CONDITIONS OF
81+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE OR
82+
NON-INFRINGEMENT. YOU BEAR THE RISK OF UNDERTAKING ANY ACTIVITIES UNDER
83+
THIS LICENSE. SOME STATES' CONSUMER LAWS DO NOT ALLOW EXCLUSION OF AN
84+
IMPLIED WARRANTY, SO THIS DISCLAIMER MAY NOT APPLY TO YOU.
85+
86+
5. Limitation of Liability.
87+
88+
EXCEPT AS PROHIBITED BY APPLICABLE LAW, IN NO EVENT AND UNDER NO LEGAL
89+
THEORY, WHETHER IN TORT (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE
90+
SHALL ANY LICENSOR BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY DIRECT,
91+
INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR
92+
RELATED TO THIS LICENSE, THE USE OR INABILITY TO USE THE WORK (INCLUDING
93+
BUT NOT LIMITED TO LOSS OF GOODWILL, BUSINESS INTERRUPTION, LOST PROFITS
94+
OR DATA, COMPUTER FAILURE OR MALFUNCTION, OR ANY OTHER COMM ERCIAL DAMAGES
95+
OR LOSSES), EVEN IF THE LICENSOR HAS BEEN ADVISED OF THE POSSIBILITY OF
96+
SUCH DAMAGES.

Diff for: NOTICE.txt

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Alexa Smart Home Skill API PRIVATE Repository
2+
3+
Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.

Diff for: sample_async/python/sample_async.py

+233
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
# -*- coding: utf-8 -*-
2+
3+
# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.
4+
#
5+
# Licensed under the Amazon Software License (the "License"). You may not use this file except in
6+
# compliance with the License. A copy of the License is located at
7+
#
8+
# http://aws.amazon.com/asl/
9+
#
10+
# or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific
12+
# language governing permissions and limitations under the License.
13+
14+
"""Alexa Smart Home Asynchronous Messaging Sample Code.
15+
16+
This file demonstrates some key concepts when working with the Alexa Smart Home, as well as
17+
Login with Amazon (LWA) in order to establish authentication and authorization, so your skill
18+
can send proactive state updates and change reports to Alexa on behalf of the customer.
19+
20+
This sample stores access and refresh tokens in a file, and shows the flow for requesting a new
21+
access token and refreshing existing and/or expired access tokens. It also shows how to use a valid
22+
access token to send a proactive change or state report. You would ideally store access and
23+
refresh tokens in a more appropriate persistence like DynamoDB.
24+
25+
Basic usage of this file is as follows:
26+
27+
1. fill in the CLIENT_ID and CLIENT_SECRET constants
28+
2. with a user in the Alexa App, enable your skill, and receive an AcceptGrant directive
29+
3. get the auth code from that AcceptGrant directive, and fill in the CODE constant
30+
4. update main() with a change or state report that is appropriate for your user and skill
31+
5. run this file and see how it works for the first time
32+
6. change PREEMPTIVE_REFRESH_TTL_IN_SECONDS to a large number to force token refresh as needed
33+
"""
34+
35+
import logging
36+
import sys
37+
import time
38+
import datetime
39+
import json
40+
import uuid
41+
import os
42+
import requests
43+
44+
# constants
45+
UTC_FORMAT = "%Y-%m-%dT%H:%M:%S.00Z"
46+
LWA_TOKEN_URI = "https://api.amazon.com/auth/o2/token"
47+
LWA_HEADERS = {
48+
"Content-Type": "application/x-www-form-urlencoded;charset=UTF-8"
49+
}
50+
ALEXA_URI = "https://api.amazonalexa.com/v3/events" # update to appropriate URI for your region
51+
ALEXA_HEADERS = {
52+
"Content-Type": "application/json;charset=UTF-8"
53+
}
54+
55+
# setup logger
56+
logging.basicConfig(stream=sys.stdout)
57+
LOGGER = logging.getLogger()
58+
LOGGER.setLevel(logging.DEBUG)
59+
60+
# LWA constants
61+
CODE = "<code>" # auth code from AcceptGrant directive, update whenever you disable/enable the skill
62+
CLIENT_ID = "<client id>" # copy from Developer Console
63+
CLIENT_SECRET = "<client secret>" # copy from Developer Console
64+
PREEMPTIVE_REFRESH_TTL_IN_SECONDS = 300 # used to preemptively refresh access token if 5 mins from expiry
65+
66+
TOKEN_FILENAME = CODE + ".txt" # everytime a new auth code is used, we store tokens in a new file
67+
68+
# utility functions
69+
def get_utc_timestamp(seconds=None):
70+
return time.strftime(UTC_FORMAT, time.gmtime(seconds))
71+
72+
def get_utc_timestamp_from_string(string):
73+
return datetime.datetime.strptime(string, UTC_FORMAT)
74+
75+
def get_uuid():
76+
return str(uuid.uuid4())
77+
78+
# authentication functions
79+
def get_need_new_token():
80+
"""Checks whether the access token is missing or needed to be refreshed"""
81+
need_new_token_response = {
82+
"need_new_token": False,
83+
"access_token": "",
84+
"refresh_token": ""
85+
}
86+
87+
if os.path.isfile(TOKEN_FILENAME):
88+
# if token file exists, then we've already gotten the first access token for this user skill enablement
89+
with open(TOKEN_FILENAME, 'r') as infile:
90+
last_line = infile.readlines()[-1] # THIS IS TOTALLY INEFFICIENT
91+
92+
token = last_line.split("***")
93+
token_received_datetime = get_utc_timestamp_from_string(token[0])
94+
token_json = json.loads(token[1])
95+
token_expires_in = token_json["expires_in"] - PREEMPTIVE_REFRESH_TTL_IN_SECONDS
96+
token_expires_datetime = token_received_datetime + datetime.timedelta(seconds=token_expires_in)
97+
current_datetime = datetime.datetime.utcnow()
98+
99+
need_new_token_response["need_new_token"] = current_datetime > token_expires_datetime
100+
need_new_token_response["access_token"] = token_json["access_token"]
101+
need_new_token_response["refresh_token"] = token_json["refresh_token"]
102+
else:
103+
# else, we've never gotten an access token for this user skill enablement
104+
need_new_token_response["need_new_token"] = True
105+
106+
return need_new_token_response
107+
108+
def get_access_token():
109+
"""Performs access token or token refresh request as needed and returns valid access token"""
110+
111+
need_new_token_response = get_need_new_token()
112+
access_token = ""
113+
114+
if need_new_token_response["need_new_token"]:
115+
if os.path.isfile(TOKEN_FILENAME):
116+
# access token already retrieved the first time, so this should be a token refresh request
117+
with open(TOKEN_FILENAME, 'a') as outfile:
118+
outfile.write("\n")
119+
120+
lwa_params = {
121+
"grant_type" : "refresh_token",
122+
"refresh_token": need_new_token_response["refresh_token"],
123+
"client_id": CLIENT_ID,
124+
"client_secret": CLIENT_SECRET
125+
}
126+
LOGGER.debug("Calling LWA to refresh the access token...")
127+
else:
128+
# access token not retrieved yet for the first time, so this should be an access token request
129+
lwa_params = {
130+
"grant_type" : "authorization_code",
131+
"code": CODE,
132+
"client_id": CLIENT_ID,
133+
"client_secret": CLIENT_SECRET
134+
}
135+
LOGGER.debug("Calling LWA to get the access token for the first time...")
136+
LOGGER.debug("Params: " + json.dumps(lwa_params))
137+
138+
response = requests.post(LWA_TOKEN_URI, headers=LWA_HEADERS, data=lwa_params, allow_redirects=True)
139+
LOGGER.debug("LWA response header: " + format(response.headers))
140+
LOGGER.debug("LWA response status: " + format(response.status_code))
141+
LOGGER.debug("LWA response body : " + format(response.text))
142+
143+
if response.status_code != 200:
144+
LOGGER.debug("Error calling LWA!")
145+
return None
146+
147+
# store token in file
148+
token = get_utc_timestamp() + "***" + response.text
149+
with open(TOKEN_FILENAME, 'a') as outfile:
150+
outfile.write(token)
151+
152+
access_token = json.loads(response.text)["access_token"]
153+
else:
154+
LOGGER.debug("Latest access token has not expired, so using it and won't call LWA...")
155+
access_token = need_new_token_response["access_token"]
156+
157+
return access_token
158+
159+
def main():
160+
"""Main function that sends a proactive state or change report to Alexa"""
161+
162+
token = get_access_token()
163+
164+
if token:
165+
message_id = get_uuid()
166+
time_of_sample = get_utc_timestamp()
167+
168+
# ensure that this change or state report is appropriate for your user and skill
169+
alexa_params = {
170+
"context": {
171+
"properties": [{
172+
"namespace": "Alexa.BrightnessController",
173+
"name": "brightness",
174+
"value": 99,
175+
"timeOfSample": time_of_sample,
176+
"uncertaintyInMilliseconds": 500
177+
}, {
178+
"namespace": "Alexa.ColorController",
179+
"name": "color",
180+
"value": {
181+
"hue": 350.5,
182+
"saturation": 0.7138,
183+
"brightness": 0.6524
184+
},
185+
"timeOfSample": time_of_sample,
186+
"uncertaintyInMilliseconds": 500
187+
}, {
188+
"namespace": "Alexa.ColorTemperatureController",
189+
"name": "colorTemperatureInKelvin",
190+
"value": 7500,
191+
"timeOfSample": time_of_sample,
192+
"uncertaintyInMilliseconds": 500
193+
}]
194+
},
195+
"event": {
196+
"header": {
197+
"namespace": "Alexa",
198+
"name": "ChangeReport",
199+
"payloadVersion": "3",
200+
"messageId": message_id
201+
},
202+
"endpoint": {
203+
"scope": {
204+
"type": "BearerToken",
205+
"token": token
206+
},
207+
"endpointId": "appliance-002"
208+
},
209+
"payload": {
210+
"change": {
211+
"cause": {
212+
"type": "ALEXA_INTERACTION"
213+
}
214+
},
215+
"properties": [{
216+
"namespace": "Alexa.PowerController",
217+
"name": "powerState",
218+
"value": "ON",
219+
"timeOfSample": time_of_sample,
220+
"uncertaintyInMilliseconds": 500
221+
}]
222+
}
223+
}
224+
}
225+
226+
response = requests.post(ALEXA_URI, headers=ALEXA_HEADERS, data=json.dumps(alexa_params), allow_redirects=True)
227+
LOGGER.debug("Request data: " + json.dumps(alexa_params))
228+
LOGGER.debug("Alexa response header: " + format(response.headers))
229+
LOGGER.debug("Alexa response status: " + format(response.status_code))
230+
LOGGER.debug("Alexa response body : " + format(response.text))
231+
232+
if __name__ == "__main__":
233+
main()

Diff for: sample_lambda/python/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)