def handle_power_state(request): # Note: This sample always returns a success response for either a request to TurnOff or TurnOn # TODO: handle other scenarios, other than on/off? endpoint_id = request['directive']['endpoint']['endpointId'] name = name = request['directive']['header']['name'] power_state_value = 'ON' if name == 'TurnOn' else 'OFF' correlation_token = request['directive']['header'][ 'correlationToken'] # required in the response bearer_token = request['directive']['endpoint']['scope'][ 'token'] # identifies the user intof_id = umap.token_intof_map(bearer_token) if (intof_id == None): response = get_error_response('INTERNAL_ERROR', 'Unable to get the Intof user id.') return send_response(response.get()) # TODO: Check for device error in setting the state state_set = set_device_state(intof_id, endpoint_id, power_state_value) if (state_set == False): print('- Error setting the device state!') response = get_error_response( 'ENDPOINT_UNREACHABLE', 'The device is not responding.' ) # TODO: use friendly name in the response return send_response(response.get()) response = AlexaResponse(correlation_token=correlation_token) response.add_context_property(namespace='Alexa.PowerController', name='powerState', value=power_state_value) return response
def control_device(request): """ Control a specific device """ power_state_value = 'OFF' if request['directive']['header'][ 'name'] == 'TurnOff' else 'ON' endpoint_id = request['directive']['endpoint']['endpointId'] correlation_token = request['directive']['header']['correlationToken'] # Check for an error when setting the state state_set = set_device_state(endpoint_id=endpoint_id, value=power_state_value) if state_set is None: return error(type='ENDPOINT_UNREACHABLE', message='Unable to reach endpoint database.') elif not state_set: return error( type='ENDPOINT_UNREACHABLE', message='I did not get a response from the device controller.') apcr = AlexaResponse(correlation_token=correlation_token) apcr.add_context_property(namespace='Alexa.PowerController', name='powerState', value=power_state_value) return success(apcr.get())
def send_event(alexa_namespace, alexa_name, endpoint_id, token, payload): remove_endpoint = alexa_name is not "ChangeReport" alexa_response = AlexaResponse(namespace=alexa_namespace, name=alexa_name, endpoint_id=endpoint_id, token=token, remove_endpoint=remove_endpoint) alexa_response.set_payload(payload) payload = json.dumps(alexa_response.get()) print('LOG api_handler_event.send_event.payload:') print(payload) # TODO Map to correct endpoint for Europe: https://api.eu.amazonalexa.com/v3/events # TODO Map to correct endpoint for Far East: https://api.fe.amazonalexa.com/v3/events alexa_event_gateway_uri = 'api.amazonalexa.com' connection = http.client.HTTPSConnection(alexa_event_gateway_uri) headers = { 'Authorization': "Bearer " + token, 'Content-Type': "application/json;charset=UTF-8", 'Cache-Control': "no-cache" } connection.request('POST', '/v3/events', payload, headers) response = connection.getresponse() print('LOG api_handler_event.send_event HTTP Status code: ' + str(response.getcode())) return response
def get_error_response(error_type, error_msg): response = AlexaResponse(name='ErrorResponse', payload={ 'type': error_type, 'message': error_msg }) return response
def error(**kwargs): """ Build an error response """ rsp = AlexaResponse(name='ErrorResponse', payload=kwargs).get() logger.debug(f'lambda handler failed; response: {json.dumps(rsp)}') return rsp
def handle_control(name, event): endpoint_id = event['directive']['endpoint']['endpointId'] power_state_value = 'OFF' if name == 'TurnOff' else 'ON' correlation_token = event['directive']['header']['correlationToken'] state_sent = send_device_state(endpoint_id=endpoint_id, state='powerState', value=power_state_value) if not state_sent: return AlexaResponse(name='ErrorResponse', payload={ 'type': 'ENDPOINT_UNREACHABLE', 'message': 'Unable to reach endpoint.' }).get() state_save = save_device_state(endpoint_id=endpoint_id, state='powerState', value=power_state_value) if not state_save: return AlexaResponse(name='ErrorResponse', payload={ 'type': 'ENDPOINT_UNREACHABLE', 'message': 'Unable to reach dynamo.' }).get() apcr = AlexaResponse(correlation_token=correlation_token) apcr.add_context_property(namespace=NAMESPACE_CONTROL, name='powerState', value=power_state_value, endpoint_id=endpoint_id) return apcr.get()
def create(self, request): print("LOG event.create.request:", request) try: json_object = json.loads(request['body']) endpoint_user_id = json_object['event']['endpoint']['userId'] # Expect a Profile endpoint_name = json_object['event']['endpoint']['id'] # Expect a valid AWS IoT Thing Name endpoint_state = json_object['event']['endpoint']['state'] # Expect a state value, ex: ON or OFF sku = json_object['event']['endpoint']['sku'] # Expect a meaningful type, ex: SW00 try: # Update the IoT Thing response = iot_aws.update_thing( thingName=endpoint_name, attributePayload={ 'attributes': { 'state': endpoint_state, 'proactively_reported': 'True', 'user_id': endpoint_user_id } } ) print('LOG event.create.iot_aws.update_thing.response:', str(response)) # Update Alexa with a Proactive State Update if endpoint_user_id == 0: print('LOG PSU: Not sent for user_id of 0') else: response_psu = self.send_psu(endpoint_user_id, endpoint_name, endpoint_state) print('LOG PSU response:', response_psu) except ClientError as e: alexa_response = AlexaResponse(name='ErrorResponse', message=e) alexa_response.set_payload( { 'type': 'INTERNAL_ERROR', 'message': e } ) response = alexa_response.get() return response except KeyError as key_error: return "KeyError: " + str(key_error)
def handle_grant (request): print ('- Code grant triggered!') # Note: This sample accepts any grant request # Use the Grant-Code and Grantee-Token to get Access-Token and Refresh-Token and save them in a DB grant_code = request['directive']['payload']['grant']['code'] grantee_token = request['directive']['payload']['grantee']['token'] print ('- Grant code: ', grant_code) print ('- Grantee token: ', grantee_token) result = save_grant (grant_code, grantee_token) if (result == False): response = AlexaResponse ( name = 'ErrorResponse', payload = { 'type': 'INTERNAL_ERROR', 'message': 'Failed to save grant code & token in DB.' }) return response response = AlexaResponse (namespace='Alexa.Authorization', name='AcceptGrant.Response') return response
def validate_request(request): if 'directive' not in request: response = AlexaResponse( name='ErrorResponse', payload={ 'type': 'INVALID_DIRECTIVE', 'message': 'Invalid Alexa request; Missing key: directive' }) return response # payload version must be 3 payload_version = request['directive']['header']['payloadVersion'] if payload_version != '3': response = AlexaResponse( name='ErrorResponse', payload={ 'type': 'INTERNAL_ERROR', 'message': 'Only Smart Home API version 3 is supported.' }) return response print('- Request validated.') return (None) # None = no error response; successfully validated
def handle_discovery(request): print('- Discovery initiated!') token = request['directive']['payload']['scope']['token'] intof_id = umap.token_intof_map(token) if (intof_id == None): response = get_error_response( 'REGISTRATION_ERROR', 'Unable to get the Intof user id.' ) # INVALID_AUTHORIZATION_CREDENTIAL / EXPIRD #return send_response (response.get()) return response devices = device.get_devices(intof_id) if (devices == None): response = get_error_response( 'DISCOVERY_ERROR', 'Unable to get your registered devices.') if len(devices) == 0: response = get_error_response( 'DISCOVERY_ERROR', 'You do not have any registered devices.') return send_response(response.get()) response = AlexaResponse(namespace='Alexa.Discovery', name='Discover.Response') # create a basic alexa capability... capability_alexa = response.create_payload_endpoint_capability() # .. and a more specific capability capability_powercontroller = response.create_payload_endpoint_capability( interface='Alexa.PowerController', supported=[{ 'name': 'powerState' }]) for i in range(len(devices)): response.add_payload_endpoint( friendly_name=devices[i][1], endpoint_id=devices[i][0], capabilities=[capability_alexa, capability_powercontroller]) return response
def test_discovery(self): 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(capabilities=[capability_alexa, capability_alexa_powercontroller]) response = adr.get() self.assertEqual(response['event']['header']['namespace'], 'Alexa.Discovery') self.assertEqual(response['event']['header']['name'], 'Discover.Response') self.assertEqual(response['event']['payload']['endpoints'][0]['friendlyName'], 'Sample Endpoint') self.assertEqual(response['event']['payload']['endpoints'][0]['capabilities'][0]['type'], 'AlexaInterface')
def handle_mode(name, event): endpoint_id = event['directive']['endpoint']['endpointId'] correlation_token = event['directive']['header']['correlationToken'] if name == "SetMode": mode = event['directive']['payload']['mode'] if mode == 'LightMode.SingleColor': neo_mode = 1 elif mode == 'LightMode.Knightrider': neo_mode = 2 elif mode == 'LightMode.Starburst': neo_mode = 3 elif mode == 'LightMode.SlowRainbow': neo_mode = 4 elif mode == 'LightMode.FastRainbow': neo_mode = 5 elif mode == 'LightMode.Emergency': neo_mode = 6 elif name == "StateReport": mode = False if mode: state_sent = send_device_state(endpoint_id=endpoint_id, state='SetMode', value=neo_mode) if not state_sent: return AlexaResponse(name='ErrorResponse', payload={ 'type': 'ENDPOINT_UNREACHABLE', 'message': 'Unable to reach endpoint.' }).get() state_save = save_device_state(endpoint_id=endpoint_id, state='SetMode', value=mode) if not state_save: return AlexaResponse(name='ErrorResponse', payload={ 'type': 'ENDPOINT_UNREACHABLE', 'message': 'Unable to reach dynamo.' }).get() apcr = AlexaResponse(correlation_token=correlation_token) apcr.add_context_property(namespace=NAMESPACE_MODE, name='mode', value=mode, endpoint_id=endpoint_id) return apcr.get()
def lambda_handler(event, context): print(f"---- Request ----\n{json.dumps(event)}") if context is not None: print(f"---- Context ----\n{context}") print(f"---- Env ----\n{os.environ}") if 'directive' not in event: aer = AlexaResponse( name='ErrorResponse', payload={ 'type': 'INVALID_DIRECTIVE', 'message': 'Missing key: directive, Is the request a valid Alexa Directive?' }) return send_response(aer.get()) directive = event['directive'] header = directive['header'] payload_version = 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()) name = header['name'] namespace = header['namespace'] if (namespace == NAMESPACE_DISCOVERY and name == REQUEST_DISCOVER): return send_response(handle_discovery(event)) elif (namespace == NAMESPACE_CONTROL): return send_response(handle_control(name, event)) elif (namespace == NAMESPACE_BRIGHTNESS): return send_response(handle_brightness(name, event)) elif (namespace == NAMESPACE_MODE): return send_response(handle_mode(name, event)) elif (namespace == NAMESPACE_COLOR): return send_response(handle_color(name, event)) elif (namespace == NAMESPACE_AUTH and name == REQUEST_GRANT): return send_response(handle_auth(namespace)) else: return send_response(handle_unexpected_info(namespace))
def discover_devices(request): """ Discover the declared devices this handler supports """ command = request['directive']['header']['name'] if command == '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' }]) for device in _DEVICES: adr.add_payload_endpoint(capabilities=[ capability_alexa, capability_alexa_powercontroller ], **device) return success(adr.get())
def handle_color(name, event): endpoint_id = event['directive']['endpoint']['endpointId'] correlation_token = event['directive']['header']['correlationToken'] if name == "SetColor": color = event['directive']['payload']['color'] neo_color = dict() neo_color["hue"] = round(color["hue"] * 65536 / 360) neo_color["saturation"] = round(color["saturation"] * 255) neo_color["brightness"] = round(color["brightness"] * 255) elif name == "StateReport": color = False if color: state_sent = send_device_state(endpoint_id=endpoint_id, state='SetColor', value=neo_color) if not state_sent: return AlexaResponse(name='ErrorResponse', payload={ 'type': 'ENDPOINT_UNREACHABLE', 'message': 'Unable to reach endpoint.' }).get() state_save = save_device_state(endpoint_id=endpoint_id, state='SetColor', value=color) if not state_save: return AlexaResponse(name='ErrorResponse', payload={ 'type': 'ENDPOINT_UNREACHABLE', 'message': 'Unable to reach dynamo.' }).get() apcr = AlexaResponse(correlation_token=correlation_token) apcr.add_context_property(namespace=NAMESPACE_COLOR, name='color', value=color, endpoint_id=endpoint_id) return apcr.get()
def handle_brightness(name, event): endpoint_id = event['directive']['endpoint']['endpointId'] correlation_token = event['directive']['header']['correlationToken'] if name == "SetBrightness": brightness = event['directive']['payload']['brightness'] elif name == "AdjustBrightness": brightness_delta = event['directive']['payload']['brightnessDelta'] brightness = 50 elif name == "StateReport": brightness = False if brightness: neo_brightness = round(brightness * 255 / 100) state_sent = send_device_state(endpoint_id=endpoint_id, state='SetBrightness', value=neo_brightness) if not state_sent: return AlexaResponse(name='ErrorResponse', payload={ 'type': 'ENDPOINT_UNREACHABLE', 'message': 'Unable to reach endpoint.' }).get() state_save = save_device_state(endpoint_id=endpoint_id, state='SetBrightness', value=brightness) if not state_save: return AlexaResponse(name='ErrorResponse', payload={ 'type': 'ENDPOINT_UNREACHABLE', 'message': 'Unable to reach dynamo.' }).get() apcr = AlexaResponse(correlation_token=correlation_token) apcr.add_context_property(namespace=NAMESPACE_BRIGHTNESS, name='brightness', value=brightness, endpoint_id=endpoint_id) return apcr.get()
def process(self, request, client_id, client_secret, redirect_uri): print('LOG api_handler_directive.process -----') # print(json.dumps(request)) response = None # Process an Alexa directive and route to the right namespace # Only process if there is an actual body to process otherwise return an ErrorResponse json_body = request['body'] if json_body: json_object = json.loads(json_body) namespace = json_object['directive']['header']['namespace'] if namespace == "Alexa": name = json_object['directive']['header']['name'] correlation_token = json_object['directive']['header'][ 'correlationToken'] token = json_object['directive']['endpoint']['scope']['token'] endpoint_id = json_object['directive']['endpoint'][ 'endpointId'] if name == 'ReportState': # Get the User ID from the access_token response_user_id = json.loads( ApiAuth.get_user_id(token).read().decode('utf-8')) result = dynamodb_aws.get_item( TableName='SampleEndpointDetails', Key={'EndpointId': { 'S': endpoint_id }}) capabilities_string = self.get_db_value( result['Item']['Capabilities']) capabilities = json.loads(capabilities_string) props = [] for c in capabilities: if not 'properties' in c: continue retrievable = c['properties'].get('retrievable', False) if retrievable: props.append(c) state = {} try: res = iot_data_aws.get_thing_shadow( thingName=endpoint_id) shadow = json.loads(res['payload'].read()) state = shadow['state']['desired'] except ClientError as e: print('LOG ', e) print('Sending StateReport for', response_user_id, 'on endpoint', endpoint_id) statereport_response = AlexaResponse( name='StateReport', endpoint_id=endpoint_id, correlation_token=correlation_token, token=token) for p in props: key = p['properties']['supported'][0]['name'] if 'instance' in p: key = p['instance'] + '.' + key current_state = state.get(key, DEFAULT_VAL[p['interface']]) if 'instance' in p: statereport_response.add_context_property( namespace=p['interface'], name=p['properties']['supported'][0]['name'], value=current_state, instance=p['instance']) else: statereport_response.add_context_property( namespace=p['interface'], name=p['properties']['supported'][0]['name'], value=current_state) response = statereport_response.get() if namespace == "Alexa.Authorization": grant_code = json_object['directive']['payload']['grant'][ 'code'] grantee_token = json_object['directive']['payload']['grantee'][ 'token'] # Spot the default from the Alexa.Discovery sample. Use as a default for development. if grantee_token == 'access-token-from-skill': user_id = "0" # <- Useful for development response_object = { 'access_token': 'INVALID', 'refresh_token': 'INVALID', 'token_type': 'Bearer', 'expires_in': 9000 } else: # Get the User ID response_user_id = json.loads( ApiAuth.get_user_id(grantee_token).read().decode( 'utf-8')) if 'error' in response_user_id: print( 'ERROR api_handler_directive.process.authorization.user_id:', response_user_id['error_description']) return AlexaResponse(name='ErrorResponse', payload={ 'type': 'INTERNAL_ERROR', 'message': response_user_id }) user_id = response_user_id['user_id'] print( 'LOG api_handler_directive.process.authorization.user_id:', user_id) # Get the Access and Refresh Tokens api_auth = ApiAuth() print('grant_code', grant_code, 'client_id', client_id, 'client_secret', client_secret, 'redirect_uri', redirect_uri) response_token = api_auth.get_access_token( grant_code, client_id, client_secret, redirect_uri) response_token_string = response_token.read().decode('utf-8') print( 'LOG api_handler_directive.process.authorization.response_token_string:', response_token_string) response_object = json.loads(response_token_string) if 'error' in response_object: return AlexaResponse(name='ErrorResponse', payload={ 'type': 'INTERNAL_ERROR', 'response_object': response_object }) # Store the retrieved from the Authorization Server access_token = response_object['access_token'] refresh_token = response_object['refresh_token'] token_type = response_object['token_type'] expires_in = response_object['expires_in'] # Calculate expiration expiration_utc = datetime.utcnow() + timedelta( seconds=(int(expires_in) - 5)) # Store the User Information - This is useful for inspection during development table = boto3.resource('dynamodb').Table('SampleUsers') result = table.put_item( Item={ 'UserId': user_id, 'GrantCode': grant_code, 'GranteeToken': grantee_token, 'AccessToken': access_token, 'ClientId': client_id, 'ClientSecret': client_secret, 'ExpirationUTC': expiration_utc.strftime("%Y-%m-%dT%H:%M:%S.00Z"), 'RedirectUri': redirect_uri, 'RefreshToken': refresh_token, 'TokenType': token_type }) if result['ResponseMetadata']['HTTPStatusCode'] == 200: print( 'LOG api_handler_directive.process.authorization.SampleUsers.put_item:', result) alexa_accept_grant_response = AlexaResponse( namespace='Alexa.Authorization', name='AcceptGrant.Response') response = alexa_accept_grant_response.get() else: error_message = 'Error creating User' print('ERR api_handler_directive.process.authorization', error_message) alexa_error_response = AlexaResponse(name='ErrorResponse') alexa_error_response.set_payload({ 'type': 'INTERNAL_ERROR', 'message': error_message }) response = alexa_error_response.get() if namespace == "Alexa.Cooking": name = json_object['directive']['header']['name'] correlation_token = json_object['directive']['header'][ 'correlationToken'] token = json_object['directive']['endpoint']['scope']['token'] endpoint_id = json_object['directive']['endpoint'][ 'endpointId'] if name == "SetCookingMode": alexa_error_response = AlexaResponse( endpoint_id=endpoint_id, correlation_token=correlation_token, token=token) response = alexa_error_response.get() if namespace == "Alexa.Discovery": # Given the Access Token, get the User ID access_token = json_object['directive']['payload']['scope'][ 'token'] # Spot the default from the Alexa.Discovery sample. Use as a default for development. if access_token == 'access-token-from-skill': print( 'WARN api_handler_directive.process.discovery.user_id: Using development user_id of 0' ) user_id = "0" # <- Useful for development else: response_user_id = json.loads( ApiAuth.get_user_id(access_token).read().decode( 'utf-8')) if 'error' in response_user_id: print( 'ERROR api_handler_directive.process.discovery.user_id: ' + response_user_id['error_description']) user_id = response_user_id['user_id'] print( 'LOG api_handler_directive.process.discovery.user_id:', user_id) adr = AlexaResponse(namespace='Alexa.Discovery', name='Discover.Response') # Get the list of endpoints to return for a User ID and add them to the response # Use the AWS IoT entries for state but get the discovery details from DynamoDB # Wanted to list by group name but that requires a second lookup for the details # iot_aws.list_things_in_thing_group(thingGroupName="Samples") list_response = iot_aws.list_things() # Get a list of sample things by the user_id attribute for thing in list_response['things']: if 'user_id' in thing['attributes']: if thing['attributes']['user_id'] == user_id: # We have an endpoint thing! endpoint_details = ApiHandlerEndpoint.EndpointDetails( ) endpoint_details.id = str(thing['thingName']) print( 'LOG api_handler_directive.process.discovery: Found:', endpoint_details.id, 'for user:'******'SampleEndpointDetails', Key={'EndpointId': { 'S': endpoint_details.id }}) capabilities_string = self.get_db_value( result['Item']['Capabilities']) endpoint_details.capabilities = json.loads( capabilities_string) endpoint_details.description = self.get_db_value( result['Item']['Description']) endpoint_details.display_categories = json.loads( self.get_db_value( result['Item']['DisplayCategories'])) endpoint_details.friendly_name = self.get_db_value( result['Item']['FriendlyName']) endpoint_details.manufacturer_name = self.get_db_value( result['Item']['ManufacturerName']) endpoint_details.sku = self.get_db_value( result['Item']['SKU']) endpoint_details.user_id = self.get_db_value( result['Item']['UserId']) adr.add_payload_endpoint( friendly_name=endpoint_details.friendly_name, endpoint_id=endpoint_details.id, capabilities=endpoint_details.capabilities, display_categories=endpoint_details. display_categories, manufacturer_name=endpoint_details. manufacturer_name) response = adr.get() if namespace == "Alexa.PowerController": name = json_object['directive']['header']['name'] correlation_token = None if 'correlationToken' in json_object['directive']['header']: correlation_token = json_object['directive']['header'][ 'correlationToken'] token = json_object['directive']['endpoint']['scope']['token'] endpoint_id = json_object['directive']['endpoint'][ 'endpointId'] response_user_id = json.loads( ApiAuth.get_user_id(token).read().decode('utf-8')) if 'error' in response_user_id: print( 'ERROR api_handler_directive.process.power_controller.user_id: ' + response_user_id['error_description']) user_id = response_user_id['user_id'] print( 'LOG api_handler_directive.process.power_controller.user_id:', user_id) # Convert to a local stored state power_state_value = 'OFF' if name == "TurnOff" else 'ON' msg = {'state': {'desired': {'powerState': 'ON'}}} msg['state']['desired']['powerState'] = power_state_value mqtt_msg = json.dumps(msg) # Send the state to the Thing Shadow try: response_update = iot_data_aws.update_thing_shadow( thingName=endpoint_id, payload=mqtt_msg.encode()) print( 'LOG api_handler_directive.process.power_controller.response_update -----' ) print(response_update) alexa_response = AlexaResponse( token=token, correlation_token=correlation_token, endpoint_id=endpoint_id) alexa_response.add_context_property( namespace='Alexa.PowerController', name='powerState', value=power_state_value) alexa_response.add_context_property() response = alexa_response.get() except ClientError as e: print( 'ERR api_handler_directive.process.power_controller Exception:ClientError:', e) response = AlexaResponse(name='ErrorResponse', message=e).get() if namespace == "Alexa.ModeController": alexa_error_response = AlexaResponse(name='ErrorResponse') alexa_error_response.set_payload({ 'type': 'INTERNAL_ERROR', 'message': 'Not Yet Implemented' }) response = alexa_error_response.get() if namespace == "Alexa.RangeController": name = json_object['directive']['header']['name'] correlation_token = json_object['directive']['header'][ 'correlationToken'] instance = json_object['directive']['header']['instance'] token = json_object['directive']['endpoint']['scope']['token'] endpoint_id = json_object['directive']['endpoint'][ 'endpointId'] result = dynamodb_aws.get_item( TableName='SampleEndpointDetails', Key={'EndpointId': { 'S': endpoint_id }}) capabilities_string = self.get_db_value( result['Item']['Capabilities']) capabilities = json.loads(capabilities_string) for c in capabilities: if 'instance' in c and c['instance'] == instance: MIN_VAL = c['configuration']['supportedRange'][ 'minimumValue'] MAX_VAL = c['configuration']['supportedRange'][ 'maximumValue'] PREC = c['configuration']['supportedRange'][ 'precision'] break alexa_response = AlexaResponse( endpoint_id=endpoint_id, correlation_token=correlation_token, token=token) value = 0 if name == "AdjustRangeValue": range_value_delta = json_object['directive']['payload'][ 'rangeValueDelta'] range_value_delta_default = json_object['directive'][ 'payload']['rangeValueDeltaDefault'] reported_range_value = 0 # Check to see if we need to use the delta default value (The user did not give a precision) if range_value_delta_default: range_value_delta = PREC # Lookup the existing value of the endpoint by endpoint_id and limit ranges as appropriate - for this sample, expecting 1-6 try: response = iot_data_aws.get_thing_shadow( thingName=endpoint_id) payload = json.loads(response['payload'].read()) reported_range_value = payload['state']['reported'][ instance + '.rangeValue'] print( 'LOG api_handler_directive.process.range_controller.range_value:', reported_range_value) except ClientError as e: print(e) except KeyError as errorKey: print('Could not find key:', errorKey) new_range_value = reported_range_value + range_value_delta value = max(min(new_range_value, MAX_VAL), MIN_VAL) if name == "SetRangeValue": range_value = json_object['directive']['payload'][ 'rangeValue'] value = max(min(range_value, MAX_VAL), MIN_VAL) alexa_response.add_context_property( namespace='Alexa.RangeController', name='rangeValue', value=value) # Update the Thing Shadow msg = {'state': {'desired': {}}} # NOTE: The instance is used to keep the stored value unique msg['state']['desired'][instance + '.rangeValue'] = value mqtt_msg = json.dumps(msg) response_update = iot_data_aws.update_thing_shadow( thingName=endpoint_id, payload=mqtt_msg.encode()) print( 'LOG api_handler_directive.process.range_controller.response_update -----' ) print(response_update) # Send back the response response = alexa_response.get() if namespace == "Alexa.ToggleController": name = json_object['directive']['header']['name'] correlation_token = json_object['directive']['header'][ 'correlationToken'] instance = json_object['directive']['header']['instance'] token = json_object['directive']['endpoint']['scope']['token'] endpoint_id = json_object['directive']['endpoint'][ 'endpointId'] # Convert to a local stored state toggle_state_value = 'OFF' if name == "TurnOff" else 'ON' state_name = instance + '.state' msg = {'state': {'desired': {state_name: 'ON'}}} msg['state']['desired'][state_name] = toggle_state_value mqtt_msg = json.dumps(msg) # Send the state to the Thing Shadow try: response_update = iot_data_aws.update_thing_shadow( thingName=endpoint_id, payload=mqtt_msg.encode()) print( 'LOG api_handler_directive.process.toggle_controller.response_update -----' ) print(response_update) alexa_response = AlexaResponse( token=token, correlation_token=correlation_token, endpoint_id=endpoint_id) alexa_response.add_context_property( namespace='Alexa.ToggleController', name='toggleState', instance=instance, value=toggle_state_value) alexa_response.add_context_property() response = alexa_response.get() except ClientError as e: print( 'ERR api_handler_directive.process.toggle_controller Exception:ClientError:', e) response = AlexaResponse(name='ErrorResponse', message=e).get() else: alexa_error_response = AlexaResponse(name='ErrorResponse') alexa_error_response.set_payload({ 'type': 'INTERNAL_ERROR', 'message': 'Empty Body' }) response = alexa_error_response.get() if response is None: # response set to None indicates an unhandled directive, review the logs alexa_error_response = AlexaResponse(name='ErrorResponse') alexa_error_response.set_payload({ 'type': 'INTERNAL_ERROR', 'message': 'Empty Response: No response processed. Unhandled Directive.' }) response = alexa_error_response.get() print('LOG api_handler_directive.process.response -----') print(json.dumps(response)) return response
def test_response(self): response = AlexaResponse().get() self.assertEqual(response['event']['header']['namespace'], 'Alexa') self.assertEqual(response['event']['header']['name'], 'Response')
def process(self, request, client_id, client_secret, redirect_uri): print('LOG directive.process.request:', request) response = None # Process an Alexa directive and route to the right namespace # Only process if there is an actual body to process otherwise return an ErrorResponse json_body = request['body'] if json_body: json_object = json.loads(json_body) namespace = json_object['directive']['header']['namespace'] if namespace == "Alexa": name = json_object['directive']['header']['name'] correlation_token = json_object['directive']['header'][ 'correlationToken'] endpoint_id = json_object['directive']['endpoint'][ 'endpointId'] if name == 'ReportState': # TODO Get the User ID from the access_token # TODO Lookup the endpoint and get state print('Sending StateReport for endpoint', endpoint_id) alexa_reportstate_response = AlexaResponse( name='StateReport', endpoint_id=endpoint_id, correlation_token=correlation_token) response = alexa_reportstate_response.get() if namespace == "Alexa.Authorization": grant_code = json_object['directive']['payload']['grant'][ 'code'] grantee_token = json_object['directive']['payload']['grantee'][ 'token'] # Spot the default from the Alexa.Discovery sample. Use as a default for development. if grantee_token == 'access-token-from-skill': user_id = "0" # <- Useful for development response_object = { 'access_token': 'INVALID', 'refresh_token': 'INVALID', 'token_type': 'Bearer', 'expires_in': 9000 } else: # Get the User ID response_user_id = json.loads( ApiAuth.get_user_id(grantee_token).read().decode( 'utf-8')) if 'error' in response_user_id: print('ERROR directive.process.authorization.user_id:', response_user_id['error_description']) user_id = response_user_id['user_id'] print('LOG directive.process.authorization.user_id:', user_id) # Get the Access and Refresh Tokens api_auth = ApiAuth() response_token = api_auth.get_access_token( grant_code, client_id, client_secret, redirect_uri) response_token_string = response_token.read().decode( 'utf-8') print( 'LOG directive.process.authorization.response_token_string:', response_token_string) response_object = json.loads(response_token_string) # Store the retrieved from the Authorization Server access_token = response_object['access_token'] refresh_token = response_object['refresh_token'] token_type = response_object['token_type'] expires_in = response_object['expires_in'] # Calculate expiration expiration_utc = datetime.utcnow() + timedelta( seconds=(int(expires_in) - 5)) # Store the User Information - This is useful for inspection during development table = boto3.resource('dynamodb').Table('SampleUsers') result = table.put_item( Item={ 'UserId': user_id, 'GrantCode': grant_code, 'GranteeToken': grantee_token, 'AccessToken': access_token, 'ClientId': client_id, 'ClientSecret': client_secret, 'ExpirationUTC': expiration_utc.strftime("%Y-%m-%dT%H:%M:%S.00Z"), 'RedirectUri': redirect_uri, 'RefreshToken': refresh_token, 'TokenType': token_type }) if result['ResponseMetadata']['HTTPStatusCode'] == 200: print( 'LOG directive.process.authorization.SampleUsers.put_item:', result) alexa_accept_grant_response = AlexaResponse( namespace='Alexa.Authorization', name='AcceptGrant.Response') response = alexa_accept_grant_response.get() else: error_message = 'Error creating User' print('ERR directive.process.authorization', error_message) alexa_error_response = AlexaResponse(name='ErrorResponse') alexa_error_response.set_payload({ 'type': 'INTERNAL_ERROR', 'message': error_message }) response = alexa_error_response.get() if namespace == "Alexa.Cooking": name = json_object['directive']['header']['name'] correlation_token = json_object['directive']['header'][ 'correlationToken'] token = json_object['directive']['endpoint']['scope']['token'] endpoint_id = json_object['directive']['endpoint'][ 'endpointId'] if name == "SetCookingMode": alexa_error_response = AlexaResponse( endpoint_id=endpoint_id, correlation_token=correlation_token, token=token) response = alexa_error_response.get() if namespace == "Alexa.Discovery": # Given the Access Token, get the User ID access_token = json_object['directive']['payload']['scope'][ 'token'] # Spot the default from the Alexa.Discovery sample. Use as a default for development. if access_token == 'access-token-from-skill': print( 'WARN directive.process.discovery.user_id: Using development user_id of 0' ) user_id = "0" # <- Useful for development else: response_user_id = json.loads( ApiAuth.get_user_id(access_token).read().decode( 'utf-8')) if 'error' in response_user_id: print('ERROR directive.process.discovery.user_id: ' + response_user_id['error_description']) user_id = response_user_id['user_id'] print('LOG directive.process.discovery.user_id:', user_id) alexa_discover_response = AlexaDiscoverResponse(json_object) # Get the list of endpoints to return for a User ID and add them to the response # Use the AWS IoT entries for state but get the discovery details from DynamoDB # HACK Boto3 1.4.8 attributeName and attributeValue stopped working, raw list_things() works however # Not Working: list_response = iot_aws.list_things(attributeName='user_id', attributeValue=user_id) list_response = iot_aws.list_things() for thing in list_response['things']: if thing['attributes']['user_id'] == user_id: endpoint_details = ApiHandlerEndpoint.EndpointDetails() endpoint_details.id = str( thing['thingName'] ) # Add attribute endpoint_id to free thingName? print('LOG directive.process.discovery: Found:', endpoint_details.id, 'for user:'******'SampleEndpointDetails', Key={'EndpointId': { 'S': endpoint_details.id }}) capabilities_string = self.get_db_value( result['Item']['Capabilities']) endpoint_details.capabilities = json.loads( capabilities_string) endpoint_details.description = self.get_db_value( result['Item']['Description']) endpoint_details.display_categories = json.loads( self.get_db_value( result['Item']['DisplayCategories'])) endpoint_details.friendly_name = self.get_db_value( result['Item']['FriendlyName']) endpoint_details.manufacturer_name = self.get_db_value( result['Item']['ManufacturerName']) endpoint_details.sku = self.get_db_value( result['Item']['SKU']) endpoint_details.user_id = self.get_db_value( result['Item']['UserId']) alexa_discover_response.add_endpoint(endpoint_details) response = alexa_discover_response.get_response() if namespace == "Alexa.PowerController": name = json_object['directive']['header']['name'] correlation_token = None if 'correlationToken' in json_object['directive']['header']: correlation_token = json_object['directive']['header'][ 'correlationToken'] token = json_object['directive']['endpoint']['scope']['token'] endpoint_id = json_object['directive']['endpoint'][ 'endpointId'] response_user_id = json.loads( ApiAuth.get_user_id(token).read().decode('utf-8')) if 'error' in response_user_id: print( 'ERROR directive.process.power_controller.user_id: ' + response_user_id['error_description']) user_id = response_user_id['user_id'] print('LOG directive.process.power_controller.user_id', user_id) # Convert to a local stored state power_state_value = 'OFF' if name == "TurnOff" else 'ON' try: # Send the state to the Thing response_update = iot_aws.update_thing( thingName=endpoint_id, attributePayload={ 'attributes': { 'state': power_state_value, 'user_id': user_id } }) print( 'LOG directive.process.power_controller.response_update:', response_update) alexa_power_controller_response = AlexaResponse( namespace='Alexa.PowerController', name=name, token=token, correlation_token=correlation_token, endpoint_id=endpoint_id) response = alexa_power_controller_response.get() except ClientError as e: response = AlexaResponse(name='ErrorResponse', message=e).get() else: response = AlexaResponse(name='ErrorResponse').get() if response is None: # response set to None indicates an unhandled directive, review the logs response = AlexaResponse( name='ErrorResponse', message='Empty Response: No response processed').get() # else: # TODO Validate the Response once the schema is updated # if not self.validate_response(response): # response = AlexaError(message='Failed to validate message against the schema').get_response() # print('LOG directive.process.response', response) return json.dumps(response)
def test_response_error(self): payload_error = {"type": "INVALID_SOMETHING", "message": "ERROR_MESSAGE"} response = AlexaResponse(name="ErrorResponse", payload=payload_error).get() self.assertEqual(response['event']['header']['name'], 'ErrorResponse')
def test_response_cookie(self): response = AlexaResponse(cookie={"key": "value"}).get() self.assertEqual(response['event']['endpoint']['cookie']['key'], 'value')
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'] print('Name:' + name) print('Namespace:' + 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' }]) capability_alexa_temperaturesensor = adr.create_payload_endpoint_capability( interface='Alexa.TemperatureSensor', supported=[{ 'name': 'temperature' }], retrievable='true', proactively_reported='true', ) capability_alexa_endpointhealth = adr.create_payload_endpoint_capability( interface='Alexa.EndpointHealth', supported=[{ 'name': 'connectivity' }], proactively_reported='true', retrievable='true') # adr.add_payload_endpoint( # friendly_name='Termostato Sala', # endpoint_id='temperaturesensor-001', # description='Termostato Sala', # display_categories=["TEMPERATURE_SENSOR"], # manufacturer_name = 'myself manufacter', # capabilities=[capability_alexa, capability_alexa_temperaturesensor, capability_alexa_endpointhealth ]) # adr.add_payload_endpoint( # friendly_name='Termostato Cucina', # endpoint_id='temperaturesensor-002', # description='Termostato Cucina', # display_categories=["TEMPERATURE_SENSOR"], # manufacturer_name = 'myself manufacter', # capabilities=[capability_alexa, capability_alexa_temperaturesensor, capability_alexa_endpointhealth ]) adr.add_payload_endpoint(friendly_name='Luce 01', endpoint_id='light-001', description='Luce 01', display_categories=["LIGHT"], manufacturer_name='myself manufacter', capabilities=[ capability_alexa, capability_alexa_powercontroller ]) adr.add_payload_endpoint(friendly_name='Luce 02', endpoint_id='light-002', description='Luce 02', display_categories=["LIGHT"], manufacturer_name='myself manufacter', capabilities=[ capability_alexa, capability_alexa_powercontroller ]) adr.add_payload_endpoint(friendly_name='Luce 03', endpoint_id='light-003', description='Luce 03', display_categories=["LIGHT"], manufacturer_name='myself manufacter', capabilities=[ capability_alexa, capability_alexa_powercontroller ]) adr.add_payload_endpoint(friendly_name='Luce 04', endpoint_id='light-004', description='Luce 04', display_categories=["LIGHT"], manufacturer_name='myself manufacter', capabilities=[ capability_alexa, capability_alexa_powercontroller ]) adr.add_payload_endpoint(friendly_name='Luce 05', endpoint_id='light-005', description='Luce 05', display_categories=["LIGHT"], manufacturer_name='myself manufacter', capabilities=[ capability_alexa, capability_alexa_powercontroller ]) adr.add_payload_endpoint(friendly_name='Luce 06', endpoint_id='light-006', description='Luce 06', display_categories=["LIGHT"], manufacturer_name='myself manufacter', capabilities=[ capability_alexa, capability_alexa_powercontroller ]) adr.add_payload_endpoint(friendly_name='Luce 07', endpoint_id='light-007', description='Luce 07', display_categories=["LIGHT"], manufacturer_name='myself manufacter', capabilities=[ capability_alexa, capability_alexa_powercontroller ]) adr.add_payload_endpoint(friendly_name='Luce 08', endpoint_id='light-008', description='Luce 08', display_categories=["LIGHT"], manufacturer_name='myself manufacter', capabilities=[ capability_alexa, capability_alexa_powercontroller ]) adr.add_payload_endpoint(friendly_name='Cancellino', endpoint_id='gate-001', description='Cancellino', display_categories=["DOOR"], manufacturer_name='myself manufacter', capabilities=[ capability_alexa, capability_alexa_powercontroller ]) return send_response(adr.get()) if namespace == 'Alexa': if name == 'ReportState': print('Richiesta tipo ReportState -----') # Note: This sample always returns a success response for either a request to TurnOff or TurnOn endpoint_id = request['directive']['endpoint']['endpointId'] print('ENDPOINT ID: ' + endpoint_id) correlation_token = request['directive']['header'][ 'correlationToken'] token = request['directive']['endpoint']['scope']['token'] print('Correlation_Token: ' + correlation_token) print('Token: ' + token) apcr = AlexaResponse(name='StateReport', endpoint_id=endpoint_id, correlation_token=correlation_token, token=token) if (endpoint_id == 'temperaturesensor-001'): apcr.add_context_property(namespace='Alexa.TemperatureSensor', name='temperature', value={ 'value': '19.9', 'scale': 'CELSIUS' }) elif (endpoint_id == 'temperaturesensor-002'): apcr.add_context_property(namespace='Alexa.TemperatureSensor', name='temperature', value={ 'value': '22.3', 'scale': 'CELSIUS' }) return send_response(apcr.get()) if namespace == 'Alexa.PowerController' or namespace == 'Alexa.ToogleController': print('Richiesta tipo 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 create(self, request): print('LOG event.create.request -----') print(request) try: json_object = json.loads(request['body']) # Transpose the Endpoint Cloud Event into an Alexa Event Gateway Event # Get the common information from the body of the request event_type = json_object['event'][ 'type'] # Expect AddOrUpdateReport, ChangeReport, DeleteReport endpoint_user_id = json_object['event']['endpoint'][ 'userId'] # Expect a Profile endpoint_id = json_object['event']['endpoint'][ 'id'] # Expect a valid AWS IoT Thing Name # Get the Access Token token = self.get_user_info(endpoint_user_id) # Build a default response response = AlexaResponse(name='ErrorResponse', message="No valid event type") if event_type == 'AddOrUpdateReport': # Get the additional information from the body of the request endpoint_friendly_name = json_object['event']['endpoint'][ 'friendlyName'] # Expect a valid string friendly name endpoint_capabilities = json_object['event']['endpoint'][ 'capabilities'] # Expect a valid AWS IoT Thing Name sku = json_object['event']['endpoint'][ 'sku'] # Expect a meaningful type, ex: SW00 # From the SKU, get the information for the device and combine it in the payload endpoint_sku_details = self.get_sku_details(sku) payload = { 'endpoints': [{ 'endpointId': endpoint_id, 'friendlyName': endpoint_friendly_name, 'description': endpoint_sku_details['description'], 'manufacturerName': endpoint_sku_details['manufacturer_name'], 'displayCategories': endpoint_sku_details['display_categories'], 'capabilities': endpoint_capabilities }], 'scope': { 'type': 'BearerToken', 'token': token } } # Send an event to Alexa to add/update the endpoint response = self.send_event('Alexa.Discovery', 'AddOrUpdateReport', endpoint_id, token, payload) if event_type == 'ChangeReport': try: state = json_object['event']['endpoint'][ 'state'] # Expect a string, ex: powerState state_value = json_object['event']['endpoint'][ 'value'] # Expect string or JSON # Update the IoT Thing Shadow state msg = {'state': {'desired': {state: state_value}}} mqtt_msg = json.dumps(msg) result = iot_data_aws.update_thing_shadow( thingName=endpoint_id, payload=mqtt_msg.encode()) print( 'LOG event.create.iot_aws.update_thing_shadow.result -----' ) print(result) # Update Alexa with an Event Update if endpoint_user_id == '0': print('LOG Event: Not sent for user_id of 0') else: payload = { 'change': { 'cause': { 'type': 'PHYSICAL_INTERACTION' }, "properties": [ AlexaResponse.create_context_property( name=state, value=state_value) ] } } print('LOG Event: Sending event') response = self.send_event('Alexa', 'ChangeReport', endpoint_id, token, payload) except ClientError as e: alexa_response = AlexaResponse(name='ErrorResponse', message=e, payload={ 'type': 'INTERNAL_ERROR', 'message': e }) return alexa_response.get() if event_type == 'DeleteReport': # Send an event to Alexa to delete the endpoint payload = { 'endpoints': [{ 'endpointId': endpoint_id }], "scope": { "type": "BearerToken", "token": token } } response = self.send_event('Alexa.Discovery', 'DeleteReport', endpoint_id, token, payload) result = response.read().decode('utf-8') print('LOG event.create.result -----') print(result) return result except KeyError as key_error: return "KeyError: " + str(key_error)
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(endpoint_id='LED', friendly_name='LED', description='これはただのLEDです。', display_categories=['LIGHT'], 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'] apcr = AlexaResponse(correlation_token=correlation_token) apcr.add_context_property(namespace='Alexa.PowerController', name='powerState', value=power_state_value) # Update Thing Shadow Desired Value update_thing_shadow("led", False if power_state_value == 'OFF' else True) return send_response(apcr.get())
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() # Check for an error when setting TurnOn/TurnOff time if power_state_value == 'ON': TurnOn_set = set_device_time(endpoint_id=endpoint_id, state='TurnOnTime', value=get_seconds_timestamp()) if not TurnOn_set: return AlexaResponse( name='ErrorResponse', payload={'type': 'ENDPOINT_UNREACHABLE', 'message': 'Unable to reach endpoint database.'}).get() global turned_on turned_on = get_seconds_timestamp() time.sleep(random.uniform(1.500,2.500)) TurnOff_set = set_device_state(endpoint_id=endpoint_id, state='powerState', value='OFF') print("turnedoff") # if not TurnOff_set: # return AlexaResponse( # name='ErrorResponse', # payload={'type': 'ENDPOINT_UNREACHABLE', 'message': 'Unable to reach endpoint database.'}).get() if power_state_value == 'OFF': TurnOff_set = set_device_time(endpoint_id=endpoint_id, state='TurnOffTime', value=get_seconds_timestamp()) if not TurnOff_set: return AlexaResponse( name='ErrorResponse', payload={'type': 'ENDPOINT_UNREACHABLE', 'message': 'Unable to reach endpoint database.'}).get() global turned_off turned_off = get_seconds_timestamp() uptime_length = get_uptime_length(turned_on, turned_off) last_uptime_length = set_device_time(endpoint_id=endpoint_id, state='LastUptimeLength', value=uptime_length) if not last_uptime_length: return AlexaResponse( name='ErrorResponse', payload={'type': 'ENDPOINT_UNREACHABLE', 'message': 'Unable to reach endpoint database.'}).get() add_value_to_file(uptime_length) 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 handle_discovery(event): adr = AlexaResponse(namespace=NAMESPACE_DISCOVERY, name=RESPONSE_DISCOVER) capability_alexa = adr.create_payload_endpoint_capability() capability_alexa_powercontroller = adr.create_payload_endpoint_capability( interface=NAMESPACE_CONTROL, supported=[{ 'name': 'powerState' }]) capability_alexa_brightnesscontroller = adr.create_payload_endpoint_capability( interface=NAMESPACE_BRIGHTNESS, supported=[{ 'name': 'brightness' }]) capability_alexa_colorcontroller = adr.create_payload_endpoint_capability( interface=NAMESPACE_COLOR, supported=[{ 'name': 'color' }]) capability_alexa_modecontroller = adr.create_payload_endpoint_capability( interface=NAMESPACE_MODE, supported=[{ 'name': 'mode' }]) capability_alexa_modecontroller['instance'] = 'CharlotteLight.LightMode' capability_alexa_modecontroller['capabilityResources'] = { "friendlyNames": [{ "@type": "text", "value": { "text": "mode", "locale": "en-GB" } }, { "@type": "text", "value": { "text": "light mode", "locale": "en-GB" } }, { "@type": "text", "value": { "text": "pattern", "locale": "en-GB" } }, { "@type": "text", "value": { "text": "color mode", "locale": "en-GB" } }] } capability_alexa_modecontroller['configuration'] = { "ordered": False, "supportedModes": [{ "value": "LightMode.SingleColor", "modeResources": { "friendlyNames": [{ "@type": "text", "value": { "text": "normal", "locale": "en-GB" } }, { "@type": "text", "value": { "text": "single color", "locale": "en-GB" } }] } }, { "value": "LightMode.Knightrider", "modeResources": { "friendlyNames": [{ "@type": "text", "value": { "text": "knightrider", "locale": "en-GB" } }, { "@type": "text", "value": { "text": "night rider", "locale": "en-GB" } }, { "@type": "text", "value": { "text": "scroll", "locale": "en-GB" } }, { "@type": "text", "value": { "text": "cylon", "locale": "en-GB" } }] } }, { "value": "LightMode.Starburst", "modeResources": { "friendlyNames": [{ "@type": "text", "value": { "text": "stars", "locale": "en-GB" } }, { "@type": "text", "value": { "text": "starburst", "locale": "en-GB" } }] } }, { "value": "LightMode.SlowRainbow", "modeResources": { "friendlyNames": [{ "@type": "text", "value": { "text": "rainbow", "locale": "en-GB" } }, { "@type": "text", "value": { "text": "slow rainbow", "locale": "en-GB" } }] } }, { "value": "LightMode.FastRainbow", "modeResources": { "friendlyNames": [{ "@type": "text", "value": { "text": "fast rainbow", "locale": "en-GB" } }, { "@type": "text", "value": { "text": "chase rainbow", "locale": "en-GB" } }, { "@type": "text", "value": { "text": "cinema chase rainbow", "locale": "en-GB" } }] } }, { "value": "LightMode.Emergency", "modeResources": { "friendlyNames": [{ "@type": "text", "value": { "text": "emergency", "locale": "en-GB" } }, { "@type": "text", "value": { "text": "emergency lights", "locale": "en-GB" } }] } }] } capability_alexa_scenecontroller = adr.create_payload_endpoint_capability( interface=NAMESPACE_SCENE) adr.add_payload_endpoint(friendly_name='Charlotte Light', endpoint_id='charlotte-light-01', manufacturer_name='Larssen Inc', description='NeoPixel Light', display_categories=["LIGHT"], capabilities=[ capability_alexa, capability_alexa_powercontroller, capability_alexa_brightnesscontroller, capability_alexa_colorcontroller, capability_alexa_modecontroller, capability_alexa_scenecontroller ]) return adr.get()
def handle_auth(event): grant_code = event['directive']['payload']['grant']['code'] grantee_token = event['directive']['payload']['grantee']['token'] aar = AlexaResponse(namespace=NAMESPACE_AUTH, name=RESPONSE_GRANT) return aar.get()
def handle_unexpected_info(namespace): return AlexaResponse(name='ErrorResponse', payload={ 'type': 'INVALID_DIRECTIVE', 'message': f'Unknown namespace: {namespace}' }).get()
def lambda_handler(request, context): # Dump the request for logging - check the CloudWatch logs logger.debug('lambda_handler request -----') logger.debug(json.dumps(request)) if context is not None: logger.debug('lambda_handler context -----') logger.debug(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' }], proactively_reported=False, retrievable=False) capability_alexa_colorcontroller = adr.create_payload_endpoint_capability( interface='Alexa.ColorController', supported=[{ 'name': 'color' }], proactively_reported=False, retrievable=False) adr.add_payload_endpoint( friendly_name= 'Den Lights', # TODO remove hardcoded friendly name endpoint_id= 'thomas-den-lights', # TODO removed hardcoded endpoint_id capabilities=[ capability_alexa, capability_alexa_powercontroller, capability_alexa_colorcontroller ]) 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()) if namespace == 'Alexa.ColorController' and name == 'SetColor': # Note: This sample always returns a success response for either a request to TurnOff or TurnOn endpoint_id = request['directive']['endpoint']['endpointId'] correlation_token = request['directive']['header']['correlationToken'] # Check for an error when setting the state color_set = set_color_state( endpoint_id=endpoint_id, state='color', value=request['directive']['payload']['color']) if not color_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.ColorController', name='color', value=request['directive']['payload']['color']) return send_response(apcr.get())
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_power_controller = adr.create_payload_endpoint_capability( interface='Alexa.PowerController', supported=[{ 'name': 'powerState' }]) capability_alexa_stepSpeaker_controller = adr.create_payload_endpoint_capability( interface='Alexa.StepSpeaker') capability_alexa_playback_controller = adr.create_payload_endpoint_capability( interface='Alexa.PlaybackController', supportedOperations=['Play', 'Pause', 'Stop']) adr.add_payload_endpoint( friendly_name='TV', endpoint_id='ps3-tv', manufacturer_name='Sony', description='PlayStation 3 TV connected with Raspberry Pi', displayCategories=['TV'], capabilities=[ capability_alexa, capability_alexa_power_controller, capability_alexa_stepSpeaker_controller, capability_alexa_playback_controller ]) 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'] try: response = requests.post('{}toggle-power'.format(constants.url), json={'powerState': power_state_value}) if response.status_code == 200: speech = response.text else: return AlexaResponse( name='ErrorResponse', payload={ 'type': 'INTERNAL_ERROR', 'message': 'There was an error with the endpoint.' }).get() except: return AlexaResponse(name='ErrorResponse', payload={ 'type': 'ENDPOINT_UNREACHABLE', 'message': 'Unable to reach endpoint.' }).get() apcr = AlexaResponse(correlation_token=correlation_token) apcr.add_context_property(namespace='Alexa.PowerController', name='powerState', value=power_state_value) elif namespace == 'Alexa.StepSpeaker': endpoint_id = request['directive']['endpoint']['endpointId'] correlation_token = request['directive']['header']['correlationToken'] try: if name == 'AdjustVolume': response = requests.post( '{}adjust-volume'.format(constants.url), json={ 'volumeSteps': request['directive']['payload']['volumeSteps'] }) else: response = None if response and response.status_code == 200: speech = response.text else: return AlexaResponse( name='ErrorResponse', payload={ 'type': 'INTERNAL_ERROR', 'message': 'There was an error with the endpoint.' }).get() except: return AlexaResponse(name='ErrorResponse', payload={ 'type': 'ENDPOINT_UNREACHABLE', 'message': 'Unable to reach endpoint.' }).get() apcr = AlexaResponse(correlation_token=correlation_token) apcr.add_context_property( namespace='Alexa.StepSpeaker', name='volumeSteps', value=request['directive']['payload']['volumeSteps']) elif namespace == 'Alexa.PlaybackController': endpoint_id = request['directive']['endpoint']['endpointId'] correlation_token = request['directive']['header']['correlationToken'] try: if name == 'Play' or name == 'Pause' or name == 'Stop': response = requests.post( '{}play-pause-stop-chromecast-youtube'.format( constants.url), json={'action': name}) else: response = None if response and response.status_code == 200: speech = response.text else: return AlexaResponse( name='ErrorResponse', payload={ 'type': 'INTERNAL_ERROR', 'message': 'There was an error with the endpoint.' }).get() except: return AlexaResponse(name='ErrorResponse', payload={ 'type': 'ENDPOINT_UNREACHABLE', 'message': 'Unable to reach endpoint.' }).get() apcr = AlexaResponse(correlation_token=correlation_token) apcr.add_context_property(namespace='Alexa.PlaybackController', name='action', value=name) return send_response(apcr.get())