-
Notifications
You must be signed in to change notification settings - Fork 54
/
lambda_function.py
228 lines (177 loc) · 8.47 KB
/
lambda_function.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
import boto3
import json
import random
import uuid
import time
def get_utc_timestamp(seconds=None):
return time.strftime('%Y-%m-%dT%H:%M:%S.00Z', time.gmtime(seconds))
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')
}
supported = kwargs.get('supported', None)
if supported:
capability['properties'] = {}
capability['properties']['supported'] = supported
capability['properties']['proactivelyReported'] = kwargs.get('proactively_reported', True)
capability['properties']['retrievable'] = kwargs.get('retrievable', True)
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
aws_dynamodb = boto3.client('dynamodb')
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':
adr = AlexaResponse(namespace='Alexa.Discovery', name='Discover.Response')
capability_alexa = adr.create_payload_endpoint_capability()
capability_alexa_powercontroller = adr.create_payload_endpoint_capability(
interface='Alexa.PowerController',
supported=[{'name': 'powerState'}])
adr.add_payload_endpoint(
friendly_name='Sample Switch',
endpoint_id='sample-switch-01',
capabilities=[capability_alexa, capability_alexa_powercontroller])
return send_response(adr.get())
if namespace == 'Alexa.PowerController':
# Note: This sample always returns a success response for either a request to TurnOff or TurnOn
endpoint_id = request['directive']['endpoint']['endpointId']
power_state_value = 'OFF' if name == 'TurnOff' else 'ON'
correlation_token = request['directive']['header']['correlationToken']
# Check for an error when setting the state
state_set = set_device_state(endpoint_id=endpoint_id, state='powerState', value=power_state_value)
if not state_set:
return AlexaResponse(
name='ErrorResponse',
payload={'type': 'ENDPOINT_UNREACHABLE', 'message': 'Unable to reach endpoint database.'}).get()
apcr = AlexaResponse(correlation_token=correlation_token)
apcr.add_context_property(namespace='Alexa.PowerController', name='powerState', value=power_state_value)
return send_response(apcr.get())
def send_response(response):
# TODO Validate the response
print('lambda_handler response -----')
print(json.dumps(response))
return response
def set_device_state(endpoint_id, state, value):
attribute_key = state + 'Value'
response = aws_dynamodb.update_item(
TableName='SmartHome',
Key={'ItemId': {'S': endpoint_id}},
AttributeUpdates={attribute_key: {'Action': 'PUT', 'Value': {'S': value}}})
print(response)
if response['ResponseMetadata']['HTTPStatusCode'] == 200:
return True
else:
return False