├── sample_lambda ├── python │ ├── __init__.py │ ├── jsonschema │ │ ├── tests │ │ │ ├── __init__.py │ │ │ ├── compat.py │ │ │ ├── test_format.py │ │ │ └── test_cli.py │ │ ├── __main__.py │ │ ├── __pycache__ │ │ │ ├── __init__.cpython-36.pyc │ │ │ ├── _format.cpython-36.pyc │ │ │ ├── _utils.cpython-36.pyc │ │ │ ├── compat.cpython-36.pyc │ │ │ ├── exceptions.cpython-36.pyc │ │ │ ├── validators.cpython-36.pyc │ │ │ └── _validators.cpython-36.pyc │ │ ├── __init__.py │ │ ├── compat.py │ │ ├── cli.py │ │ ├── schemas │ │ │ └── draft3.json │ │ ├── _reflect.py │ │ └── _utils.py │ └── validation.py └── README.md ├── sample_backend ├── lambda │ ├── lambda_api │ │ └── python │ │ │ ├── alexa │ │ │ ├── __init__.py │ │ │ └── skills │ │ │ │ ├── __init__.py │ │ │ │ └── smarthome │ │ │ │ ├── __init__.py │ │ │ │ ├── alexa_utils.py │ │ │ │ ├── alexa_acceptgrant_response.py │ │ │ │ ├── alexa_response.py │ │ │ │ ├── alexa_error.py │ │ │ │ ├── alexa_power_controller.py │ │ │ │ ├── alexa_change_report.py │ │ │ │ └── alexa_discover_response.py │ │ │ ├── jsonschema │ │ │ ├── tests │ │ │ │ ├── __init__.py │ │ │ │ ├── compat.py │ │ │ │ ├── test_format.py │ │ │ │ └── test_cli.py │ │ │ ├── __main__.py │ │ │ ├── _version.py │ │ │ ├── __init__.py │ │ │ ├── compat.py │ │ │ ├── cli.py │ │ │ ├── schemas │ │ │ │ └── draft3.json │ │ │ ├── _reflect.py │ │ │ └── _utils.py │ │ │ ├── endpoint_cloud │ │ │ ├── __init__.py │ │ │ ├── api_utils.py │ │ │ ├── api_message.py │ │ │ ├── api_response_body.py │ │ │ ├── api_response.py │ │ │ └── api_auth.py │ │ │ └── index.py │ ├── README.md │ └── lambda_smarthome │ │ └── python │ │ └── index.py ├── docs │ ├── img │ │ ├── 3.4.3-lambda-trigger.png │ │ ├── 5.2.5-linking-dialog.png │ │ ├── 2.1.10-lwa-web-settings.png │ │ ├── 2.1.14-lwa-web-settings.png │ │ ├── 5.2.3-smart-home-skill.png │ │ ├── 6.2.4-thing-inspection.png │ │ ├── alexa-sample-smarthome-108x.png │ │ ├── alexa-sample-smarthome-150x.png │ │ ├── alexa-sample-smarthome-512x.png │ │ ├── 2.1.7-lwa-profile-configuration.png │ │ └── 6.1.3.1-postman-manage-environments.png │ ├── README.md │ ├── config.txt │ ├── 009-setup-cleanup.md │ ├── 008-setup-send-an-event.md │ ├── 007-setup-test-endpoints.md │ ├── 000-setup-requirements.md │ ├── 001-setup-create-backend.md │ ├── 002-setup-lwa.md │ ├── 005-setup-link-skill-smarthome.md │ ├── 003-setup-create-skill-smarthome.md │ └── 006-setup-create-endpoints.md └── README.md ├── sample_async └── README.md ├── sample_messages ├── README.md ├── ErrorResponse │ ├── README.md │ ├── ErrorResponse.General.json │ ├── ErrorResponse.General.ENDPOINT_LOW_POWER.json │ ├── ErrorResponse.General.VALUE_OUT_OF_RANGE.json │ └── ErrorResponse.General.TEMPERATURE_VALUE_OUT_OF_RANGE.json ├── LockController │ ├── README.md │ ├── LockController.Lock.request.json │ ├── LockController.Unlock.request.json │ ├── LockController.Lock.response.json │ └── LockController.Unlock.response.json ├── Authorization │ ├── Authorization.AcceptGrant.response.json │ ├── ErrorResponse.Authorization.ACCEPT_GRANT_FAILED.json │ └── Authorization.AcceptGrant.request.json ├── DeferredResponse │ └── DeferredResponse.json ├── Discovery │ └── Discovery.request.json ├── StateReport │ ├── ReportState.json │ └── StateReport.json ├── PowerController │ ├── PowerController.TurnOff.request.json │ ├── PowerController.TurnOn.request.json │ ├── PowerController.TurnOff.response.json │ └── PowerController.TurnOn.response.json ├── PlaybackController │ ├── PlaybackController.Next.request.json │ ├── PlaybackController.Pause.request.json │ ├── PlaybackController.Play.request.json │ ├── PlaybackController.Stop.request.json │ ├── PlaybackController.Rewind.request.json │ ├── PlaybackController.Previous.request.json │ ├── PlaybackController.StartOver.request.json │ ├── PlaybackController.FastForward.request.json │ └── PlaybackController.response.json ├── SceneController │ ├── SceneController.Activate.request.json │ ├── SceneController.Deactivate.request.json │ ├── SceneController.ActivationStarted.message.json │ └── SceneController.DeactivationStarted.message.json ├── Speaker │ ├── Speaker.SetMute.request.json │ ├── Speaker.SetVolume.request.json │ ├── Speaker.AdjustVolume.request.json │ └── Speaker.response.json ├── StepSpeaker │ ├── StepSpeaker.SetMute.request.json │ ├── StepSpeaker.AdjustVolume.request.json │ └── StepSpeaker.response.json ├── InputController │ ├── InputController.SelectInput.request.json │ └── InputController.SelectInput.response.json ├── ChannelController │ ├── ChannelController.SkipChannels.request.json │ ├── ChannelController.ChangeChannel.request.json │ └── ChannelController.response.json ├── ColorTemperatureController │ ├── ColorTemperatureController.DecreaseColorTemperature.request.json │ ├── ColorTemperatureController.IncreaseColorTemperature.request.json │ ├── ColorTemperatureController.SetColorTemperature.request.json │ └── ColorTemperatureController.response.json ├── BrightnessController │ ├── BrightnessController.SetBrightness.request.json │ ├── BrightnessController.AdjustBrightness.request.json │ ├── BrightnessController.SetBrightness.response.json │ └── BrightnessController.AdjustBrightness.response.json ├── PercentageController │ ├── PercentageController.SetPercentage.request.json │ ├── PercentageController.AdjustPercentage.request.json │ └── PercentageController.response.json ├── PowerLevelController │ ├── PowerLevelController.SetPowerLevel.request.json │ ├── PowerLevelController.AdjustPowerLevel.request.json │ └── PowerLevelController.response.json ├── ThermostatController │ ├── ErrorResponse.ThermostatController.General.json │ ├── ThermostatController.SetThermostatMode.request.json │ ├── ThermostatController.SetTargetTemperature.SingleMode.request.json │ ├── ThermostatController.AdjustTargetTemperature.request.json │ ├── ErrorResponse.ThermostatController.REQUESTED_SETPOINTS_TOO_CLOSE.json │ ├── ThermostatController.SetTargetTemperature.DualMode.request.json │ ├── ThermostatController.SetTargetTemperature.TripleMode.request.json │ ├── ThermostatController.SetTargetTemperature.SingleMode.response.json │ ├── ThermostatController.SetTargetTemperature.DualMode.response.json │ └── ThermostatController.SetTargetTemperature.TripleMode.response.json ├── ColorController │ ├── ColorController.SetColor.request.json │ └── ColorController.SetColor.response.json ├── TemperatureSensor │ └── TemperatureSensor.response.json ├── CameraStreamController │ ├── CameraStreamController.request.json │ └── CameraStreamController.response.json └── ChangeReport │ └── ChangeReport.json ├── validation_schemas └── README.md ├── NOTICE.txt ├── .gitignore ├── README.md └── LICENSE.txt /sample_lambda/python/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /sample_lambda/python/jsonschema/tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /sample_backend/lambda/lambda_api/python/alexa/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /sample_backend/lambda/lambda_api/python/jsonschema/tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /sample_backend/lambda/lambda_api/python/alexa/skills/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /sample_lambda/python/jsonschema/__main__.py: -------------------------------------------------------------------------------- 1 | from jsonschema.cli import main 2 | main() 3 | -------------------------------------------------------------------------------- /sample_lambda/README.md: -------------------------------------------------------------------------------- 1 | Readme: https://github.com/alexa/alexa-smarthome/wiki/Sample-Lambda 2 | -------------------------------------------------------------------------------- /sample_async/README.md: -------------------------------------------------------------------------------- 1 | Readme: https://github.com/alexa/alexa-smarthome/wiki/Sample-Async-Messaging 2 | -------------------------------------------------------------------------------- /sample_messages/README.md: -------------------------------------------------------------------------------- 1 | Readme: https://github.com/alexa/alexa-smarthome/wiki/Sample-Messages 2 | -------------------------------------------------------------------------------- /validation_schemas/README.md: -------------------------------------------------------------------------------- 1 | Readme: https://github.com/alexa/alexa-smarthome/wiki/Validation-Schemas 2 | -------------------------------------------------------------------------------- /sample_backend/lambda/lambda_api/python/jsonschema/__main__.py: -------------------------------------------------------------------------------- 1 | from jsonschema.cli import main 2 | main() 3 | -------------------------------------------------------------------------------- /NOTICE.txt: -------------------------------------------------------------------------------- 1 | Alexa Smart Home Skill API PRIVATE Repository 2 | 3 | Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. 4 | -------------------------------------------------------------------------------- /sample_backend/docs/img/3.4.3-lambda-trigger.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ilhan-mstf/alexa-smarthome/master/sample_backend/docs/img/3.4.3-lambda-trigger.png -------------------------------------------------------------------------------- /sample_backend/docs/img/5.2.5-linking-dialog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ilhan-mstf/alexa-smarthome/master/sample_backend/docs/img/5.2.5-linking-dialog.png -------------------------------------------------------------------------------- /sample_backend/docs/img/2.1.10-lwa-web-settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ilhan-mstf/alexa-smarthome/master/sample_backend/docs/img/2.1.10-lwa-web-settings.png -------------------------------------------------------------------------------- /sample_backend/docs/img/2.1.14-lwa-web-settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ilhan-mstf/alexa-smarthome/master/sample_backend/docs/img/2.1.14-lwa-web-settings.png -------------------------------------------------------------------------------- /sample_backend/docs/img/5.2.3-smart-home-skill.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ilhan-mstf/alexa-smarthome/master/sample_backend/docs/img/5.2.3-smart-home-skill.png -------------------------------------------------------------------------------- /sample_backend/docs/img/6.2.4-thing-inspection.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ilhan-mstf/alexa-smarthome/master/sample_backend/docs/img/6.2.4-thing-inspection.png -------------------------------------------------------------------------------- /sample_messages/ErrorResponse/README.md: -------------------------------------------------------------------------------- 1 | These ErrorResponses can be sent as a response to any directive from Alexa, and can opportunistically include context properties. 2 | -------------------------------------------------------------------------------- /sample_backend/docs/img/alexa-sample-smarthome-108x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ilhan-mstf/alexa-smarthome/master/sample_backend/docs/img/alexa-sample-smarthome-108x.png -------------------------------------------------------------------------------- /sample_backend/docs/img/alexa-sample-smarthome-150x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ilhan-mstf/alexa-smarthome/master/sample_backend/docs/img/alexa-sample-smarthome-150x.png -------------------------------------------------------------------------------- /sample_backend/docs/img/alexa-sample-smarthome-512x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ilhan-mstf/alexa-smarthome/master/sample_backend/docs/img/alexa-sample-smarthome-512x.png -------------------------------------------------------------------------------- /sample_backend/docs/img/2.1.7-lwa-profile-configuration.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ilhan-mstf/alexa-smarthome/master/sample_backend/docs/img/2.1.7-lwa-profile-configuration.png -------------------------------------------------------------------------------- /sample_messages/LockController/README.md: -------------------------------------------------------------------------------- 1 | LockController provides some flexibilty for both fast and slow lock hardware. The directives are the same, but the responses can be different. -------------------------------------------------------------------------------- /sample_backend/docs/img/6.1.3.1-postman-manage-environments.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ilhan-mstf/alexa-smarthome/master/sample_backend/docs/img/6.1.3.1-postman-manage-environments.png -------------------------------------------------------------------------------- /sample_lambda/python/jsonschema/__pycache__/__init__.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ilhan-mstf/alexa-smarthome/master/sample_lambda/python/jsonschema/__pycache__/__init__.cpython-36.pyc -------------------------------------------------------------------------------- /sample_lambda/python/jsonschema/__pycache__/_format.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ilhan-mstf/alexa-smarthome/master/sample_lambda/python/jsonschema/__pycache__/_format.cpython-36.pyc -------------------------------------------------------------------------------- /sample_lambda/python/jsonschema/__pycache__/_utils.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ilhan-mstf/alexa-smarthome/master/sample_lambda/python/jsonschema/__pycache__/_utils.cpython-36.pyc -------------------------------------------------------------------------------- /sample_lambda/python/jsonschema/__pycache__/compat.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ilhan-mstf/alexa-smarthome/master/sample_lambda/python/jsonschema/__pycache__/compat.cpython-36.pyc -------------------------------------------------------------------------------- /sample_lambda/python/jsonschema/__pycache__/exceptions.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ilhan-mstf/alexa-smarthome/master/sample_lambda/python/jsonschema/__pycache__/exceptions.cpython-36.pyc -------------------------------------------------------------------------------- /sample_lambda/python/jsonschema/__pycache__/validators.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ilhan-mstf/alexa-smarthome/master/sample_lambda/python/jsonschema/__pycache__/validators.cpython-36.pyc -------------------------------------------------------------------------------- /sample_backend/lambda/lambda_api/python/jsonschema/_version.py: -------------------------------------------------------------------------------- 1 | 2 | # This file is automatically generated by setup.py. 3 | __version__ = '2.6.0' 4 | __sha__ = 'gd16713a' 5 | __revision__ = 'gd16713a' 6 | -------------------------------------------------------------------------------- /sample_lambda/python/jsonschema/__pycache__/_validators.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ilhan-mstf/alexa-smarthome/master/sample_lambda/python/jsonschema/__pycache__/_validators.cpython-36.pyc -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | endpoint-package.zip 3 | skill-package.zip 4 | sample_lambda/python/python.zip 5 | __pycache__ 6 | *.txt 7 | sample_lambda/python/alexa_smart_home_message_schema.json 8 | sample_lambda/python/lambda1.py 9 | -------------------------------------------------------------------------------- /sample_backend/lambda/lambda_api/python/endpoint_cloud/__init__.py: -------------------------------------------------------------------------------- 1 | from .api_auth import ApiAuth 2 | from .api_handler import ApiHandler 3 | from .api_response import ApiResponse 4 | from .api_response_body import ApiResponseBody 5 | from .api_utils import ApiUtils 6 | -------------------------------------------------------------------------------- /sample_backend/lambda/README.md: -------------------------------------------------------------------------------- 1 | # Lambda Functions for Sample Smart Home Backend 2 | 3 | ### lambda_api 4 | This is written in python and functions as the device cloud for an Alexa Smart Home Skill. 5 | 6 | ### lambda_smarthome 7 | The Smart Home Skill Lambda that routes directives from Alexa to the customer endpoint. 8 | 9 | -------------------------------------------------------------------------------- /sample_lambda/python/jsonschema/tests/compat.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | 4 | if sys.version_info[:2] < (2, 7): # pragma: no cover 5 | import unittest2 as unittest 6 | else: 7 | import unittest 8 | 9 | try: 10 | from unittest import mock 11 | except ImportError: 12 | import mock 13 | 14 | 15 | # flake8: noqa 16 | -------------------------------------------------------------------------------- /sample_backend/lambda/lambda_api/python/jsonschema/tests/compat.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | 4 | if sys.version_info[:2] < (2, 7): # pragma: no cover 5 | import unittest2 as unittest 6 | else: 7 | import unittest 8 | 9 | try: 10 | from unittest import mock 11 | except ImportError: 12 | import mock 13 | 14 | 15 | # flake8: noqa 16 | -------------------------------------------------------------------------------- /sample_backend/docs/README.md: -------------------------------------------------------------------------------- 1 | # Build an Alexa Smart Home Skill and Backend 2 | These instructions build a Sample Alexa Smart Home skill and backend using Cloud Formation using the API Gateway as an access layer to AWS IoT Things that represent the state of customer endpoints. 3 | 4 | 5 | To begin, start with [Step 0: Set Up the Required Accounts](000-setup-requirements.md) 6 | 7 | -------------------------------------------------------------------------------- /sample_messages/Authorization/Authorization.AcceptGrant.response.json: -------------------------------------------------------------------------------- 1 | { 2 | "event": { 3 | "header": { 4 | "namespace": "Alexa.Authorization", 5 | "name": "AcceptGrant.Response", 6 | "payloadVersion": "3", 7 | "messageId": "5f8a426e-01e4-4cc9-8b79-65f8bd0fd8a4" 8 | }, 9 | "payload": {} 10 | } 11 | } -------------------------------------------------------------------------------- /sample_backend/README.md: -------------------------------------------------------------------------------- 1 | # Build an Alexa Smart Home Skill and Backend 2 | These instructions build a Sample Alexa Smart Home skill and backend using Cloud Formation using the Amazon API Gateway as an access layer to AWS IoT Things that represent the state of customer endpoints. 3 | 4 | 5 | To begin, start with [Step 0: Set Up the Required Accounts](docs/000-setup-requirements.md) 6 | 7 | -------------------------------------------------------------------------------- /sample_backend/lambda/lambda_api/python/alexa/skills/smarthome/__init__.py: -------------------------------------------------------------------------------- 1 | from .alexa_acceptgrant_response import AlexaAcceptGrantResponse 2 | from .alexa_change_report import AlexaChangeReport 3 | from .alexa_discover_response import AlexaDiscoverResponse 4 | from .alexa_error import AlexaError 5 | from .alexa_power_controller import AlexaPowerController 6 | from .alexa_response import AlexaResponse 7 | from .alexa_utils import get_utc_timestamp 8 | -------------------------------------------------------------------------------- /sample_messages/DeferredResponse/DeferredResponse.json: -------------------------------------------------------------------------------- 1 | { 2 | "event": { 3 | "header": { 4 | "namespace": "Alexa", 5 | "name": "DeferredResponse", 6 | "payloadVersion": "3", 7 | "messageId": "5f8a426e-01e4-4cc9-8b79-65f8bd0fd8a4", 8 | "correlationToken": "dFMb0z+PgpgdDmluhJ1LddFvSqZ/jCc8ptlAKulUj90jSqg==" 9 | }, 10 | "payload": { 11 | "estimatedDeferralInSeconds": 20 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /sample_messages/Discovery/Discovery.request.json: -------------------------------------------------------------------------------- 1 | { 2 | "directive": { 3 | "header": { 4 | "namespace": "Alexa.Discovery", 5 | "name": "Discover", 6 | "payloadVersion": "3", 7 | "messageId": "1bd5d003-31b9-476f-ad03-71d471922820" 8 | }, 9 | "payload": { 10 | "scope": { 11 | "type": "BearerToken", 12 | "token": "access-token-from-skill" 13 | } 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /sample_messages/Authorization/ErrorResponse.Authorization.ACCEPT_GRANT_FAILED.json: -------------------------------------------------------------------------------- 1 | { 2 | "event": { 3 | "header": { 4 | "namespace": "Alexa.Authorization", 5 | "name": "ErrorResponse", 6 | "payloadVersion": "3", 7 | "messageId": "5f8a426e-01e4-4cc9-8b79-65f8bd0fd8a4", 8 | "correlationToken": "dFMb0z+PgpgdDmluhJ1LddFvSqZ/jCc8ptlAKulUj90jSqg==" 9 | }, 10 | "payload": { 11 | "type": "ACCEPT_GRANT_FAILED", 12 | "message": "Failed to handle the AcceptGrant directive because request to Login with Amazon failed" 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /sample_messages/StateReport/ReportState.json: -------------------------------------------------------------------------------- 1 | { 2 | "directive": { 3 | "header": { 4 | "namespace": "Alexa", 5 | "name": "ReportState", 6 | "payloadVersion": "3", 7 | "messageId": "1bd5d003-31b9-476f-ad03-71d471922820", 8 | "correlationToken": "dFMb0z+PgpgdDmluhJ1LddFvSqZ/jCc8ptlAKulUj90jSqg==" 9 | }, 10 | "endpoint": { 11 | "endpointId": "endpoint-001", 12 | "cookie": {}, 13 | "scope": { 14 | "type": "BearerToken", 15 | "token": "access-token-from-skill" 16 | } 17 | }, 18 | "payload": {} 19 | } 20 | } -------------------------------------------------------------------------------- /sample_messages/LockController/LockController.Lock.request.json: -------------------------------------------------------------------------------- 1 | { 2 | "directive": { 3 | "header": { 4 | "namespace": "Alexa.LockController", 5 | "name": "Lock", 6 | "payloadVersion": "3", 7 | "messageId": "1bd5d003-31b9-476f-ad03-71d471922820", 8 | "correlationToken": "dFMb0z+PgpgdDmluhJ1LddFvSqZ/jCc8ptlAKulUj90jSqg==" 9 | }, 10 | "endpoint": { 11 | "scope": { 12 | "type": "BearerToken", 13 | "token": "access-token-from-skill" 14 | }, 15 | "endpointId": "endpoint-001", 16 | "cookie": {} 17 | }, 18 | "payload": {} 19 | } 20 | } -------------------------------------------------------------------------------- /sample_messages/LockController/LockController.Unlock.request.json: -------------------------------------------------------------------------------- 1 | { 2 | "directive": { 3 | "header": { 4 | "namespace": "Alexa.LockController", 5 | "name": "Unlock", 6 | "payloadVersion": "3", 7 | "messageId": "1bd5d003-31b9-476f-ad03-71d471922820", 8 | "correlationToken": "dFMb0z+PgpgdDmluhJ1LddFvSqZ/jCc8ptlAKulUj90jSqg==" 9 | }, 10 | "endpoint": { 11 | "scope": { 12 | "type": "BearerToken", 13 | "token": "access-token-from-skill" 14 | }, 15 | "endpointId": "endpoint-001", 16 | "cookie": {} 17 | }, 18 | "payload": {} 19 | } 20 | } -------------------------------------------------------------------------------- /sample_messages/PowerController/PowerController.TurnOff.request.json: -------------------------------------------------------------------------------- 1 | { 2 | "directive": { 3 | "header": { 4 | "namespace": "Alexa.PowerController", 5 | "name": "TurnOff", 6 | "payloadVersion": "3", 7 | "messageId": "1bd5d003-31b9-476f-ad03-71d471922820", 8 | "correlationToken": "dFMb0z+PgpgdDmluhJ1LddFvSqZ/jCc8ptlAKulUj90jSqg==" 9 | }, 10 | "endpoint": { 11 | "scope": { 12 | "type": "BearerToken", 13 | "token": "access-token-from-skill" 14 | }, 15 | "endpointId": "endpoint-001", 16 | "cookie": {} 17 | }, 18 | "payload": {} 19 | } 20 | } -------------------------------------------------------------------------------- /sample_messages/PowerController/PowerController.TurnOn.request.json: -------------------------------------------------------------------------------- 1 | { 2 | "directive": { 3 | "header": { 4 | "namespace": "Alexa.PowerController", 5 | "name": "TurnOn", 6 | "payloadVersion": "3", 7 | "messageId": "1bd5d003-31b9-476f-ad03-71d471922820", 8 | "correlationToken": "dFMb0z+PgpgdDmluhJ1LddFvSqZ/jCc8ptlAKulUj90jSqg==" 9 | }, 10 | "endpoint": { 11 | "scope": { 12 | "type": "BearerToken", 13 | "token": "access-token-from-skill" 14 | }, 15 | "endpointId": "endpoint-001", 16 | "cookie": {} 17 | }, 18 | "payload": {} 19 | } 20 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Welcome to the Alexa Smart Home GitHub 2 | 3 | This repository contains sample code and resources for Alexa Smart Home developers. 4 | 5 | To get started, https://github.com/alexa/alexa-smarthome/wiki. 6 | 7 | For full developer documentation including how to get started and API references, please visit the [Alexa Smart Home developer pages](https://developer.amazon.com/alexa/smart-home). 8 | 9 | Questions? Comments? Please add to [Issues](https://github.com/alexa/alexa-smarthome/issues), thanks! 10 | 11 | # AWS re:Invent 12 | 13 | In 2017, we held a few sessions at AWS re:Invent, and you'll find the companion content to those sessions [here](https://github.com/alexa/alexa-smarthome/wiki/reinvent). 14 | -------------------------------------------------------------------------------- /sample_messages/PlaybackController/PlaybackController.Next.request.json: -------------------------------------------------------------------------------- 1 | { 2 | "directive": { 3 | "header": { 4 | "namespace": "Alexa.PlaybackController", 5 | "name": "Next", 6 | "payloadVersion": "3", 7 | "messageId": "1bd5d003-31b9-476f-ad03-71d471922820", 8 | "correlationToken": "dFMb0z+PgpgdDmluhJ1LddFvSqZ/jCc8ptlAKulUj90jSqg==" 9 | }, 10 | "endpoint": { 11 | "scope": { 12 | "type": "BearerToken", 13 | "token": "access-token-from-skill" 14 | }, 15 | "endpointId": "endpoint-001", 16 | "cookie": {} 17 | }, 18 | "payload": {} 19 | } 20 | } -------------------------------------------------------------------------------- /sample_messages/PlaybackController/PlaybackController.Pause.request.json: -------------------------------------------------------------------------------- 1 | { 2 | "directive": { 3 | "header": { 4 | "namespace": "Alexa.PlaybackController", 5 | "name": "Pause", 6 | "payloadVersion": "3", 7 | "messageId": "1bd5d003-31b9-476f-ad03-71d471922820", 8 | "correlationToken": "dFMb0z+PgpgdDmluhJ1LddFvSqZ/jCc8ptlAKulUj90jSqg==" 9 | }, 10 | "endpoint": { 11 | "scope": { 12 | "type": "BearerToken", 13 | "token": "access-token-from-skill" 14 | }, 15 | "endpointId": "endpoint-001", 16 | "cookie": {} 17 | }, 18 | "payload": {} 19 | } 20 | } -------------------------------------------------------------------------------- /sample_messages/PlaybackController/PlaybackController.Play.request.json: -------------------------------------------------------------------------------- 1 | { 2 | "directive": { 3 | "header": { 4 | "namespace": "Alexa.PlaybackController", 5 | "name": "Play", 6 | "payloadVersion": "3", 7 | "messageId": "1bd5d003-31b9-476f-ad03-71d471922820", 8 | "correlationToken": "dFMb0z+PgpgdDmluhJ1LddFvSqZ/jCc8ptlAKulUj90jSqg==" 9 | }, 10 | "endpoint": { 11 | "scope": { 12 | "type": "BearerToken", 13 | "token": "access-token-from-skill" 14 | }, 15 | "endpointId": "endpoint-001", 16 | "cookie": {} 17 | }, 18 | "payload": {} 19 | } 20 | } -------------------------------------------------------------------------------- /sample_messages/PlaybackController/PlaybackController.Stop.request.json: -------------------------------------------------------------------------------- 1 | { 2 | "directive": { 3 | "header": { 4 | "namespace": "Alexa.PlaybackController", 5 | "name": "Stop", 6 | "payloadVersion": "3", 7 | "messageId": "1bd5d003-31b9-476f-ad03-71d471922820", 8 | "correlationToken": "dFMb0z+PgpgdDmluhJ1LddFvSqZ/jCc8ptlAKulUj90jSqg==" 9 | }, 10 | "endpoint": { 11 | "scope": { 12 | "type": "BearerToken", 13 | "token": "access-token-from-skill" 14 | }, 15 | "endpointId": "endpoint-001", 16 | "cookie": {} 17 | }, 18 | "payload": {} 19 | } 20 | } -------------------------------------------------------------------------------- /sample_messages/SceneController/SceneController.Activate.request.json: -------------------------------------------------------------------------------- 1 | { 2 | "directive": { 3 | "header": { 4 | "namespace": "Alexa.SceneController", 5 | "name": "Activate", 6 | "payloadVersion": "3", 7 | "messageId": "1bd5d003-31b9-476f-ad03-71d471922820", 8 | "correlationToken": "dFMb0z+PgpgdDmluhJ1LddFvSqZ/jCc8ptlAKulUj90jSqg==" 9 | }, 10 | "endpoint": { 11 | "scope": { 12 | "type": "BearerToken", 13 | "token": "access-token-from-skill" 14 | }, 15 | "endpointId": "endpoint-001", 16 | "cookie": {} 17 | }, 18 | "payload": {} 19 | } 20 | } -------------------------------------------------------------------------------- /sample_messages/PlaybackController/PlaybackController.Rewind.request.json: -------------------------------------------------------------------------------- 1 | { 2 | "directive": { 3 | "header": { 4 | "namespace": "Alexa.PlaybackController", 5 | "name": "Rewind", 6 | "payloadVersion": "3", 7 | "messageId": "1bd5d003-31b9-476f-ad03-71d471922820", 8 | "correlationToken": "dFMb0z+PgpgdDmluhJ1LddFvSqZ/jCc8ptlAKulUj90jSqg==" 9 | }, 10 | "endpoint": { 11 | "scope": { 12 | "type": "BearerToken", 13 | "token": "access-token-from-skill" 14 | }, 15 | "endpointId": "endpoint-001", 16 | "cookie": {} 17 | }, 18 | "payload": {} 19 | } 20 | } -------------------------------------------------------------------------------- /sample_messages/SceneController/SceneController.Deactivate.request.json: -------------------------------------------------------------------------------- 1 | { 2 | "directive": { 3 | "header": { 4 | "namespace": "Alexa.SceneController", 5 | "name": "Deactivate", 6 | "payloadVersion": "3", 7 | "messageId": "1bd5d003-31b9-476f-ad03-71d471922820", 8 | "correlationToken": "dFMb0z+PgpgdDmluhJ1LddFvSqZ/jCc8ptlAKulUj90jSqg==" 9 | }, 10 | "endpoint": { 11 | "scope": { 12 | "type": "BearerToken", 13 | "token": "access-token-from-skill" 14 | }, 15 | "endpointId": "endpoint-001", 16 | "cookie": {} 17 | }, 18 | "payload": {} 19 | } 20 | } -------------------------------------------------------------------------------- /sample_messages/PlaybackController/PlaybackController.Previous.request.json: -------------------------------------------------------------------------------- 1 | { 2 | "directive": { 3 | "header": { 4 | "namespace": "Alexa.PlaybackController", 5 | "name": "Previous", 6 | "payloadVersion": "3", 7 | "messageId": "1bd5d003-31b9-476f-ad03-71d471922820", 8 | "correlationToken": "dFMb0z+PgpgdDmluhJ1LddFvSqZ/jCc8ptlAKulUj90jSqg==" 9 | }, 10 | "endpoint": { 11 | "scope": { 12 | "type": "BearerToken", 13 | "token": "access-token-from-skill" 14 | }, 15 | "endpointId": "endpoint-001", 16 | "cookie": {} 17 | }, 18 | "payload": {} 19 | } 20 | } -------------------------------------------------------------------------------- /sample_messages/PlaybackController/PlaybackController.StartOver.request.json: -------------------------------------------------------------------------------- 1 | { 2 | "directive": { 3 | "header": { 4 | "namespace": "Alexa.PlaybackController", 5 | "name": "StartOver", 6 | "payloadVersion": "3", 7 | "messageId": "1bd5d003-31b9-476f-ad03-71d471922820", 8 | "correlationToken": "dFMb0z+PgpgdDmluhJ1LddFvSqZ/jCc8ptlAKulUj90jSqg==" 9 | }, 10 | "endpoint": { 11 | "scope": { 12 | "type": "BearerToken", 13 | "token": "access-token-from-skill" 14 | }, 15 | "endpointId": "endpoint-001", 16 | "cookie": {} 17 | }, 18 | "payload": {} 19 | } 20 | } -------------------------------------------------------------------------------- /sample_messages/PlaybackController/PlaybackController.FastForward.request.json: -------------------------------------------------------------------------------- 1 | { 2 | "directive": { 3 | "header": { 4 | "namespace": "Alexa.PlaybackController", 5 | "name": "FastForward", 6 | "payloadVersion": "3", 7 | "messageId": "1bd5d003-31b9-476f-ad03-71d471922820", 8 | "correlationToken": "dFMb0z+PgpgdDmluhJ1LddFvSqZ/jCc8ptlAKulUj90jSqg==" 9 | }, 10 | "endpoint": { 11 | "scope": { 12 | "type": "BearerToken", 13 | "token": "access-token-from-skill" 14 | }, 15 | "endpointId": "endpoint-001", 16 | "cookie": {} 17 | }, 18 | "payload": {} 19 | } 20 | } -------------------------------------------------------------------------------- /sample_messages/Speaker/Speaker.SetMute.request.json: -------------------------------------------------------------------------------- 1 | { 2 | "directive": { 3 | "header": { 4 | "namespace": "Alexa.Speaker", 5 | "name": "SetMute", 6 | "payloadVersion": "3", 7 | "messageId": "1bd5d003-31b9-476f-ad03-71d471922820", 8 | "correlationToken": "dFMb0z+PgpgdDmluhJ1LddFvSqZ/jCc8ptlAKulUj90jSqg==" 9 | }, 10 | "endpoint": { 11 | "scope": { 12 | "type": "BearerToken", 13 | "token": "access-token-from-skill" 14 | }, 15 | "endpointId": "endpoint-001", 16 | "cookie": {} 17 | }, 18 | "payload": { 19 | "mute": true 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /sample_messages/Speaker/Speaker.SetVolume.request.json: -------------------------------------------------------------------------------- 1 | { 2 | "directive": { 3 | "header": { 4 | "namespace": "Alexa.Speaker", 5 | "name": "SetVolume", 6 | "payloadVersion": "3", 7 | "messageId": "1bd5d003-31b9-476f-ad03-71d471922820", 8 | "correlationToken": "dFMb0z+PgpgdDmluhJ1LddFvSqZ/jCc8ptlAKulUj90jSqg==" 9 | }, 10 | "endpoint": { 11 | "scope": { 12 | "type": "BearerToken", 13 | "token": "access-token-from-skill" 14 | }, 15 | "endpointId": "endpoint-001", 16 | "cookie": {} 17 | }, 18 | "payload": { 19 | "volume": 50 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /sample_messages/StepSpeaker/StepSpeaker.SetMute.request.json: -------------------------------------------------------------------------------- 1 | { 2 | "directive": { 3 | "header": { 4 | "namespace": "Alexa.StepSpeaker", 5 | "name": "SetMute", 6 | "payloadVersion": "3", 7 | "messageId": "1bd5d003-31b9-476f-ad03-71d471922820", 8 | "correlationToken": "dFMb0z+PgpgdDmluhJ1LddFvSqZ/jCc8ptlAKulUj90jSqg==" 9 | }, 10 | "endpoint": { 11 | "scope": { 12 | "type": "BearerToken", 13 | "token": "access-token-from-skill" 14 | }, 15 | "endpointId": "endpoint-001", 16 | "cookie": {} 17 | }, 18 | "payload": { 19 | "mute": true 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /sample_messages/StepSpeaker/StepSpeaker.AdjustVolume.request.json: -------------------------------------------------------------------------------- 1 | { 2 | "directive": { 3 | "header": { 4 | "namespace": "Alexa.StepSpeaker", 5 | "name": "AdjustVolume", 6 | "payloadVersion": "3", 7 | "messageId": "1bd5d003-31b9-476f-ad03-71d471922820", 8 | "correlationToken": "dFMb0z+PgpgdDmluhJ1LddFvSqZ/jCc8ptlAKulUj90jSqg==" 9 | }, 10 | "endpoint": { 11 | "scope": { 12 | "type": "BearerToken", 13 | "token": "access-token-from-skill" 14 | }, 15 | "endpointId": "endpoint-001", 16 | "cookie": {} 17 | }, 18 | "payload": { 19 | "volumeSteps": -20 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /sample_messages/Authorization/Authorization.AcceptGrant.request.json: -------------------------------------------------------------------------------- 1 | { 2 | "directive": { 3 | "header": { 4 | "namespace": "Alexa.Authorization", 5 | "name": "AcceptGrant", 6 | "payloadVersion": "3", 7 | "messageId": "1bd5d003-31b9-476f-ad03-71d471922820", 8 | "correlationToken": "dFMb0z+PgpgdDmluhJ1LddFvSqZ/jCc8ptlAKulUj90jSqg==" 9 | }, 10 | "payload": { 11 | "grant": { 12 | "type": "OAuth2.AuthorizationCode", 13 | "code": "ANUbUKCJqlBOpMhwYWxU" 14 | }, 15 | "grantee": { 16 | "type": "BearerToken", 17 | "token": "access-token-from-skill" 18 | } 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /sample_messages/InputController/InputController.SelectInput.request.json: -------------------------------------------------------------------------------- 1 | { 2 | "directive": { 3 | "header": { 4 | "namespace": "Alexa.InputController", 5 | "name": "SelectInput", 6 | "payloadVersion": "3", 7 | "messageId": "1bd5d003-31b9-476f-ad03-71d471922820", 8 | "correlationToken": "dFMb0z+PgpgdDmluhJ1LddFvSqZ/jCc8ptlAKulUj90jSqg==" 9 | }, 10 | "endpoint": { 11 | "scope": { 12 | "type": "BearerToken", 13 | "token": "access-token-from-skill" 14 | }, 15 | "endpointId": "endpoint-001", 16 | "cookie": {} 17 | }, 18 | "payload": { 19 | "input": "HDMI1" 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /sample_messages/ChannelController/ChannelController.SkipChannels.request.json: -------------------------------------------------------------------------------- 1 | { 2 | "directive": { 3 | "header": { 4 | "namespace": "Alexa.ChannelController", 5 | "name": "SkipChannels", 6 | "payloadVersion": "3", 7 | "messageId": "1bd5d003-31b9-476f-ad03-71d471922820", 8 | "correlationToken": "dFMb0z+PgpgdDmluhJ1LddFvSqZ/jCc8ptlAKulUj90jSqg==" 9 | }, 10 | "endpoint": { 11 | "scope": { 12 | "type": "BearerToken", 13 | "token": "access-token-from-skill" 14 | }, 15 | "endpointId": "endpoint-001", 16 | "cookie": {} 17 | }, 18 | "payload": { 19 | "channelCount": 5 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /sample_messages/ColorTemperatureController/ColorTemperatureController.DecreaseColorTemperature.request.json: -------------------------------------------------------------------------------- 1 | { 2 | "directive": { 3 | "header": { 4 | "namespace": "Alexa.ColorTemperatureController", 5 | "name": "DecreaseColorTemperature", 6 | "payloadVersion": "3", 7 | "messageId": "1bd5d003-31b9-476f-ad03-71d471922820", 8 | "correlationToken": "dFMb0z+PgpgdDmluhJ1LddFvSqZ/jCc8ptlAKulUj90jSqg==" 9 | }, 10 | "endpoint": { 11 | "scope": { 12 | "type": "BearerToken", 13 | "token": "access-token-from-skill" 14 | }, 15 | "endpointId": "endpoint-001", 16 | "cookie": {} 17 | }, 18 | "payload": {} 19 | } 20 | } -------------------------------------------------------------------------------- /sample_messages/ColorTemperatureController/ColorTemperatureController.IncreaseColorTemperature.request.json: -------------------------------------------------------------------------------- 1 | { 2 | "directive": { 3 | "header": { 4 | "namespace": "Alexa.ColorTemperatureController", 5 | "name": "IncreaseColorTemperature", 6 | "payloadVersion": "3", 7 | "messageId": "1bd5d003-31b9-476f-ad03-71d471922820", 8 | "correlationToken": "dFMb0z+PgpgdDmluhJ1LddFvSqZ/jCc8ptlAKulUj90jSqg==" 9 | }, 10 | "endpoint": { 11 | "scope": { 12 | "type": "BearerToken", 13 | "token": "access-token-from-skill" 14 | }, 15 | "endpointId": "endpoint-001", 16 | "cookie": {} 17 | }, 18 | "payload": {} 19 | } 20 | } -------------------------------------------------------------------------------- /sample_messages/BrightnessController/BrightnessController.SetBrightness.request.json: -------------------------------------------------------------------------------- 1 | { 2 | "directive": { 3 | "header": { 4 | "namespace": "Alexa.BrightnessController", 5 | "name": "SetBrightness", 6 | "payloadVersion": "3", 7 | "messageId": "1bd5d003-31b9-476f-ad03-71d471922820", 8 | "correlationToken": "dFMb0z+PgpgdDmluhJ1LddFvSqZ/jCc8ptlAKulUj90jSqg==" 9 | }, 10 | "endpoint": { 11 | "scope": { 12 | "type": "BearerToken", 13 | "token": "access-token-from-skill" 14 | }, 15 | "endpointId": "endpoint-001", 16 | "cookie": {} 17 | }, 18 | "payload": { 19 | "brightness": 75 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /sample_messages/PercentageController/PercentageController.SetPercentage.request.json: -------------------------------------------------------------------------------- 1 | { 2 | "directive": { 3 | "header": { 4 | "namespace": "Alexa.PercentageController", 5 | "name": "SetPercentage", 6 | "payloadVersion": "3", 7 | "messageId": "1bd5d003-31b9-476f-ad03-71d471922820", 8 | "correlationToken": "dFMb0z+PgpgdDmluhJ1LddFvSqZ/jCc8ptlAKulUj90jSqg==" 9 | }, 10 | "endpoint": { 11 | "scope": { 12 | "type": "BearerToken", 13 | "token": "access-token-from-skill" 14 | }, 15 | "endpointId": "endpoint-001", 16 | "cookie": {} 17 | }, 18 | "payload": { 19 | "percentage": 74 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /sample_messages/PowerLevelController/PowerLevelController.SetPowerLevel.request.json: -------------------------------------------------------------------------------- 1 | { 2 | "directive": { 3 | "header": { 4 | "namespace": "Alexa.PowerLevelController", 5 | "name": "SetPowerLevel", 6 | "payloadVersion": "3", 7 | "messageId": "1bd5d003-31b9-476f-ad03-71d471922820", 8 | "correlationToken": "dFMb0z+PgpgdDmluhJ1LddFvSqZ/jCc8ptlAKulUj90jSqg==" 9 | }, 10 | "endpoint": { 11 | "scope": { 12 | "type": "BearerToken", 13 | "token": "access-token-from-skill" 14 | }, 15 | "endpointId": "endpoint-001", 16 | "cookie": {} 17 | }, 18 | "payload": { 19 | "powerLevel": 42 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /sample_messages/Speaker/Speaker.AdjustVolume.request.json: -------------------------------------------------------------------------------- 1 | { 2 | "directive": { 3 | "header": { 4 | "namespace": "Alexa.Speaker", 5 | "name": "AdjustVolume", 6 | "payloadVersion": "3", 7 | "messageId": "1bd5d003-31b9-476f-ad03-71d471922820", 8 | "correlationToken": "dFMb0z+PgpgdDmluhJ1LddFvSqZ/jCc8ptlAKulUj90jSqg==" 9 | }, 10 | "endpoint": { 11 | "scope": { 12 | "type": "BearerToken", 13 | "token": "access-token-from-skill" 14 | }, 15 | "endpointId": "endpoint-001", 16 | "cookie": {} 17 | }, 18 | "payload": { 19 | "volume": -20, 20 | "volumeDefault": false 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /sample_messages/PowerLevelController/PowerLevelController.AdjustPowerLevel.request.json: -------------------------------------------------------------------------------- 1 | { 2 | "directive": { 3 | "header": { 4 | "namespace": "Alexa.PowerLevelController", 5 | "name": "AdjustPowerLevel", 6 | "payloadVersion": "3", 7 | "messageId": "1bd5d003-31b9-476f-ad03-71d471922820", 8 | "correlationToken": "dFMb0z+PgpgdDmluhJ1LddFvSqZ/jCc8ptlAKulUj90jSqg==" 9 | }, 10 | "endpoint": { 11 | "scope": { 12 | "type": "BearerToken", 13 | "token": "access-token-from-skill" 14 | }, 15 | "endpointId": "endpoint-001", 16 | "cookie": {} 17 | }, 18 | "payload": { 19 | "powerLevelDelta": 3 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /sample_messages/BrightnessController/BrightnessController.AdjustBrightness.request.json: -------------------------------------------------------------------------------- 1 | { 2 | "directive": { 3 | "header": { 4 | "namespace": "Alexa.BrightnessController", 5 | "name": "AdjustBrightness", 6 | "payloadVersion": "3", 7 | "messageId": "1bd5d003-31b9-476f-ad03-71d471922820", 8 | "correlationToken": "dFMb0z+PgpgdDmluhJ1LddFvSqZ/jCc8ptlAKulUj90jSqg==" 9 | }, 10 | "endpoint": { 11 | "scope": { 12 | "type": "BearerToken", 13 | "token": "access-token-from-skill" 14 | }, 15 | "endpointId": "endpoint-001", 16 | "cookie": {} 17 | }, 18 | "payload": { 19 | "brightnessDelta": -25 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /sample_messages/PercentageController/PercentageController.AdjustPercentage.request.json: -------------------------------------------------------------------------------- 1 | { 2 | "directive": { 3 | "header": { 4 | "namespace": "Alexa.PercentageController", 5 | "name": "AdjustPercentage", 6 | "payloadVersion": "3", 7 | "messageId": "1bd5d003-31b9-476f-ad03-71d471922820", 8 | "correlationToken": "dFMb0z+PgpgdDmluhJ1LddFvSqZ/jCc8ptlAKulUj90jSqg==" 9 | }, 10 | "endpoint": { 11 | "scope": { 12 | "type": "BearerToken", 13 | "token": "access-token-from-skill" 14 | }, 15 | "endpointId": "endpoint-001", 16 | "cookie": {} 17 | }, 18 | "payload": { 19 | "percentageDelta": -20 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /sample_messages/ErrorResponse/ErrorResponse.General.json: -------------------------------------------------------------------------------- 1 | { 2 | "event": { 3 | "header": { 4 | "namespace": "Alexa", 5 | "name": "ErrorResponse", 6 | "payloadVersion": "3", 7 | "messageId": "5f8a426e-01e4-4cc9-8b79-65f8bd0fd8a4", 8 | "correlationToken": "dFMb0z+PgpgdDmluhJ1LddFvSqZ/jCc8ptlAKulUj90jSqg==" 9 | }, 10 | "endpoint": { 11 | "scope": { 12 | "type": "BearerToken", 13 | "token": "access-token-from-Amazon" 14 | }, 15 | "endpointId": "endpoint-001" 16 | }, 17 | "payload": { 18 | "type": "ENDPOINT_UNREACHABLE", 19 | "message": "Unable to reach endpoint-001 because it appears to be offline" 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /sample_lambda/python/jsonschema/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | An implementation of JSON Schema for Python 3 | 4 | The main functionality is provided by the validator classes for each of the 5 | supported JSON Schema versions. 6 | 7 | Most commonly, :func:`validate` is the quickest way to simply validate a given 8 | instance under a schema, and will create a validator for you. 9 | 10 | """ 11 | 12 | from jsonschema.exceptions import ( 13 | ErrorTree, FormatError, RefResolutionError, SchemaError, ValidationError 14 | ) 15 | from jsonschema._format import ( 16 | FormatChecker, draft3_format_checker, draft4_format_checker, 17 | ) 18 | from jsonschema.validators import ( 19 | Draft3Validator, Draft4Validator, RefResolver, validate 20 | ) 21 | 22 | #from jsonschema._version import __version__ 23 | 24 | # flake8: noqa 25 | -------------------------------------------------------------------------------- /sample_messages/ErrorResponse/ErrorResponse.General.ENDPOINT_LOW_POWER.json: -------------------------------------------------------------------------------- 1 | { 2 | "event": { 3 | "header": { 4 | "namespace": "Alexa", 5 | "name": "ErrorResponse", 6 | "payloadVersion": "3", 7 | "messageId": "5f8a426e-01e4-4cc9-8b79-65f8bd0fd8a4", 8 | "correlationToken": "dFMb0z+PgpgdDmluhJ1LddFvSqZ/jCc8ptlAKulUj90jSqg==" 9 | }, 10 | "endpoint": { 11 | "scope": { 12 | "type": "BearerToken", 13 | "token": "access-token-from-Amazon" 14 | }, 15 | "endpointId": "endpoint-001" 16 | }, 17 | "payload": { 18 | "type": "ENDPOINT_LOW_POWER", 19 | "message": "The lock battery is low", 20 | "percentageState": 5 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /sample_backend/lambda/lambda_api/python/jsonschema/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | An implementation of JSON Schema for Python 3 | 4 | The main functionality is provided by the validator classes for each of the 5 | supported JSON Schema versions. 6 | 7 | Most commonly, :func:`validate` is the quickest way to simply validate a given 8 | instance under a schema, and will create a validator for you. 9 | 10 | """ 11 | 12 | from jsonschema.exceptions import ( 13 | ErrorTree, FormatError, RefResolutionError, SchemaError, ValidationError 14 | ) 15 | from jsonschema._format import ( 16 | FormatChecker, draft3_format_checker, draft4_format_checker, 17 | ) 18 | from jsonschema.validators import ( 19 | Draft3Validator, Draft4Validator, RefResolver, validate 20 | ) 21 | 22 | from jsonschema._version import __version__ 23 | 24 | # flake8: noqa 25 | -------------------------------------------------------------------------------- /sample_messages/ColorTemperatureController/ColorTemperatureController.SetColorTemperature.request.json: -------------------------------------------------------------------------------- 1 | { 2 | "directive": { 3 | "header": { 4 | "namespace": "Alexa.ColorTemperatureController", 5 | "name": "SetColorTemperature", 6 | "payloadVersion": "3", 7 | "messageId": "1bd5d003-31b9-476f-ad03-71d471922820", 8 | "correlationToken": "dFMb0z+PgpgdDmluhJ1LddFvSqZ/jCc8ptlAKulUj90jSqg==" 9 | }, 10 | "endpoint": { 11 | "scope": { 12 | "type": "BearerToken", 13 | "token": "access-token-from-skill" 14 | }, 15 | "endpointId": "endpoint-001", 16 | "cookie": {} 17 | }, 18 | "payload": { 19 | "colorTemperatureInKelvin": 5000 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /sample_backend/lambda/lambda_api/python/alexa/skills/smarthome/alexa_utils.py: -------------------------------------------------------------------------------- 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 | import time 15 | 16 | 17 | def get_utc_timestamp(seconds=None): 18 | return time.strftime('%Y-%m-%dT%H:%M:%S.00Z', time.gmtime(seconds)) 19 | 20 | 21 | -------------------------------------------------------------------------------- /sample_messages/ThermostatController/ErrorResponse.ThermostatController.General.json: -------------------------------------------------------------------------------- 1 | { 2 | "event": { 3 | "header": { 4 | "namespace": "Alexa.ThermostatController", 5 | "name": "ErrorResponse", 6 | "payloadVersion": "3", 7 | "messageId": "5f8a426e-01e4-4cc9-8b79-65f8bd0fd8a4", 8 | "correlationToken": "dFMb0z+PgpgdDmluhJ1LddFvSqZ/jCc8ptlAKulUj90jSqg==" 9 | }, 10 | "endpoint": { 11 | "scope": { 12 | "type": "BearerToken", 13 | "token": "access-token-from-Amazon" 14 | }, 15 | "endpointId": "endpoint-001" 16 | }, 17 | "payload": { 18 | "type": "THERMOSTAT_IS_OFF", 19 | "message": "The thermostat is off, cannot turn on due to safety reasons" 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /sample_messages/ThermostatController/ThermostatController.SetThermostatMode.request.json: -------------------------------------------------------------------------------- 1 | { 2 | "directive": { 3 | "header": { 4 | "namespace": "Alexa.ThermostatController", 5 | "name": "SetThermostatMode", 6 | "payloadVersion": "3", 7 | "messageId": "1bd5d003-31b9-476f-ad03-71d471922820", 8 | "correlationToken": "dFMb0z+PgpgdDmluhJ1LddFvSqZ/jCc8ptlAKulUj90jSqg==" 9 | }, 10 | "endpoint": { 11 | "scope": { 12 | "type": "BearerToken", 13 | "token": "access-token-from-skill" 14 | }, 15 | "endpointId": "endpoint-001", 16 | "cookie": {} 17 | }, 18 | "payload": { 19 | "thermostatMode": { 20 | "value": "COOL" 21 | } 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /sample_messages/ColorController/ColorController.SetColor.request.json: -------------------------------------------------------------------------------- 1 | { 2 | "directive": { 3 | "header": { 4 | "namespace": "Alexa.ColorController", 5 | "name": "SetColor", 6 | "payloadVersion": "3", 7 | "messageId": "1bd5d003-31b9-476f-ad03-71d471922820", 8 | "correlationToken": "dFMb0z+PgpgdDmluhJ1LddFvSqZ/jCc8ptlAKulUj90jSqg==" 9 | }, 10 | "endpoint": { 11 | "scope": { 12 | "type": "BearerToken", 13 | "token": "access-token-from-skill" 14 | }, 15 | "endpointId": "endpoint-001", 16 | "cookie": {} 17 | }, 18 | "payload": { 19 | "color": { 20 | "hue": 350.5, 21 | "saturation": 0.7138, 22 | "brightness": 0.6524 23 | } 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /sample_messages/ThermostatController/ThermostatController.SetTargetTemperature.SingleMode.request.json: -------------------------------------------------------------------------------- 1 | { 2 | "directive": { 3 | "header": { 4 | "namespace": "Alexa.ThermostatController", 5 | "name": "SetTargetTemperature", 6 | "payloadVersion": "3", 7 | "messageId": "1bd5d003-31b9-476f-ad03-71d471922820", 8 | "correlationToken": "dFMb0z+PgpgdDmluhJ1LddFvSqZ/jCc8ptlAKulUj90jSqg==" 9 | }, 10 | "endpoint": { 11 | "scope": { 12 | "type": "BearerToken", 13 | "token": "access-token-from-skill" 14 | }, 15 | "endpointId": "endpoint-001", 16 | "cookie": {} 17 | }, 18 | "payload": { 19 | "targetSetpoint": { 20 | "value": 25.0, 21 | "scale": "CELSIUS" 22 | } 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /sample_messages/ThermostatController/ThermostatController.AdjustTargetTemperature.request.json: -------------------------------------------------------------------------------- 1 | { 2 | "directive": { 3 | "header": { 4 | "namespace": "Alexa.ThermostatController", 5 | "name": "AdjustTargetTemperature", 6 | "payloadVersion": "3", 7 | "messageId": "1bd5d003-31b9-476f-ad03-71d471922820", 8 | "correlationToken": "dFMb0z+PgpgdDmluhJ1LddFvSqZ/jCc8ptlAKulUj90jSqg==" 9 | }, 10 | "endpoint": { 11 | "scope": { 12 | "type": "BearerToken", 13 | "token": "access-token-from-skill" 14 | }, 15 | "endpointId": "endpoint-001", 16 | "cookie": {} 17 | }, 18 | "payload": { 19 | "targetSetpointDelta": { 20 | "value": -2.0, 21 | "scale": "FAHRENHEIT" 22 | } 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /sample_messages/ErrorResponse/ErrorResponse.General.VALUE_OUT_OF_RANGE.json: -------------------------------------------------------------------------------- 1 | { 2 | "event": { 3 | "header": { 4 | "namespace": "Alexa", 5 | "name": "ErrorResponse", 6 | "payloadVersion": "3", 7 | "messageId": "5f8a426e-01e4-4cc9-8b79-65f8bd0fd8a4", 8 | "correlationToken": "dFMb0z+PgpgdDmluhJ1LddFvSqZ/jCc8ptlAKulUj90jSqg==" 9 | }, 10 | "endpoint": { 11 | "scope": { 12 | "type": "BearerToken", 13 | "token": "access-token-from-Amazon" 14 | }, 15 | "endpointId": "endpoint-001" 16 | }, 17 | "payload": { 18 | "type": "VALUE_OUT_OF_RANGE", 19 | "message": "The color temperature cannot be set to 500", 20 | "validRange": { 21 | "minimumValue": 1000, 22 | "maximumValue": 10000 23 | } 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /sample_backend/lambda/lambda_api/python/endpoint_cloud/api_utils.py: -------------------------------------------------------------------------------- 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 | import datetime 15 | 16 | 17 | class ApiUtils: 18 | 19 | @staticmethod 20 | def get_time_utc(): 21 | """ 22 | An ISO 8601 formatted string in UTC (e.g. YYYY-MM-DDThh:mm:ss.sD) 23 | :return: string date time 24 | """ 25 | return datetime.datetime.utcnow().isoformat() 26 | -------------------------------------------------------------------------------- /sample_messages/ThermostatController/ErrorResponse.ThermostatController.REQUESTED_SETPOINTS_TOO_CLOSE.json: -------------------------------------------------------------------------------- 1 | { 2 | "event": { 3 | "header": { 4 | "namespace": "Alexa.ThermostatController", 5 | "name": "ErrorResponse", 6 | "payloadVersion": "3", 7 | "messageId": "5f8a426e-01e4-4cc9-8b79-65f8bd0fd8a4", 8 | "correlationToken": "dFMb0z+PgpgdDmluhJ1LddFvSqZ/jCc8ptlAKulUj90jSqg==" 9 | }, 10 | "endpoint": { 11 | "scope": { 12 | "type": "BearerToken", 13 | "token": "access-token-from-Amazon" 14 | }, 15 | "endpointId": "endpoint-001" 16 | }, 17 | "payload": { 18 | "type": "REQUESTED_SETPOINTS_TOO_CLOSE", 19 | "message": "The requested temperature results in setpoints too close", 20 | "minimumTemperatureDelta": { 21 | "value": 2.0, 22 | "scale": "CELSIUS" 23 | } 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /sample_backend/lambda/lambda_api/python/endpoint_cloud/api_message.py: -------------------------------------------------------------------------------- 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 | 15 | class ApiMessage: 16 | 17 | def __init__(self, **kwargs): 18 | self.context = kwargs.get('context', {}) 19 | self.header = kwargs.get('header', {}) 20 | self.endpoint = kwargs.get('endpoint', {}) 21 | self.payload = kwargs.get('payload', {}) 22 | 23 | def validate(self): 24 | 25 | return False -------------------------------------------------------------------------------- /sample_messages/ThermostatController/ThermostatController.SetTargetTemperature.DualMode.request.json: -------------------------------------------------------------------------------- 1 | { 2 | "directive": { 3 | "header": { 4 | "namespace": "Alexa.ThermostatController", 5 | "name": "SetTargetTemperature", 6 | "payloadVersion": "3", 7 | "messageId": "1bd5d003-31b9-476f-ad03-71d471922820", 8 | "correlationToken": "dFMb0z+PgpgdDmluhJ1LddFvSqZ/jCc8ptlAKulUj90jSqg==" 9 | }, 10 | "endpoint": { 11 | "scope": { 12 | "type": "BearerToken", 13 | "token": "access-token-from-skill" 14 | }, 15 | "endpointId": "endpoint-001", 16 | "cookie": {} 17 | }, 18 | "payload": { 19 | "lowerSetpoint": { 20 | "value": 68.0, 21 | "scale": "FAHRENHEIT" 22 | }, 23 | "upperSetpoint": { 24 | "value": 78.0, 25 | "scale": "FAHRENHEIT" 26 | } 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /sample_messages/StepSpeaker/StepSpeaker.response.json: -------------------------------------------------------------------------------- 1 | { 2 | "context": { 3 | "properties": [ 4 | { 5 | "namespace": "Alexa.EndpointHealth", 6 | "name": "connectivity", 7 | "value": { 8 | "value": "OK" 9 | }, 10 | "timeOfSample": "2017-09-27T18:30:30.45Z", 11 | "uncertaintyInMilliseconds": 200 12 | } 13 | ] 14 | }, 15 | "event": { 16 | "header": { 17 | "namespace": "Alexa", 18 | "name": "Response", 19 | "payloadVersion": "3", 20 | "messageId": "5f8a426e-01e4-4cc9-8b79-65f8bd0fd8a4", 21 | "correlationToken": "dFMb0z+PgpgdDmluhJ1LddFvSqZ/jCc8ptlAKulUj90jSqg==" 22 | }, 23 | "endpoint": { 24 | "scope": { 25 | "type": "BearerToken", 26 | "token": "access-token-from-Amazon" 27 | }, 28 | "endpointId": "endpoint-001" 29 | }, 30 | "payload": {} 31 | } 32 | } -------------------------------------------------------------------------------- /sample_messages/PlaybackController/PlaybackController.response.json: -------------------------------------------------------------------------------- 1 | { 2 | "context": { 3 | "properties": [ 4 | { 5 | "namespace": "Alexa.EndpointHealth", 6 | "name": "connectivity", 7 | "value": { 8 | "value": "OK" 9 | }, 10 | "timeOfSample": "2017-09-27T18:30:30.45Z", 11 | "uncertaintyInMilliseconds": 200 12 | } 13 | ] 14 | }, 15 | "event": { 16 | "header": { 17 | "namespace": "Alexa", 18 | "name": "Response", 19 | "payloadVersion": "3", 20 | "messageId": "5f8a426e-01e4-4cc9-8b79-65f8bd0fd8a4", 21 | "correlationToken": "dFMb0z+PgpgdDmluhJ1LddFvSqZ/jCc8ptlAKulUj90jSqg==" 22 | }, 23 | "endpoint": { 24 | "scope": { 25 | "type": "BearerToken", 26 | "token": "access-token-from-Amazon" 27 | }, 28 | "endpointId": "endpoint-001" 29 | }, 30 | "payload": {} 31 | } 32 | } -------------------------------------------------------------------------------- /sample_messages/ChannelController/ChannelController.ChangeChannel.request.json: -------------------------------------------------------------------------------- 1 | { 2 | "directive": { 3 | "header": { 4 | "namespace": "Alexa.ChannelController", 5 | "name": "ChangeChannel", 6 | "payloadVersion": "3", 7 | "messageId": "1bd5d003-31b9-476f-ad03-71d471922820", 8 | "correlationToken": "dFMb0z+PgpgdDmluhJ1LddFvSqZ/jCc8ptlAKulUj90jSqg==" 9 | }, 10 | "endpoint": { 11 | "scope": { 12 | "type": "BearerToken", 13 | "token": "access-token-from-skill" 14 | }, 15 | "endpointId": "endpoint-001", 16 | "cookie": {} 17 | }, 18 | "payload": { 19 | "channel": { 20 | "number": "1234", 21 | "callSign": "KSTATION1", 22 | "affiliateCallSign": "KSTATION2", 23 | "uri": "someUrl" 24 | }, 25 | "channelMetadata": { 26 | "name": "Alternate Channel Name", 27 | "image": "urlToImage" 28 | } 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /sample_backend/docs/config.txt: -------------------------------------------------------------------------------- 1 | Set in Step 1 - A unique API ID and name for the Alexa Smart Home Skill Lambda function 2 | [EndpointApiId] 3 | XXXXXXXXXX 4 | 5 | [SkillLambdaArn] 6 | arn:aws:lambda:us-east-1:XXXXXXXXXXXX:function:SampleSkillAdapter 7 | 8 | Set in Step 2 - The Client ID and Secret from the LWA Security Profile 9 | [Login with Amazon Client ID] 10 | amzn1.application-oa2-client.XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX 11 | 12 | [Login with Amazon Client Secret] 13 | XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX 14 | 15 | Set in Step 3 - A unique ID for the Alexa Smart Home Skill 16 | [Alexa Skill Application Id] 17 | amzn1.ask.skill.XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX 18 | 19 | Set in Step 4 - The Messaging Client and Secret from the Alexa Smart Home Skill 20 | [Alexa Skill Messaging Client Id] 21 | amzn1.application-oa2-client.XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX 22 | 23 | [Alexa Skill Messaging Client Secret] 24 | XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX 25 | 26 | Set in Step 6 - The profile user_id of the User 27 | [user_id] 28 | amzn1.account.XXXXXXXXXXXXXXXXXXXXXXXXXXXX -------------------------------------------------------------------------------- /sample_messages/ErrorResponse/ErrorResponse.General.TEMPERATURE_VALUE_OUT_OF_RANGE.json: -------------------------------------------------------------------------------- 1 | { 2 | "event": { 3 | "header": { 4 | "namespace": "Alexa", 5 | "name": "ErrorResponse", 6 | "payloadVersion": "3", 7 | "messageId": "5f8a426e-01e4-4cc9-8b79-65f8bd0fd8a4", 8 | "correlationToken": "dFMb0z+PgpgdDmluhJ1LddFvSqZ/jCc8ptlAKulUj90jSqg==" 9 | }, 10 | "endpoint": { 11 | "scope": { 12 | "type": "BearerToken", 13 | "token": "access-token-from-Amazon" 14 | }, 15 | "endpointId": "endpoint-001" 16 | }, 17 | "payload": { 18 | "type": "TEMPERATURE_VALUE_OUT_OF_RANGE", 19 | "message": "The requested temperature of -15 is out of range", 20 | "validRange": { 21 | "minimumValue": { 22 | "value": 15.0, 23 | "scale": "CELSIUS" 24 | }, 25 | "maximumValue": { 26 | "value": 30.0, 27 | "scale": "CELSIUS" 28 | } 29 | } 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /sample_messages/ThermostatController/ThermostatController.SetTargetTemperature.TripleMode.request.json: -------------------------------------------------------------------------------- 1 | { 2 | "directive": { 3 | "header": { 4 | "namespace": "Alexa.ThermostatController", 5 | "name": "SetTargetTemperature", 6 | "payloadVersion": "3", 7 | "messageId": "1bd5d003-31b9-476f-ad03-71d471922820", 8 | "correlationToken": "dFMb0z+PgpgdDmluhJ1LddFvSqZ/jCc8ptlAKulUj90jSqg==" 9 | }, 10 | "endpoint": { 11 | "scope": { 12 | "type": "BearerToken", 13 | "token": "access-token-from-skill" 14 | }, 15 | "endpointId": "endpoint-001", 16 | "cookie": {} 17 | }, 18 | "payload": { 19 | "targetSetpoint": { 20 | "value": 73.0, 21 | "scale": "FAHRENHEIT" 22 | }, 23 | "lowerSetpoint": { 24 | "value": 68.0, 25 | "scale": "FAHRENHEIT" 26 | }, 27 | "upperSetpoint": { 28 | "value": 78.0, 29 | "scale": "FAHRENHEIT" 30 | } 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /sample_backend/lambda/lambda_api/python/endpoint_cloud/api_response_body.py: -------------------------------------------------------------------------------- 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 | import json 15 | 16 | 17 | class ApiResponseBody: 18 | 19 | def __init__(self, **kwargs): 20 | self.body = {} 21 | self.result = kwargs.get('result', "OK") 22 | self.message = kwargs.get('message', "") 23 | 24 | def __repr__(self): 25 | return self.create() 26 | 27 | def create(self): 28 | self.body['result'] = self.result 29 | if self.message: # Check for an empty message 30 | self.body['message'] = self.message 31 | 32 | return json.dumps(self.body) 33 | 34 | -------------------------------------------------------------------------------- /sample_backend/docs/009-setup-cleanup.md: -------------------------------------------------------------------------------- 1 | # Step 9: Clean Up 2 | Clean up the resources used during the workshop and tear down the Endpoint Device backend. 3 | 4 | 5 | #### 9.1 Delete the _Sample-Smart-Home-Backend_ Stack and Alexa Skill 6 | 7 | 9.1.1 Browse to Navigate to the Cloud Formation Console at https://console.aws.amazon.com/cloudformation/home?region=us-east-1. 8 | 9 | 9.1.2 From the list of Stacks, check the box next to only the _Sample-Smart-Home-Backend_ stack. 10 | 11 | 9.1.3 From the Action drop down menu, select **Delete Stack**. 12 | 13 | 9.1.4 From the list of Stacks, check the box next to only the _Sample-Smart-Home-Backend_ stack. 14 | 15 | 9.1.5 Go to your list of Alexa skills at https://developer.amazon.com/edw/home.html#/skills and locate the _Sample Smart Home Skill_ and click the **Delete** link. 16 | 17 | #### 9.2 Delete local files 18 | 19 | 9.2.1 Delete the _Alexa-SmartHome-Sample_ directory and its contents from the desktop. 20 | 21 | ____ 22 | Return to the [README](../README.md). 23 | -------------------------------------------------------------------------------- /sample_messages/SceneController/SceneController.ActivationStarted.message.json: -------------------------------------------------------------------------------- 1 | { 2 | "context": { 3 | "properties": [ 4 | { 5 | "namespace": "Alexa.EndpointHealth", 6 | "name": "connectivity", 7 | "value": { 8 | "value": "OK" 9 | }, 10 | "timeOfSample": "2017-09-27T18:30:30.45Z", 11 | "uncertaintyInMilliseconds": 200 12 | } 13 | ] 14 | }, 15 | "event": { 16 | "header": { 17 | "namespace": "Alexa.SceneController", 18 | "name": "ActivationStarted", 19 | "payloadVersion": "3", 20 | "messageId": "5f8a426e-01e4-4cc9-8b79-65f8bd0fd8a4", 21 | "correlationToken": "dFMb0z+PgpgdDmluhJ1LddFvSqZ/jCc8ptlAKulUj90jSqg==" 22 | }, 23 | "endpoint": { 24 | "scope": { 25 | "type": "BearerToken", 26 | "token": "access-token-from-Amazon" 27 | }, 28 | "endpointId": "endpoint-001" 29 | }, 30 | "payload": { 31 | "cause": { 32 | "type": "VOICE_INTERACTION" 33 | }, 34 | "timestamp": "2017-09-27T18:30:30.45Z" 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /sample_messages/SceneController/SceneController.DeactivationStarted.message.json: -------------------------------------------------------------------------------- 1 | { 2 | "context": { 3 | "properties": [ 4 | { 5 | "namespace": "Alexa.EndpointHealth", 6 | "name": "connectivity", 7 | "value": { 8 | "value": "OK" 9 | }, 10 | "timeOfSample": "2017-09-27T18:30:30.45Z", 11 | "uncertaintyInMilliseconds": 200 12 | } 13 | ] 14 | }, 15 | "event": { 16 | "header": { 17 | "namespace": "Alexa.SceneController", 18 | "name": "DeactivationStarted", 19 | "payloadVersion": "3", 20 | "messageId": "5f8a426e-01e4-4cc9-8b79-65f8bd0fd8a4", 21 | "correlationToken": "dFMb0z+PgpgdDmluhJ1LddFvSqZ/jCc8ptlAKulUj90jSqg==" 22 | }, 23 | "endpoint": { 24 | "scope": { 25 | "type": "BearerToken", 26 | "token": "access-token-from-Amazon" 27 | }, 28 | "endpointId": "endpoint-001" 29 | }, 30 | "payload": { 31 | "cause": { 32 | "type": "APP_INTERACTION" 33 | }, 34 | "timestamp": "2017-09-27T18:30:30.45Z" 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /sample_messages/LockController/LockController.Lock.response.json: -------------------------------------------------------------------------------- 1 | { 2 | "context": { 3 | "properties": [ 4 | { 5 | "namespace": "Alexa.LockController", 6 | "name": "lockState", 7 | "value": "LOCKED", 8 | "timeOfSample": "2017-09-27T18:30:30.45Z", 9 | "uncertaintyInMilliseconds": 200 10 | }, 11 | { 12 | "namespace": "Alexa.EndpointHealth", 13 | "name": "connectivity", 14 | "value": { 15 | "value": "OK" 16 | }, 17 | "timeOfSample": "2017-09-27T18:30:30.45Z", 18 | "uncertaintyInMilliseconds": 200 19 | } 20 | ] 21 | }, 22 | "event": { 23 | "header": { 24 | "namespace": "Alexa", 25 | "name": "Response", 26 | "payloadVersion": "3", 27 | "messageId": "5f8a426e-01e4-4cc9-8b79-65f8bd0fd8a4", 28 | "correlationToken": "dFMb0z+PgpgdDmluhJ1LddFvSqZ/jCc8ptlAKulUj90jSqg==" 29 | }, 30 | "endpoint": { 31 | "scope": { 32 | "type": "BearerToken", 33 | "token": "access-token-from-Amazon" 34 | }, 35 | "endpointId": "endpoint-001" 36 | }, 37 | "payload": {} 38 | } 39 | } -------------------------------------------------------------------------------- /sample_messages/LockController/LockController.Unlock.response.json: -------------------------------------------------------------------------------- 1 | { 2 | "context": { 3 | "properties": [ 4 | { 5 | "namespace": "Alexa.LockController", 6 | "name": "lockState", 7 | "value": "UNLOCKED", 8 | "timeOfSample": "2017-09-27T18:30:30.45Z", 9 | "uncertaintyInMilliseconds": 200 10 | }, 11 | { 12 | "namespace": "Alexa.EndpointHealth", 13 | "name": "connectivity", 14 | "value": { 15 | "value": "OK" 16 | }, 17 | "timeOfSample": "2017-09-27T18:30:30.45Z", 18 | "uncertaintyInMilliseconds": 200 19 | } 20 | ] 21 | }, 22 | "event": { 23 | "header": { 24 | "namespace": "Alexa", 25 | "name": "Response", 26 | "payloadVersion": "3", 27 | "messageId": "5f8a426e-01e4-4cc9-8b79-65f8bd0fd8a4", 28 | "correlationToken": "dFMb0z+PgpgdDmluhJ1LddFvSqZ/jCc8ptlAKulUj90jSqg==" 29 | }, 30 | "endpoint": { 31 | "scope": { 32 | "type": "BearerToken", 33 | "token": "access-token-from-Amazon" 34 | }, 35 | "endpointId": "endpoint-001" 36 | }, 37 | "payload": {} 38 | } 39 | } -------------------------------------------------------------------------------- /sample_messages/PowerController/PowerController.TurnOff.response.json: -------------------------------------------------------------------------------- 1 | { 2 | "context": { 3 | "properties": [ 4 | { 5 | "namespace": "Alexa.PowerController", 6 | "name": "powerState", 7 | "value": "OFF", 8 | "timeOfSample": "2017-09-27T18:30:30.45Z", 9 | "uncertaintyInMilliseconds": 200 10 | }, 11 | { 12 | "namespace": "Alexa.EndpointHealth", 13 | "name": "connectivity", 14 | "value": { 15 | "value": "OK" 16 | }, 17 | "timeOfSample": "2017-09-27T18:30:30.45Z", 18 | "uncertaintyInMilliseconds": 200 19 | } 20 | ] 21 | }, 22 | "event": { 23 | "header": { 24 | "namespace": "Alexa", 25 | "name": "Response", 26 | "payloadVersion": "3", 27 | "messageId": "5f8a426e-01e4-4cc9-8b79-65f8bd0fd8a4", 28 | "correlationToken": "dFMb0z+PgpgdDmluhJ1LddFvSqZ/jCc8ptlAKulUj90jSqg==" 29 | }, 30 | "endpoint": { 31 | "scope": { 32 | "type": "BearerToken", 33 | "token": "access-token-from-Amazon" 34 | }, 35 | "endpointId": "endpoint-001" 36 | }, 37 | "payload": {} 38 | } 39 | } -------------------------------------------------------------------------------- /sample_messages/PowerController/PowerController.TurnOn.response.json: -------------------------------------------------------------------------------- 1 | { 2 | "context": { 3 | "properties": [ 4 | { 5 | "namespace": "Alexa.PowerController", 6 | "name": "powerState", 7 | "value": "ON", 8 | "timeOfSample": "2017-09-27T18:30:30.45Z", 9 | "uncertaintyInMilliseconds": 200 10 | }, 11 | { 12 | "namespace": "Alexa.EndpointHealth", 13 | "name": "connectivity", 14 | "value": { 15 | "value": "OK" 16 | }, 17 | "timeOfSample": "2017-09-27T18:30:30.45Z", 18 | "uncertaintyInMilliseconds": 200 19 | } 20 | ] 21 | }, 22 | "event": { 23 | "header": { 24 | "namespace": "Alexa", 25 | "name": "Response", 26 | "payloadVersion": "3", 27 | "messageId": "5f8a426e-01e4-4cc9-8b79-65f8bd0fd8a4", 28 | "correlationToken": "dFMb0z+PgpgdDmluhJ1LddFvSqZ/jCc8ptlAKulUj90jSqg==" 29 | }, 30 | "endpoint": { 31 | "scope": { 32 | "type": "BearerToken", 33 | "token": "access-token-from-Amazon" 34 | }, 35 | "endpointId": "endpoint-001" 36 | }, 37 | "payload": {} 38 | } 39 | } -------------------------------------------------------------------------------- /sample_messages/InputController/InputController.SelectInput.response.json: -------------------------------------------------------------------------------- 1 | { 2 | "context": { 3 | "properties": [ 4 | { 5 | "namespace": "Alexa.InputController", 6 | "name": "input", 7 | "value": "HDMI1", 8 | "timeOfSample": "2017-09-27T18:30:30.45Z", 9 | "uncertaintyInMilliseconds": 200 10 | }, 11 | { 12 | "namespace": "Alexa.EndpointHealth", 13 | "name": "connectivity", 14 | "value": { 15 | "value": "OK" 16 | }, 17 | "timeOfSample": "2017-09-27T18:30:30.45Z", 18 | "uncertaintyInMilliseconds": 200 19 | } 20 | ] 21 | }, 22 | "event": { 23 | "header": { 24 | "namespace": "Alexa", 25 | "name": "Response", 26 | "payloadVersion": "3", 27 | "messageId": "5f8a426e-01e4-4cc9-8b79-65f8bd0fd8a4", 28 | "correlationToken": "dFMb0z+PgpgdDmluhJ1LddFvSqZ/jCc8ptlAKulUj90jSqg==" 29 | }, 30 | "endpoint": { 31 | "scope": { 32 | "type": "BearerToken", 33 | "token": "access-token-from-Amazon" 34 | }, 35 | "endpointId": "endpoint-001" 36 | }, 37 | "payload": {} 38 | } 39 | } -------------------------------------------------------------------------------- /sample_messages/PercentageController/PercentageController.response.json: -------------------------------------------------------------------------------- 1 | { 2 | "context": { 3 | "properties": [ 4 | { 5 | "namespace": "Alexa.PercentageController", 6 | "name": "percentage", 7 | "value": 74, 8 | "timeOfSample": "2017-09-27T18:30:30.45Z", 9 | "uncertaintyInMilliseconds": 200 10 | }, 11 | { 12 | "namespace": "Alexa.EndpointHealth", 13 | "name": "connectivity", 14 | "value": { 15 | "value": "OK" 16 | }, 17 | "timeOfSample": "2017-09-27T18:30:30.45Z", 18 | "uncertaintyInMilliseconds": 200 19 | } 20 | ] 21 | }, 22 | "event": { 23 | "header": { 24 | "namespace": "Alexa", 25 | "name": "Response", 26 | "payloadVersion": "3", 27 | "messageId": "5f8a426e-01e4-4cc9-8b79-65f8bd0fd8a4", 28 | "correlationToken": "dFMb0z+PgpgdDmluhJ1LddFvSqZ/jCc8ptlAKulUj90jSqg==" 29 | }, 30 | "endpoint": { 31 | "scope": { 32 | "type": "BearerToken", 33 | "token": "access-token-from-Amazon" 34 | }, 35 | "endpointId": "endpoint-001" 36 | }, 37 | "payload": {} 38 | } 39 | } -------------------------------------------------------------------------------- /sample_messages/PowerLevelController/PowerLevelController.response.json: -------------------------------------------------------------------------------- 1 | { 2 | "context": { 3 | "properties": [ 4 | { 5 | "namespace": "Alexa.PowerLevelController", 6 | "name": "powerLevel", 7 | "value": 42, 8 | "timeOfSample": "2017-09-27T18:30:30.45Z", 9 | "uncertaintyInMilliseconds": 200 10 | }, 11 | { 12 | "namespace": "Alexa.EndpointHealth", 13 | "name": "connectivity", 14 | "value": { 15 | "value": "OK" 16 | }, 17 | "timeOfSample": "2017-09-27T18:30:30.45Z", 18 | "uncertaintyInMilliseconds": 200 19 | } 20 | ] 21 | }, 22 | "event": { 23 | "header": { 24 | "namespace": "Alexa", 25 | "name": "Response", 26 | "payloadVersion": "3", 27 | "messageId": "5f8a426e-01e4-4cc9-8b79-65f8bd0fd8a4", 28 | "correlationToken": "dFMb0z+PgpgdDmluhJ1LddFvSqZ/jCc8ptlAKulUj90jSqg==" 29 | }, 30 | "endpoint": { 31 | "scope": { 32 | "type": "BearerToken", 33 | "token": "access-token-from-Amazon" 34 | }, 35 | "endpointId": "endpoint-001" 36 | }, 37 | "payload": {} 38 | } 39 | } -------------------------------------------------------------------------------- /sample_messages/BrightnessController/BrightnessController.SetBrightness.response.json: -------------------------------------------------------------------------------- 1 | { 2 | "context": { 3 | "properties": [ 4 | { 5 | "namespace": "Alexa.BrightnessController", 6 | "name": "brightness", 7 | "value": 75, 8 | "timeOfSample": "2017-09-27T18:30:30.45Z", 9 | "uncertaintyInMilliseconds": 200 10 | }, 11 | { 12 | "namespace": "Alexa.EndpointHealth", 13 | "name": "connectivity", 14 | "value": { 15 | "value": "OK" 16 | }, 17 | "timeOfSample": "2017-09-27T18:30:30.45Z", 18 | "uncertaintyInMilliseconds": 200 19 | } 20 | ] 21 | }, 22 | "event": { 23 | "header": { 24 | "namespace": "Alexa", 25 | "name": "Response", 26 | "payloadVersion": "3", 27 | "messageId": "5f8a426e-01e4-4cc9-8b79-65f8bd0fd8a4", 28 | "correlationToken": "dFMb0z+PgpgdDmluhJ1LddFvSqZ/jCc8ptlAKulUj90jSqg==" 29 | }, 30 | "endpoint": { 31 | "scope": { 32 | "type": "BearerToken", 33 | "token": "access-token-from-Amazon" 34 | }, 35 | "endpointId": "endpoint-001" 36 | }, 37 | "payload": {} 38 | } 39 | } -------------------------------------------------------------------------------- /sample_messages/BrightnessController/BrightnessController.AdjustBrightness.response.json: -------------------------------------------------------------------------------- 1 | { 2 | "context": { 3 | "properties": [ 4 | { 5 | "namespace": "Alexa.BrightnessController", 6 | "name": "brightness", 7 | "value": 50, 8 | "timeOfSample": "2017-02-03T16:20:50.52Z", 9 | "uncertaintyInMilliseconds": 200 10 | }, 11 | { 12 | "namespace": "Alexa.EndpointHealth", 13 | "name": "connectivity", 14 | "value": { 15 | "value": "OK" 16 | }, 17 | "timeOfSample": "2017-09-27T18:30:30.45Z", 18 | "uncertaintyInMilliseconds": 200 19 | } 20 | ] 21 | }, 22 | "event": { 23 | "header": { 24 | "namespace": "Alexa", 25 | "name": "Response", 26 | "payloadVersion": "3", 27 | "messageId": "5f8a426e-01e4-4cc9-8b79-65f8bd0fd8a4", 28 | "correlationToken": "dFMb0z+PgpgdDmluhJ1LddFvSqZ/jCc8ptlAKulUj90jSqg==" 29 | }, 30 | "endpoint": { 31 | "scope": { 32 | "type": "BearerToken", 33 | "token": "access-token-from-Amazon" 34 | }, 35 | "endpointId": "endpoint-001" 36 | }, 37 | "payload": {} 38 | } 39 | } -------------------------------------------------------------------------------- /sample_messages/ColorTemperatureController/ColorTemperatureController.response.json: -------------------------------------------------------------------------------- 1 | { 2 | "context": { 3 | "properties": [ 4 | { 5 | "namespace": "Alexa.ColorTemperatureController", 6 | "name": "colorTemperatureInKelvin", 7 | "value": 5000, 8 | "timeOfSample": "2017-09-27T18:30:30.45Z", 9 | "uncertaintyInMilliseconds": 200 10 | }, 11 | { 12 | "namespace": "Alexa.EndpointHealth", 13 | "name": "connectivity", 14 | "value": { 15 | "value": "OK" 16 | }, 17 | "timeOfSample": "2017-09-27T18:30:30.45Z", 18 | "uncertaintyInMilliseconds": 200 19 | } 20 | ] 21 | }, 22 | "event": { 23 | "header": { 24 | "namespace": "Alexa", 25 | "name": "Response", 26 | "payloadVersion": "3", 27 | "messageId": "5f8a426e-01e4-4cc9-8b79-65f8bd0fd8a4", 28 | "correlationToken": "dFMb0z+PgpgdDmluhJ1LddFvSqZ/jCc8ptlAKulUj90jSqg==" 29 | }, 30 | "endpoint": { 31 | "scope": { 32 | "type": "BearerToken", 33 | "token": "access-token-from-Amazon" 34 | }, 35 | "endpointId": "endpoint-001" 36 | }, 37 | "payload": {} 38 | } 39 | } -------------------------------------------------------------------------------- /sample_messages/TemperatureSensor/TemperatureSensor.response.json: -------------------------------------------------------------------------------- 1 | { 2 | "context": { 3 | "properties": [ 4 | { 5 | "namespace": "Alexa.TemperatureSensor", 6 | "name": "temperature", 7 | "value": { 8 | "value": 24.0, 9 | "scale": "CELSIUS" 10 | }, 11 | "timeOfSample": "2017-09-27T18:30:30.45Z", 12 | "uncertaintyInMilliseconds": 200 13 | }, 14 | { 15 | "namespace": "Alexa.EndpointHealth", 16 | "name": "connectivity", 17 | "value": { 18 | "value": "OK" 19 | }, 20 | "timeOfSample": "2017-09-27T18:30:30.45Z", 21 | "uncertaintyInMilliseconds": 200 22 | } 23 | ] 24 | }, 25 | "event": { 26 | "header": { 27 | "namespace": "Alexa", 28 | "name": "StateReport", 29 | "payloadVersion": "3", 30 | "messageId": "5f8a426e-01e4-4cc9-8b79-65f8bd0fd8a4", 31 | "correlationToken": "dFMb0z+PgpgdDmluhJ1LddFvSqZ/jCc8ptlAKulUj90jSqg==" 32 | }, 33 | "endpoint": { 34 | "scope": { 35 | "type": "BearerToken", 36 | "token": "access-token-from-Amazon" 37 | }, 38 | "endpointId": "endpoint-001" 39 | }, 40 | "payload": {} 41 | } 42 | } -------------------------------------------------------------------------------- /sample_backend/lambda/lambda_api/python/endpoint_cloud/api_response.py: -------------------------------------------------------------------------------- 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 | from .api_response_body import ApiResponseBody 15 | 16 | 17 | class ApiResponse: 18 | 19 | def __init__(self, **kwargs): 20 | self.isBase64Encoded = kwargs.get('isBase64Encoded', False) 21 | self.statusCode = kwargs.get('statusCode', 200) 22 | self.headers = {} 23 | self.body = ApiResponseBody() 24 | self.response = {} 25 | 26 | def __repr__(self): 27 | return self.create() 28 | 29 | def create(self): 30 | self.headers['Content-Type'] = 'application/json' 31 | 32 | self.response['isBase64Encoded'] = str(self.isBase64Encoded) 33 | self.response['statusCode'] = str(self.statusCode) 34 | self.response['headers'] = self.headers 35 | self.response['body'] = str(self.body) 36 | 37 | return self.response 38 | -------------------------------------------------------------------------------- /sample_messages/ColorController/ColorController.SetColor.response.json: -------------------------------------------------------------------------------- 1 | { 2 | "context": { 3 | "properties": [ 4 | { 5 | "namespace": "Alexa.ColorController", 6 | "name": "color", 7 | "value": { 8 | "hue": 350.5, 9 | "saturation": 0.7138, 10 | "brightness": 0.6524 11 | }, 12 | "timeOfSample": "2017-09-27T18:30:30.45Z", 13 | "uncertaintyInMilliseconds": 200 14 | }, 15 | { 16 | "namespace": "Alexa.EndpointHealth", 17 | "name": "connectivity", 18 | "value": { 19 | "value": "OK" 20 | }, 21 | "timeOfSample": "2017-09-27T18:30:30.45Z", 22 | "uncertaintyInMilliseconds": 200 23 | } 24 | ] 25 | }, 26 | "event": { 27 | "header": { 28 | "namespace": "Alexa", 29 | "name": "Response", 30 | "payloadVersion": "3", 31 | "messageId": "5f8a426e-01e4-4cc9-8b79-65f8bd0fd8a4", 32 | "correlationToken": "dFMb0z+PgpgdDmluhJ1LddFvSqZ/jCc8ptlAKulUj90jSqg==" 33 | }, 34 | "endpoint": { 35 | "scope": { 36 | "type": "BearerToken", 37 | "token": "access-token-from-Amazon" 38 | }, 39 | "endpointId": "endpoint-001" 40 | }, 41 | "payload": {} 42 | } 43 | } -------------------------------------------------------------------------------- /sample_messages/ChannelController/ChannelController.response.json: -------------------------------------------------------------------------------- 1 | { 2 | "context": { 3 | "properties": [ 4 | { 5 | "namespace": "Alexa.ChannelController", 6 | "name": "channel", 7 | "value": { 8 | "number": "1234", 9 | "callSign": "callsign1", 10 | "affiliateCallSign": "callsign2" 11 | }, 12 | "timeOfSample": "2017-09-27T18:30:30.45Z", 13 | "uncertaintyInMilliseconds": 200 14 | }, 15 | { 16 | "namespace": "Alexa.EndpointHealth", 17 | "name": "connectivity", 18 | "value": { 19 | "value": "OK" 20 | }, 21 | "timeOfSample": "2017-09-27T18:30:30.45Z", 22 | "uncertaintyInMilliseconds": 200 23 | } 24 | ] 25 | }, 26 | "event": { 27 | "header": { 28 | "namespace": "Alexa", 29 | "name": "Response", 30 | "payloadVersion": "3", 31 | "messageId": "5f8a426e-01e4-4cc9-8b79-65f8bd0fd8a4", 32 | "correlationToken": "dFMb0z+PgpgdDmluhJ1LddFvSqZ/jCc8ptlAKulUj90jSqg==" 33 | }, 34 | "endpoint": { 35 | "scope": { 36 | "type": "BearerToken", 37 | "token": "access-token-from-Amazon" 38 | }, 39 | "endpointId": "endpoint-001" 40 | }, 41 | "payload": {} 42 | } 43 | } -------------------------------------------------------------------------------- /sample_messages/CameraStreamController/CameraStreamController.request.json: -------------------------------------------------------------------------------- 1 | { 2 | "directive": { 3 | "header": { 4 | "namespace": "Alexa.CameraStreamController", 5 | "name": "InitializeCameraStreams", 6 | "payloadVersion": "3", 7 | "messageId": "1bd5d003-31b9-476f-ad03-71d471922820", 8 | "correlationToken": "dFMb0z+PgpgdDmluhJ1LddFvSqZ/jCc8ptlAKulUj90jSqg==" 9 | }, 10 | "endpoint": { 11 | "scope": { 12 | "type": "BearerToken", 13 | "token": "access-token-from-skill" 14 | }, 15 | "endpointId": "endpoint-001", 16 | "cookie": {} 17 | }, 18 | "payload": { 19 | "cameraStreams": [ 20 | { 21 | "protocol": "RTSP", 22 | "resolution": { 23 | "width": 1920, 24 | "height": 1080 25 | }, 26 | "authorizationType": "BEARER", 27 | "videoCodec": "H264", 28 | "audioCodec": "AAC" 29 | }, 30 | { 31 | "protocol": "RTSP", 32 | "resolution": { 33 | "width": 1280, 34 | "height": 720 35 | }, 36 | "authorizationType": "BEARER", 37 | "videoCodec": "MPEG2", 38 | "audioCodec": "G711" 39 | } 40 | ] 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /sample_messages/Speaker/Speaker.response.json: -------------------------------------------------------------------------------- 1 | { 2 | "context": { 3 | "properties": [ 4 | { 5 | "namespace": "Alexa.Speaker", 6 | "name": "volume", 7 | "value": 50, 8 | "timeOfSample": "2017-09-27T18:30:30.45Z", 9 | "uncertaintyInMilliseconds": 200 10 | }, 11 | { 12 | "namespace": "Alexa.Speaker", 13 | "name": "muted", 14 | "value": false, 15 | "timeOfSample": "2017-09-27T18:30:30.45Z", 16 | "uncertaintyInMilliseconds": 200 17 | }, 18 | { 19 | "namespace": "Alexa.EndpointHealth", 20 | "name": "connectivity", 21 | "value": { 22 | "value": "OK" 23 | }, 24 | "timeOfSample": "2017-09-27T18:30:30.45Z", 25 | "uncertaintyInMilliseconds": 200 26 | } 27 | ] 28 | }, 29 | "event": { 30 | "header": { 31 | "namespace": "Alexa", 32 | "name": "Response", 33 | "payloadVersion": "3", 34 | "messageId": "5f8a426e-01e4-4cc9-8b79-65f8bd0fd8a4", 35 | "correlationToken": "dFMb0z+PgpgdDmluhJ1LddFvSqZ/jCc8ptlAKulUj90jSqg==" 36 | }, 37 | "endpoint": { 38 | "scope": { 39 | "type": "BearerToken", 40 | "token": "access-token-from-Amazon" 41 | }, 42 | "endpointId": "endpoint-001" 43 | }, 44 | "payload": {} 45 | } 46 | } -------------------------------------------------------------------------------- /sample_lambda/python/jsonschema/compat.py: -------------------------------------------------------------------------------- 1 | import operator 2 | import sys 3 | 4 | 5 | try: 6 | from collections import MutableMapping, Sequence # noqa 7 | except ImportError: 8 | from collections.abc import MutableMapping, Sequence # noqa 9 | 10 | PY3 = sys.version_info[0] >= 3 11 | 12 | if PY3: 13 | zip = zip 14 | from functools import lru_cache 15 | from io import StringIO 16 | from urllib.parse import ( 17 | unquote, urljoin, urlunsplit, SplitResult, urlsplit as _urlsplit 18 | ) 19 | from urllib.request import urlopen 20 | str_types = str, 21 | int_types = int, 22 | iteritems = operator.methodcaller("items") 23 | else: 24 | from itertools import izip as zip # noqa 25 | from StringIO import StringIO 26 | from urlparse import ( 27 | urljoin, urlunsplit, SplitResult, urlsplit as _urlsplit # noqa 28 | ) 29 | from urllib import unquote # noqa 30 | from urllib2 import urlopen # noqa 31 | str_types = basestring 32 | int_types = int, long 33 | iteritems = operator.methodcaller("iteritems") 34 | 35 | from functools32 import lru_cache 36 | 37 | 38 | # On python < 3.3 fragments are not handled properly with unknown schemes 39 | def urlsplit(url): 40 | scheme, netloc, path, query, fragment = _urlsplit(url) 41 | if "#" in path: 42 | path, fragment = path.split("#", 1) 43 | return SplitResult(scheme, netloc, path, query, fragment) 44 | 45 | 46 | def urldefrag(url): 47 | if "#" in url: 48 | s, n, p, q, frag = urlsplit(url) 49 | defrag = urlunsplit((s, n, p, q, '')) 50 | else: 51 | defrag = url 52 | frag = '' 53 | return defrag, frag 54 | 55 | 56 | # flake8: noqa 57 | -------------------------------------------------------------------------------- /sample_backend/lambda/lambda_api/python/jsonschema/compat.py: -------------------------------------------------------------------------------- 1 | import operator 2 | import sys 3 | 4 | 5 | try: 6 | from collections import MutableMapping, Sequence # noqa 7 | except ImportError: 8 | from collections.abc import MutableMapping, Sequence # noqa 9 | 10 | PY3 = sys.version_info[0] >= 3 11 | 12 | if PY3: 13 | zip = zip 14 | from functools import lru_cache 15 | from io import StringIO 16 | from urllib.parse import ( 17 | unquote, urljoin, urlunsplit, SplitResult, urlsplit as _urlsplit 18 | ) 19 | from urllib.request import urlopen 20 | str_types = str, 21 | int_types = int, 22 | iteritems = operator.methodcaller("items") 23 | else: 24 | from itertools import izip as zip # noqa 25 | from StringIO import StringIO 26 | from urlparse import ( 27 | urljoin, urlunsplit, SplitResult, urlsplit as _urlsplit # noqa 28 | ) 29 | from urllib import unquote # noqa 30 | from urllib2 import urlopen # noqa 31 | str_types = basestring 32 | int_types = int, long 33 | iteritems = operator.methodcaller("iteritems") 34 | 35 | from functools32 import lru_cache 36 | 37 | 38 | # On python < 3.3 fragments are not handled properly with unknown schemes 39 | def urlsplit(url): 40 | scheme, netloc, path, query, fragment = _urlsplit(url) 41 | if "#" in path: 42 | path, fragment = path.split("#", 1) 43 | return SplitResult(scheme, netloc, path, query, fragment) 44 | 45 | 46 | def urldefrag(url): 47 | if "#" in url: 48 | s, n, p, q, frag = urlsplit(url) 49 | defrag = urlunsplit((s, n, p, q, '')) 50 | else: 51 | defrag = url 52 | frag = '' 53 | return defrag, frag 54 | 55 | 56 | # flake8: noqa 57 | -------------------------------------------------------------------------------- /sample_backend/docs/008-setup-send-an-event.md: -------------------------------------------------------------------------------- 1 | # Step 8: Send an Event 2 | Send an external event into Alexa from the Endpoint Device backend. This simulates an external event on the endpoint that will need to be updated with Alexa. 3 | 4 | 5 | #### 8.1 Send a Proactive State Update 6 | 7 | 8.1.1 In Postman, select the **POST** _/events_ resource from the left menu. 8 | 9 | 8.1.2 Select the _Body_ tab and view the raw JSON. It should look like the following: 10 | ``` 11 | { 12 | "event": { 13 | "endpoint": { 14 | "userId" : "0", 15 | "id": "black_switch", 16 | "state": "OFF", 17 | "type": "SWITCH" 18 | } 19 | } 20 | } 21 | ``` 22 | 8.1.3 Update the JSON by replacing the `"userId"` "0" value with the [user_id] stored in the `config.txt` file. When edited, it should something like the following: 23 | ``` 24 | { 25 | "event": { 26 | "endpoint": { 27 | "userId" : "amzn1.account.XXXXXXXXXXXXXXXXXXXXXXXXXXXX", 28 | "id": "black_switch", 29 | "state": "OFF", 30 | "type": "SWITCH" 31 | } 32 | } 33 | } 34 | ``` 35 | 36 | 8.1.1 Click **Save** in the top right and then and then click the **Send** button. 37 | 38 | 8.1.2 Return to the [AWS IoT Things console](https://console.aws.amazon.com/iotv2/home?region=us-east-1#/thinghub) and note the _state_ value of the _black_switch_. The state should reflect the _"state"_ value passed in the body. For instance, if set to _"OFF"_, the _black_switch_ attribute _state_ will be set to _OFF_. 39 | 40 |
41 | 42 | ____ 43 | Go to [Step 9: Clean Up](009-setup-cleanup.md). -------------------------------------------------------------------------------- /sample_messages/ThermostatController/ThermostatController.SetTargetTemperature.SingleMode.response.json: -------------------------------------------------------------------------------- 1 | { 2 | "context": { 3 | "properties": [ 4 | { 5 | "namespace": "Alexa.ThermostatController", 6 | "name": "targetSetpoint", 7 | "value": { 8 | "value": 25, 9 | "scale": "CELSIUS" 10 | }, 11 | "timeOfSample": "2017-09-27T18:30:30.45Z", 12 | "uncertaintyInMilliseconds": 200 13 | }, 14 | { 15 | "namespace": "Alexa.ThermostatController", 16 | "name": "thermostatMode", 17 | "value": "HEAT", 18 | "timeOfSample": "2017-09-27T18:30:30.45Z", 19 | "uncertaintyInMilliseconds": 200 20 | }, 21 | { 22 | "namespace": "Alexa.EndpointHealth", 23 | "name": "connectivity", 24 | "value": { 25 | "value": "OK" 26 | }, 27 | "timeOfSample": "2017-09-27T18:30:30.45Z", 28 | "uncertaintyInMilliseconds": 200 29 | } 30 | ] 31 | }, 32 | "event": { 33 | "header": { 34 | "namespace": "Alexa", 35 | "name": "Response", 36 | "payloadVersion": "3", 37 | "messageId": "5f8a426e-01e4-4cc9-8b79-65f8bd0fd8a4", 38 | "correlationToken": "dFMb0z+PgpgdDmluhJ1LddFvSqZ/jCc8ptlAKulUj90jSqg==" 39 | }, 40 | "endpoint": { 41 | "scope": { 42 | "type": "BearerToken", 43 | "token": "access-token-from-Amazon" 44 | }, 45 | "endpointId": "endpoint-001" 46 | }, 47 | "payload": {} 48 | } 49 | } -------------------------------------------------------------------------------- /sample_messages/ChangeReport/ChangeReport.json: -------------------------------------------------------------------------------- 1 | { 2 | "context": { 3 | "properties": [ 4 | { 5 | "namespace": "Alexa.BrightnessController", 6 | "name": "brightness", 7 | "value": 85, 8 | "timeOfSample": "2017-09-27T18:30:30.45Z", 9 | "uncertaintyInMilliseconds": 200 10 | }, 11 | { 12 | "namespace": "Alexa.EndpointHealth", 13 | "name": "connectivity", 14 | "value": { 15 | "value": "OK" 16 | }, 17 | "timeOfSample": "2017-09-27T18:30:30.45Z", 18 | "uncertaintyInMilliseconds": 200 19 | } 20 | ] 21 | }, 22 | "event": { 23 | "header": { 24 | "namespace": "Alexa", 25 | "name": "ChangeReport", 26 | "payloadVersion": "3", 27 | "messageId": "5f8a426e-01e4-4cc9-8b79-65f8bd0fd8a4" 28 | }, 29 | "endpoint": { 30 | "scope": { 31 | "type": "BearerToken", 32 | "token": "access-token-from-Amazon" 33 | }, 34 | "endpointId": "endpoint-001" 35 | }, 36 | "payload": { 37 | "change": { 38 | "cause": { 39 | "type": "PHYSICAL_INTERACTION" 40 | }, 41 | "properties": [ 42 | { 43 | "namespace": "Alexa.PowerController", 44 | "name": "powerState", 45 | "value": "ON", 46 | "timeOfSample": "2017-09-27T18:30:30.45Z", 47 | "uncertaintyInMilliseconds": 200 48 | } 49 | ] 50 | } 51 | } 52 | } 53 | } -------------------------------------------------------------------------------- /sample_backend/lambda/lambda_api/python/alexa/skills/smarthome/alexa_acceptgrant_response.py: -------------------------------------------------------------------------------- 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 | import uuid 15 | 16 | 17 | class AlexaAcceptGrantResponse: 18 | 19 | def __init__(self, **kwargs): 20 | 21 | self.namespace = kwargs.get('namespace', 'Alexa.Authorization') 22 | self.name = kwargs.get('name', 'AcceptGrant.Response') 23 | self.payload_version = kwargs.get('payload_version', "3") 24 | self.message_id = kwargs.get('message_id', str(uuid.uuid4())) 25 | self.type = kwargs.get('type', None) 26 | self.message = kwargs.get('message', None) 27 | 28 | def get_response(self): 29 | response = {} 30 | 31 | header = {} 32 | header['namespace'] = self.namespace 33 | header['name'] = self.name 34 | header['payloadVersion'] = self.payload_version 35 | header['messageId'] = self.message_id 36 | 37 | response['event'] = {} 38 | response['event']['header'] = header 39 | response['event']['payload'] = {} 40 | 41 | if self.type and self.message: 42 | response['event']['payload']['type'] = self.type 43 | response['event']['payload']['message'] = self.message 44 | 45 | return response 46 | -------------------------------------------------------------------------------- /sample_lambda/python/validation.py: -------------------------------------------------------------------------------- 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 Validation Package Sample Code. 15 | 16 | This module is used by Alexa Smart Home skills to validate their Lambda responses and async 17 | messages before sending them back to Alexa. If an error is found, an exception is thrown so that 18 | the developer can catch the error and do something about it, instead of sending it back to Alexa 19 | and causing an error on the Alexa side. 20 | 21 | This specific package uses the jsonschema (https://github.com/Julian/jsonschema) Python implementation 22 | of the JSON Schema Draft 4 to perform the actual validation against the validation schema. 23 | 24 | """ 25 | 26 | import json 27 | 28 | from jsonschema import validate 29 | 30 | def validate_message(request, response): 31 | 32 | # update below with path to your validation schema 33 | # this path works if you copy the latest validation schema into the same directory as this file 34 | # validation schema: https://github.com/alexa/alexa-smarthome/wiki/Validation-Schema 35 | path_to_validation_schema = "alexa_smart_home_message_schema.json" 36 | 37 | with open(path_to_validation_schema) as json_file: 38 | schema = json.load(json_file) 39 | validate(response, schema) 40 | -------------------------------------------------------------------------------- /sample_messages/StateReport/StateReport.json: -------------------------------------------------------------------------------- 1 | { 2 | "context": { 3 | "properties": [ 4 | { 5 | "namespace": "Alexa.EndpointHealth", 6 | "name": "connectivity", 7 | "value": { 8 | "value": "OK" 9 | }, 10 | "timeOfSample": "2017-09-27T18:30:30.45Z", 11 | "uncertaintyInMilliseconds": 200 12 | }, 13 | { 14 | "name": "targetSetpoint", 15 | "namespace": "Alexa.ThermostatController", 16 | "value": { 17 | "scale": "CELSIUS", 18 | "value": 25 19 | }, 20 | "timeOfSample": "2017-09-27T18:30:30.45Z", 21 | "uncertaintyInMilliseconds": 200 22 | }, 23 | { 24 | "name": "thermostatMode", 25 | "namespace": "Alexa.ThermostatController", 26 | "value": "AUTO", 27 | "timeOfSample": "2017-09-27T18:30:30.45Z", 28 | "uncertaintyInMilliseconds": 200 29 | }, 30 | { 31 | "name": "temperature", 32 | "namespace": "Alexa.TemperatureSensor", 33 | "value": { 34 | "scale": "CELSIUS", 35 | "value": 20 36 | }, 37 | "timeOfSample": "2017-09-27T18:30:30.45Z", 38 | "uncertaintyInMilliseconds": 200 39 | } 40 | ] 41 | }, 42 | "event": { 43 | "header": { 44 | "namespace": "Alexa", 45 | "name": "StateReport", 46 | "payloadVersion": "3", 47 | "messageId": "5f8a426e-01e4-4cc9-8b79-65f8bd0fd8a4", 48 | "correlationToken": "dFMb0z+PgpgdDmluhJ1LddFvSqZ/jCc8ptlAKulUj90jSqg==" 49 | }, 50 | "endpoint": { 51 | "scope": { 52 | "type": "BearerToken", 53 | "token": "access-token-from-Amazon" 54 | }, 55 | "endpointId": "endpoint-001" 56 | }, 57 | "payload": {} 58 | } 59 | } -------------------------------------------------------------------------------- /sample_backend/lambda/lambda_api/python/alexa/skills/smarthome/alexa_response.py: -------------------------------------------------------------------------------- 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 | import uuid 15 | 16 | 17 | class AlexaResponse: 18 | 19 | def __init__(self, **kwargs): 20 | self.message_id = str(uuid.uuid4()) 21 | self.name = 'Response' 22 | self.payload_version = kwargs.get('payload_version', '3') 23 | self.correlation_token = kwargs.get('correlation_token', None) 24 | self.endpoint_id = kwargs.get('endpoint_id', None) 25 | self.token = kwargs.get('token', None) 26 | 27 | def get_response(self): 28 | response = {} 29 | event = {} 30 | 31 | header = {} 32 | header['namespace'] = 'Alexa' 33 | header['name'] = self.name 34 | header['payloadVersion'] = self.payload_version 35 | header['messageId'] = self.message_id 36 | if self.correlation_token: 37 | header["correlationToken"] = self.correlation_token 38 | 39 | endpoint = {} 40 | if not self.endpoint_id: 41 | self.endpoint_id = "INVALID" 42 | endpoint['endpointId'] = self.endpoint_id 43 | 44 | # If this is an asynchronous response, include the token 45 | if self.token: 46 | scope = {} 47 | scope['type'] = 'BearerToken' 48 | scope['type'] = self.token 49 | endpoint['scope'] = scope 50 | 51 | event['header'] = header 52 | event['endpoint'] = endpoint 53 | event['payload'] = {} 54 | 55 | response['event'] = event 56 | 57 | return response 58 | -------------------------------------------------------------------------------- /sample_messages/ThermostatController/ThermostatController.SetTargetTemperature.DualMode.response.json: -------------------------------------------------------------------------------- 1 | { 2 | "context": { 3 | "properties": [ 4 | { 5 | "namespace": "Alexa.ThermostatController", 6 | "name": "lowerSetpoint", 7 | "value": { 8 | "value": 68.0, 9 | "scale": "FAHRENHEIT" 10 | }, 11 | "timeOfSample": "2017-09-27T18:30:30.45Z", 12 | "uncertaintyInMilliseconds": 200 13 | }, 14 | { 15 | "namespace": "Alexa.ThermostatController", 16 | "name": "upperSetpoint", 17 | "value": { 18 | "value": 74.0, 19 | "scale": "FAHRENHEIT" 20 | }, 21 | "timeOfSample": "2017-09-27T18:30:30.45Z", 22 | "uncertaintyInMilliseconds": 200 23 | }, 24 | { 25 | "namespace": "Alexa.ThermostatController", 26 | "name": "thermostatMode", 27 | "value": "AUTO", 28 | "timeOfSample": "2017-09-27T18:30:30.45Z", 29 | "uncertaintyInMilliseconds": 200 30 | }, 31 | { 32 | "namespace": "Alexa.EndpointHealth", 33 | "name": "connectivity", 34 | "value": { 35 | "value": "OK" 36 | }, 37 | "timeOfSample": "2017-09-27T18:30:30.45Z", 38 | "uncertaintyInMilliseconds": 200 39 | } 40 | ] 41 | }, 42 | "event": { 43 | "header": { 44 | "namespace": "Alexa", 45 | "name": "Response", 46 | "payloadVersion": "3", 47 | "messageId": "5f8a426e-01e4-4cc9-8b79-65f8bd0fd8a4", 48 | "correlationToken": "dFMb0z+PgpgdDmluhJ1LddFvSqZ/jCc8ptlAKulUj90jSqg==" 49 | }, 50 | "endpoint": { 51 | "scope": { 52 | "type": "BearerToken", 53 | "token": "access-token-from-Amazon" 54 | }, 55 | "endpointId": "endpoint-001" 56 | }, 57 | "payload": {} 58 | } 59 | } -------------------------------------------------------------------------------- /sample_backend/lambda/lambda_api/python/endpoint_cloud/api_auth.py: -------------------------------------------------------------------------------- 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 | import http.client 15 | from urllib.parse import urlencode 16 | 17 | 18 | class ApiAuth: 19 | 20 | def post_to_api(self, payload): 21 | connection = http.client.HTTPSConnection("api.amazon.com") 22 | headers = { 23 | 'content-type': "application/x-www-form-urlencoded", 24 | 'cache-control': "no-cache" 25 | } 26 | connection.request('POST', '/auth/o2/token', urlencode(payload), headers) 27 | return connection.getresponse() 28 | 29 | def get_access_token(self, code, client_id, client_secret, redirect_uri): 30 | payload = { 31 | 'grant_type': 'authorization_code', 32 | 'code': code, 33 | 'client_id': client_id, 34 | 'client_secret': client_secret, 35 | 'redirect_uri': redirect_uri 36 | } 37 | return self.post_to_api(payload) 38 | 39 | @staticmethod 40 | def get_user_id(access_token): 41 | connection = http.client.HTTPSConnection('api.amazon.com') 42 | connection.request('GET', '/user/profile?access_token=' + access_token) 43 | return connection.getresponse() 44 | 45 | def refresh_access_token(self, refresh_token, client_id, client_secret, redirect_uri): 46 | payload = { 47 | 'grant_type': 'refresh_token', 48 | 'refresh_token': refresh_token, 49 | 'client_id': client_id, 50 | 'client_secret': client_secret, 51 | 'redirect_uri': redirect_uri 52 | } 53 | return self.post_to_api(payload) 54 | 55 | 56 | -------------------------------------------------------------------------------- /sample_backend/docs/007-setup-test-endpoints.md: -------------------------------------------------------------------------------- 1 | # Step 7: Test the Endpoints 2 | Now that the environment is in place and a test device has been created, test out the endpoints. 3 | 4 | 5 | #### Step 7.1 Discover devices via a voice command 6 | 7 | 7.1.1 Browse to https://echosim.io and login with your Amazon account. 8 | 9 | 7.1.2 Activate echosim.io by clicking and holding the speaker icon and give the command "Discover Devices". Alexa should respond with "Starting Discovery..." with a description of the discovery process. 10 | 11 | 7.1.3 Return to https://alexa.amazon.com/ and select **Smart Home** from the left menu and then select **Devices** in the main window. 12 | 13 | 7.1.4 From the list of devices, not the inclusion of a **black switch** with a description of _Endpoint Description_ in the list of _Devices_. 14 | 15 | > Note If you are using an Echo device to issue a "Discover Devices" command, once discovery is complete, Alexa should respond "I found one new switch called black switch." or possibly with additional devices discovered. Note the addition of a _black switch_ device to your list of devices. 16 | 17 | > Optionally, for device discovery, you can browse to https://alexa.amazon.com/spa/index.html#appliances and click the **Discover** button at the bottom of the page. 18 | 19 | #### Step 7.2 Send at Turn On voice command 20 | 21 | 7.2.1 Activate echosim and give the command "Turn on Black Switch". Alexa should respond with "OK". 22 | 23 | 7.2.2 Return to the [AWS IoT Things console](https://console.aws.amazon.com/iotv2/home?region=us-east-1#/thinghub) and click on the `black_switch` Thing instance to inspect its attributes. 24 | 25 | 7.2.3 That `black_switch` Thing should reflect the state of the "ON" or "OFF" power voice command when the Thing is refreshed. 26 | 27 | 28 |
29 | 30 | ____ 31 | Go to [Step 8: Send an Event](008-setup-send-an-event.md). 32 | -------------------------------------------------------------------------------- /sample_backend/lambda/lambda_api/python/alexa/skills/smarthome/alexa_error.py: -------------------------------------------------------------------------------- 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 | import uuid 15 | 16 | 17 | class AlexaError: 18 | 19 | def __init__(self, **kwargs): 20 | self.message_id = str(uuid.uuid4()) 21 | self.name = 'ErrorResponse' 22 | self.payload_version = kwargs.get('payload_version', '3') 23 | self.correlation_token = kwargs.get('correlation_token', None) 24 | self.endpoint_id = kwargs.get('endpoint_id', None) 25 | self.token = kwargs.get('token', None) 26 | self.type = kwargs.get('type', 'INTERNAL_ERROR') 27 | self.message = kwargs.get('message', 'An Internal Error has occurred') 28 | 29 | def get_response(self): 30 | response = {} 31 | event = {} 32 | 33 | header = {} 34 | header['namespace'] = 'Alexa' 35 | header['name'] = self.name 36 | header['payloadVersion'] = self.payload_version 37 | header['messageId'] = self.message_id 38 | if self.correlation_token: 39 | header["correlationToken"] = self.correlation_token 40 | 41 | endpoint = {} 42 | if not self.endpoint_id: 43 | self.endpoint_id = "INVALID" 44 | endpoint['endpointId'] = self.endpoint_id 45 | 46 | # If this is an asynchronous response, include the token 47 | if self.token: 48 | scope = {} 49 | scope['type'] = 'BearerToken' 50 | scope['type'] = self.token 51 | endpoint['scope'] = scope 52 | 53 | payload = {} 54 | payload['type'] = self.type 55 | payload['message'] = self.message 56 | 57 | event['header'] = header 58 | event['endpoint'] = endpoint 59 | event['payload'] = payload 60 | 61 | response['event'] = event 62 | 63 | return response 64 | -------------------------------------------------------------------------------- /sample_messages/CameraStreamController/CameraStreamController.response.json: -------------------------------------------------------------------------------- 1 | { 2 | "context": { 3 | "properties": [ 4 | { 5 | "namespace": "Alexa.EndpointHealth", 6 | "name": "connectivity", 7 | "value": { 8 | "value": "OK" 9 | }, 10 | "timeOfSample": "2017-09-27T18:30:30.45Z", 11 | "uncertaintyInMilliseconds": 200 12 | } 13 | ] 14 | }, 15 | "event": { 16 | "header": { 17 | "namespace": "Alexa.CameraStreamController", 18 | "name": "Response", 19 | "payloadVersion": "3", 20 | "messageId": "5f8a426e-01e4-4cc9-8b79-65f8bd0fd8a4", 21 | "correlationToken": "dFMb0z+PgpgdDmluhJ1LddFvSqZ/jCc8ptlAKulUj90jSqg==" 22 | }, 23 | "endpoint": { 24 | "scope": { 25 | "type": "BearerToken", 26 | "token": "access-token-from-Amazon" 27 | }, 28 | "endpointId": "endpoint-001" 29 | }, 30 | "payload": { 31 | "cameraStreams": [ 32 | { 33 | "uri": "rtsp://username:password@link.to.video:443/feed1.mp4", 34 | "expirationTime": "2017-09-27T20:30:30.45Z", 35 | "idleTimeoutSeconds": 30, 36 | "protocol": "RTSP", 37 | "resolution": { 38 | "width": 1920, 39 | "height": 1080 40 | }, 41 | "authorizationType": "BASIC", 42 | "videoCodec": "H264", 43 | "audioCodec": "AAC" 44 | }, 45 | { 46 | "uri": "rtsp://username:password@link.to.video:443/feed2.mp4", 47 | "expirationTime": "2017-09-27T20:30:30.45Z", 48 | "idleTimeoutSeconds": 60, 49 | "protocol": "RTSP", 50 | "resolution": { 51 | "width": 1280, 52 | "height": 720 53 | }, 54 | "authorizationType": "DIGEST", 55 | "videoCodec": "MPEG2", 56 | "audioCodec": "G711" 57 | } 58 | ], 59 | "imageUri": "https://username:password@link.to.image/image.jpg" 60 | } 61 | } 62 | } -------------------------------------------------------------------------------- /sample_lambda/python/jsonschema/tests/test_format.py: -------------------------------------------------------------------------------- 1 | """ 2 | Tests for the parts of jsonschema related to the :validator:`format` property. 3 | 4 | """ 5 | 6 | from jsonschema.tests.compat import mock, unittest 7 | 8 | from jsonschema import FormatError, ValidationError, FormatChecker 9 | from jsonschema.validators import Draft4Validator 10 | 11 | 12 | class TestFormatChecker(unittest.TestCase): 13 | def setUp(self): 14 | self.fn = mock.Mock() 15 | 16 | def test_it_can_validate_no_formats(self): 17 | checker = FormatChecker(formats=()) 18 | self.assertFalse(checker.checkers) 19 | 20 | def test_it_raises_a_key_error_for_unknown_formats(self): 21 | with self.assertRaises(KeyError): 22 | FormatChecker(formats=["o noes"]) 23 | 24 | def test_it_can_register_cls_checkers(self): 25 | with mock.patch.dict(FormatChecker.checkers, clear=True): 26 | FormatChecker.cls_checks("new")(self.fn) 27 | self.assertEqual(FormatChecker.checkers, {"new": (self.fn, ())}) 28 | 29 | def test_it_can_register_checkers(self): 30 | checker = FormatChecker() 31 | checker.checks("new")(self.fn) 32 | self.assertEqual( 33 | checker.checkers, 34 | dict(FormatChecker.checkers, new=(self.fn, ())) 35 | ) 36 | 37 | def test_it_catches_registered_errors(self): 38 | checker = FormatChecker() 39 | cause = self.fn.side_effect = ValueError() 40 | 41 | checker.checks("foo", raises=ValueError)(self.fn) 42 | 43 | with self.assertRaises(FormatError) as cm: 44 | checker.check("bar", "foo") 45 | 46 | self.assertIs(cm.exception.cause, cause) 47 | self.assertIs(cm.exception.__cause__, cause) 48 | 49 | # Unregistered errors should not be caught 50 | self.fn.side_effect = AttributeError 51 | with self.assertRaises(AttributeError): 52 | checker.check("bar", "foo") 53 | 54 | def test_format_error_causes_become_validation_error_causes(self): 55 | checker = FormatChecker() 56 | checker.checks("foo", raises=ValueError)(self.fn) 57 | cause = self.fn.side_effect = ValueError() 58 | validator = Draft4Validator({"format": "foo"}, format_checker=checker) 59 | 60 | with self.assertRaises(ValidationError) as cm: 61 | validator.validate("bar") 62 | 63 | self.assertIs(cm.exception.__cause__, cause) 64 | -------------------------------------------------------------------------------- /sample_backend/lambda/lambda_api/python/jsonschema/tests/test_format.py: -------------------------------------------------------------------------------- 1 | """ 2 | Tests for the parts of jsonschema related to the :validator:`format` property. 3 | 4 | """ 5 | 6 | from jsonschema.tests.compat import mock, unittest 7 | 8 | from jsonschema import FormatError, ValidationError, FormatChecker 9 | from jsonschema.validators import Draft4Validator 10 | 11 | 12 | class TestFormatChecker(unittest.TestCase): 13 | def setUp(self): 14 | self.fn = mock.Mock() 15 | 16 | def test_it_can_validate_no_formats(self): 17 | checker = FormatChecker(formats=()) 18 | self.assertFalse(checker.checkers) 19 | 20 | def test_it_raises_a_key_error_for_unknown_formats(self): 21 | with self.assertRaises(KeyError): 22 | FormatChecker(formats=["o noes"]) 23 | 24 | def test_it_can_register_cls_checkers(self): 25 | with mock.patch.dict(FormatChecker.checkers, clear=True): 26 | FormatChecker.cls_checks("new")(self.fn) 27 | self.assertEqual(FormatChecker.checkers, {"new": (self.fn, ())}) 28 | 29 | def test_it_can_register_checkers(self): 30 | checker = FormatChecker() 31 | checker.checks("new")(self.fn) 32 | self.assertEqual( 33 | checker.checkers, 34 | dict(FormatChecker.checkers, new=(self.fn, ())) 35 | ) 36 | 37 | def test_it_catches_registered_errors(self): 38 | checker = FormatChecker() 39 | cause = self.fn.side_effect = ValueError() 40 | 41 | checker.checks("foo", raises=ValueError)(self.fn) 42 | 43 | with self.assertRaises(FormatError) as cm: 44 | checker.check("bar", "foo") 45 | 46 | self.assertIs(cm.exception.cause, cause) 47 | self.assertIs(cm.exception.__cause__, cause) 48 | 49 | # Unregistered errors should not be caught 50 | self.fn.side_effect = AttributeError 51 | with self.assertRaises(AttributeError): 52 | checker.check("bar", "foo") 53 | 54 | def test_format_error_causes_become_validation_error_causes(self): 55 | checker = FormatChecker() 56 | checker.checks("foo", raises=ValueError)(self.fn) 57 | cause = self.fn.side_effect = ValueError() 58 | validator = Draft4Validator({"format": "foo"}, format_checker=checker) 59 | 60 | with self.assertRaises(ValidationError) as cm: 61 | validator.validate("bar") 62 | 63 | self.assertIs(cm.exception.__cause__, cause) 64 | -------------------------------------------------------------------------------- /sample_backend/lambda/lambda_smarthome/python/index.py: -------------------------------------------------------------------------------- 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 | import json 15 | import os 16 | import urllib.request 17 | from urllib.request import HTTPError 18 | 19 | 20 | def get_api_url(api_id, aws_region, resource): 21 | return 'https://{0}.execute-api.{1}.amazonaws.com/prod/{2}'.format(api_id, aws_region, resource) 22 | 23 | 24 | def handler(request, context): 25 | try: 26 | print("LOG skill.index.handler.request:", request) 27 | 28 | # Get the Environment Variables, these are used to dynamically compose the API URI 29 | 30 | # Get the Region 31 | env_aws_default_region = os.environ.get('AWS_DEFAULT_REGION', None) 32 | if env_aws_default_region is None: 33 | print("ERROR skill.index.handler.aws_default_region is None default to us-east-1") 34 | env_aws_default_region = 'us-east-1' 35 | 36 | # Get the API ID 37 | env_api_id = os.environ.get('api_id', None) 38 | if env_api_id is None: 39 | print("ERROR skill.index.handler.env_api_id is None") 40 | return '{}' 41 | 42 | # Pass the requested directive to the backend Endpoint API 43 | url = get_api_url(env_api_id, env_aws_default_region, 'directives') 44 | data = bytes(json.dumps(request), encoding="utf-8") 45 | headers = {'Content-Type': 'application/json'} 46 | req = urllib.request.Request(url, data, headers) 47 | result = urllib.request.urlopen(req).read().decode("utf-8") 48 | response = json.loads(result) 49 | print("LOG skill.index.handler.response:", response) 50 | return response 51 | 52 | except HTTPError as error: 53 | print("ERROR skill.index.handler.error:", error) 54 | return error 55 | 56 | except ValueError as error: 57 | print("ERROR skill.index.handler.error:", error) 58 | return error 59 | -------------------------------------------------------------------------------- /sample_lambda/python/jsonschema/cli.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | import argparse 3 | import json 4 | import sys 5 | 6 | from jsonschema._reflect import namedAny 7 | from jsonschema.validators import validator_for 8 | 9 | 10 | def _namedAnyWithDefault(name): 11 | if "." not in name: 12 | name = "jsonschema." + name 13 | return namedAny(name) 14 | 15 | 16 | def _json_file(path): 17 | with open(path) as file: 18 | return json.load(file) 19 | 20 | 21 | parser = argparse.ArgumentParser( 22 | description="JSON Schema Validation CLI", 23 | ) 24 | parser.add_argument( 25 | "-i", "--instance", 26 | action="append", 27 | dest="instances", 28 | type=_json_file, 29 | help=( 30 | "a path to a JSON instance (i.e. filename.json)" 31 | "to validate (may be specified multiple times)" 32 | ), 33 | ) 34 | parser.add_argument( 35 | "-F", "--error-format", 36 | default="{error.instance}: {error.message}\n", 37 | help=( 38 | "the format to use for each error output message, specified in " 39 | "a form suitable for passing to str.format, which will be called " 40 | "with 'error' for each error" 41 | ), 42 | ) 43 | parser.add_argument( 44 | "-V", "--validator", 45 | type=_namedAnyWithDefault, 46 | help=( 47 | "the fully qualified object name of a validator to use, or, for " 48 | "validators that are registered with jsonschema, simply the name " 49 | "of the class." 50 | ), 51 | ) 52 | parser.add_argument( 53 | "schema", 54 | help="the JSON Schema to validate with (i.e. filename.schema)", 55 | type=_json_file, 56 | ) 57 | 58 | 59 | def parse_args(args): 60 | arguments = vars(parser.parse_args(args=args or ["--help"])) 61 | if arguments["validator"] is None: 62 | arguments["validator"] = validator_for(arguments["schema"]) 63 | return arguments 64 | 65 | 66 | def main(args=sys.argv[1:]): 67 | sys.exit(run(arguments=parse_args(args=args))) 68 | 69 | 70 | def run(arguments, stdout=sys.stdout, stderr=sys.stderr): 71 | error_format = arguments["error_format"] 72 | validator = arguments["validator"](schema=arguments["schema"]) 73 | 74 | validator.check_schema(arguments["schema"]) 75 | 76 | errored = False 77 | for instance in arguments["instances"] or (): 78 | for error in validator.iter_errors(instance): 79 | stderr.write(error_format.format(error=error)) 80 | errored = True 81 | return errored 82 | -------------------------------------------------------------------------------- /sample_backend/lambda/lambda_api/python/jsonschema/cli.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | import argparse 3 | import json 4 | import sys 5 | 6 | from jsonschema._reflect import namedAny 7 | from jsonschema.validators import validator_for 8 | 9 | 10 | def _namedAnyWithDefault(name): 11 | if "." not in name: 12 | name = "jsonschema." + name 13 | return namedAny(name) 14 | 15 | 16 | def _json_file(path): 17 | with open(path) as file: 18 | return json.load(file) 19 | 20 | 21 | parser = argparse.ArgumentParser( 22 | description="JSON Schema Validation CLI", 23 | ) 24 | parser.add_argument( 25 | "-i", "--instance", 26 | action="append", 27 | dest="instances", 28 | type=_json_file, 29 | help=( 30 | "a path to a JSON instance (i.e. filename.json)" 31 | "to validate (may be specified multiple times)" 32 | ), 33 | ) 34 | parser.add_argument( 35 | "-F", "--error-format", 36 | default="{error.instance}: {error.message}\n", 37 | help=( 38 | "the format to use for each error output message, specified in " 39 | "a form suitable for passing to str.format, which will be called " 40 | "with 'error' for each error" 41 | ), 42 | ) 43 | parser.add_argument( 44 | "-V", "--validator", 45 | type=_namedAnyWithDefault, 46 | help=( 47 | "the fully qualified object name of a validator to use, or, for " 48 | "validators that are registered with jsonschema, simply the name " 49 | "of the class." 50 | ), 51 | ) 52 | parser.add_argument( 53 | "schema", 54 | help="the JSON Schema to validate with (i.e. filename.schema)", 55 | type=_json_file, 56 | ) 57 | 58 | 59 | def parse_args(args): 60 | arguments = vars(parser.parse_args(args=args or ["--help"])) 61 | if arguments["validator"] is None: 62 | arguments["validator"] = validator_for(arguments["schema"]) 63 | return arguments 64 | 65 | 66 | def main(args=sys.argv[1:]): 67 | sys.exit(run(arguments=parse_args(args=args))) 68 | 69 | 70 | def run(arguments, stdout=sys.stdout, stderr=sys.stderr): 71 | error_format = arguments["error_format"] 72 | validator = arguments["validator"](schema=arguments["schema"]) 73 | 74 | validator.check_schema(arguments["schema"]) 75 | 76 | errored = False 77 | for instance in arguments["instances"] or (): 78 | for error in validator.iter_errors(instance): 79 | stderr.write(error_format.format(error=error)) 80 | errored = True 81 | return errored 82 | -------------------------------------------------------------------------------- /sample_messages/ThermostatController/ThermostatController.SetTargetTemperature.TripleMode.response.json: -------------------------------------------------------------------------------- 1 | { 2 | "context": { 3 | "properties": [ 4 | { 5 | "namespace": "Alexa.ThermostatController", 6 | "name": "lowerSetpoint", 7 | "value": { 8 | "value": 68.0, 9 | "scale": "FAHRENHEIT" 10 | }, 11 | "timeOfSample": "2017-09-27T18:30:30.45Z", 12 | "uncertaintyInMilliseconds": 200 13 | }, 14 | { 15 | "namespace": "Alexa.ThermostatController", 16 | "name": "targetSetpoint", 17 | "value": { 18 | "value": 72.0, 19 | "scale": "FAHRENHEIT" 20 | }, 21 | "timeOfSample": "2017-09-27T18:30:30.45Z", 22 | "uncertaintyInMilliseconds": 200 23 | }, 24 | { 25 | "namespace": "Alexa.ThermostatController", 26 | "name": "upperSetpoint", 27 | "value": { 28 | "value": 76.0, 29 | "scale": "FAHRENHEIT" 30 | }, 31 | "timeOfSample": "2017-09-27T18:30:30.45Z", 32 | "uncertaintyInMilliseconds": 200 33 | }, 34 | { 35 | "namespace": "Alexa.ThermostatController", 36 | "name": "thermostatMode", 37 | "value": "AUTO", 38 | "timeOfSample": "2017-09-27T18:30:30.45Z", 39 | "uncertaintyInMilliseconds": 200 40 | }, 41 | { 42 | "namespace": "Alexa.EndpointHealth", 43 | "name": "connectivity", 44 | "value": { 45 | "value": "OK" 46 | }, 47 | "timeOfSample": "2017-09-27T18:30:30.45Z", 48 | "uncertaintyInMilliseconds": 200 49 | } 50 | ] 51 | }, 52 | "event": { 53 | "header": { 54 | "namespace": "Alexa", 55 | "name": "Response", 56 | "payloadVersion": "3", 57 | "messageId": "5f8a426e-01e4-4cc9-8b79-65f8bd0fd8a4", 58 | "correlationToken": "dFMb0z+PgpgdDmluhJ1LddFvSqZ/jCc8ptlAKulUj90jSqg==" 59 | }, 60 | "endpoint": { 61 | "scope": { 62 | "type": "BearerToken", 63 | "token": "access-token-from-Amazon" 64 | }, 65 | "endpointId": "endpoint-001" 66 | }, 67 | "payload": {} 68 | } 69 | } -------------------------------------------------------------------------------- /sample_backend/docs/000-setup-requirements.md: -------------------------------------------------------------------------------- 1 | # Step 0: Set Up the Required Accounts 2 | These are the required accounts needed for working through these instructions. 3 | 4 | #### 0.1 Verify the Required Accounts 5 | To work with these instructions, you will need both an Amazon Developer account and an Amazon Web Services account. 6 | 7 | ##### Amazon Developer Account 8 | Go to [https://developer.amazon.com](https://developer.amazon.com) and establish an account if you do not already have one. 9 | 10 | ##### Amazon Web Services Account 11 | Go to [https://aws.amazon.com](https://aws.amazon.com) and register for an Amazon Web Services (AWS) account if you do not already have one. 12 | 13 | #### 0.2 Open a Configuration File 14 | This configuration file is useful to store temporary IDs and other values during configuration of the environment. 15 | 16 | 0.2.1 Create a folder on your Desktop called `Alexa-SmartHome-Sample`. 17 | 18 | 0.2.2 Download https://raw.githubusercontent.com/alexa/alexa-smarthome/master/sample_backend/docs/config.txt into the `Alexa-SmartHome-Sample` folder. 19 | 20 | 0.2.3 Open and review the `config.txt` file: 21 | ``` 22 | Set in Step 1 - A unique API ID and name for the Alexa Smart Home Skill Lambda function 23 | [EndpointApiId] 24 | XXXXXXXXXX 25 | 26 | [SkillLambdaArn] 27 | arn:aws:lambda:us-east-1:XXXXXXXXXXXX:function:SampleSkillAdapter 28 | 29 | Set in Step 2 - The Client ID and Secret from the LWA Security Profile 30 | [Login with Amazon Client ID] 31 | amzn1.application-oa2-client.XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX 32 | 33 | [Login with Amazon Client Secret] 34 | XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX 35 | 36 | Set in Step 3 - A unique ID for the Alexa Smart Home Skill 37 | [Alexa Skill Application Id] 38 | amzn1.ask.skill.XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX 39 | 40 | Set in Step 4 - The Messaging Client and Secret from the Alexa Smart Home Skill 41 | [Alexa Skill Messaging Client Id] 42 | amzn1.application-oa2-client.XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX 43 | 44 | [Alexa Skill Messaging Client Secret] 45 | XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX 46 | 47 | Set in Step 6 - The profile user_id of the User 48 | [user_id] 49 | amzn1.account.XXXXXXXXXXXXXXXXXXXXXXXXXXXX 50 | ``` 51 | These placeholders represent the configuration entities to be collected or created for the environment. 52 | 53 | > TIP Keep secrets safe. If a Client Secret is compromised or needs to be reset, you will have to discard the secret and regenerate the Client ID and Secret again or recreate the profile. This will immediately sever the existing access relationships and customers will have to re-authenticate or re-link their account or skill. 54 | 55 |
56 | 57 | ____ 58 | Go to [Step 1: Set Up the Smart Home Skill Backend](001-setup-create-backend.md). 59 | -------------------------------------------------------------------------------- /sample_backend/docs/001-setup-create-backend.md: -------------------------------------------------------------------------------- 1 | # Step 1: Set Up the Smart Home Skill Backend 2 | These instructions create the backend services needed by the Smart Home Skill using a Cloud Formation stack. 3 | 4 | #### 1.1 Create the Backend Stack 5 | 6 | 1.1.1 Navigate to the Cloud Formation Console at https://console.aws.amazon.com/cloudformation/home?region=us-east-1 and authenticate using your AWS account. 7 | 8 | 1.1.2 Verify you are in the N. Virginia (us-east-1) region and click the **Create Stack** button in the top left of the page. 9 | 10 | 1.1.3 In the _Select Template_ section, select the **Specify an Amazon S3 template URL** radio button and enter the following URL `https://s3.amazonaws.com/endpoint-code-us/backend.template.us` into the text field. 11 | 12 | 1.1.4 Click **Next** on the bottom right of the page. 13 | 14 | 1.1.5 On the _Specify Details_ page and in the **Stack name** text input box, enter `Sample-Smart-Home-Backend` and then click **Next**. 15 | 16 | 1.1.6 When on the _Options_ page, click **Next** without making any changes. 17 | 18 | 1.1.7 On the _Review_ page, click the checkbox "I acknowledge that AWS CloudFormation might create IAM resources with custom names." and then click the **Create** button. 19 | 20 | > This IAM resource warning comes from the need for the Stack to create an Execution Role for the created Lambda functions. 21 | 22 | 1.1.8 In the Stacks list in the Cloud Formation console, the newly created Sample-Smart-Home-Backend stack will be created and initially have a status of _CREATE\_IN\_PROGRESS_. When the backend stack has been created and is ready, the status will change to _CREATE\_COMPLETE_. This may take 1-3 minutes. 23 | 24 | > If any issues arise, you should see a ROLLBACK\_IN\_PROGRESS message and ultimately a ROLLBACK\_COMPLETE status. 25 | 26 | #### 1.2 Locate the _Sample-Smart-Home-Backend_ Stack Outputs 27 | 28 | 1.2.1 While still in the Cloud Formation console, select the check box for _Sample-Smart-Home-Backend_ and then select the _Outputs_ details tab. If the tabs are not visible along the bottom of the Cloud Formation console, click the _Restore_ or _Maximize_ buttons in the bottom right. 29 | 30 | 1.2.2 Locate the _EndpointApiId_ **Value** that will look something like the following example: `y053kmfr5m` and copy it to the `config.txt` file into the [EndpointApiId] section. 31 | 32 | 1.2.3 Locate the _SkillLambdaArn_ **Value** that is formatted like the following example: `arn:aws:lambda:us-east-1:############:function:SampleSkillAdapter` and copy it into the [SkillLambdaArn] section of `config.txt`. 33 | 34 | 35 |
36 | 37 | ____ 38 | Go to [Step 2: Set Up Login with Amazon](002-setup-lwa.md). 39 | -------------------------------------------------------------------------------- /sample_backend/docs/002-setup-lwa.md: -------------------------------------------------------------------------------- 1 | # Step 2: Set Up Login with Amazon 2 | For the sample environment, a development Login with Amazon (LWA) security profile will be used for configuring Account Linking, which is required for a Smart Home Skill. 3 | 4 | #### 2.1 Create a Login with Amazon Security Profile 5 | 6 | 2.1.1 In your web browser, go to [https://developer.amazon.com/lwa/sp/overview.html](https://developer.amazon.com/lwa/sp/overview.html) and make sure _APPS & SERVICES_ is selected in the top menu and _Login with Amazon_ is selected in the sub menu. 7 | 8 | 2.1.2 On the _Login with Amazon_ page, click the **Create a New Security Profile** button. 9 | 10 | 2.1.3 On the Security Profile Management page, enter `Sample Alexa Smart Home` for the **Security Profile Name**. 11 | 12 | 2.1.4 For the **Security Profile Description** enter `A sample security profile for Alexa Smart Home Skill development`. 13 | 14 | 2.1.5 For the **Consent Privacy Notice URL** enter `http://example.com/privacy.html` for illustrative purposes or use your own if you already have a public consent privacy policy. 15 | 16 | > For a production Smart Home Skill, a valid consent privacy notice will be required. 17 | 18 | 2.1.6 For the **Consent Logo Image** download https://raw.githubusercontent.com/alexa/alexa-smarthome/master/sample_backend/docs/img/alexa-sample-smarthome-150x.png into the `Alexa-SmartHome-Sample` directory created on your Desktop and then click the **Upload Image** area to load the file from where you saved it. 19 | 20 | 2.1.7 If your profile configuration looks like the following, click **Save** on the Security Profile Management page. 21 | 22 | ![Security Profile Configuration](img/2.1.7-lwa-profile-configuration.png "Security Profile Configuration") 23 | 24 | 25 | > If successful, a message similar to 'Login with Amazon successfully enabled for Security Profile. Click (gear) to manage Security Profile.' will be returned. 26 | 27 | 2.1.8 From the list of Security Profiles, click the **Show Client ID and Client Secret** link for the _Sample Alexa Smart Home_ profile. 28 | 29 | 2.1.9 Copy the displayed Client ID and Client Secret values to the `config.txt` file replacing the template entries for [Login with Amazon Client ID] and [Login with Amazon Client Secret] respectively: 30 | 31 | ``` 32 | [Login with Amazon Client ID] 33 | amzn1.application-oa2-client.XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX 34 | 35 | [Login with Amazon Client Secret] 36 | XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX 37 | ``` 38 | > Further configuration of the Security Profile Allowed Return URLs will be done during configuration of the Alexa Smart Home Skill. 39 | 40 |
41 | 42 | ____ 43 | Go to [Step 3: Create the Alexa Smart Home Skill](003-setup-create-skill-smarthome.md). 44 | -------------------------------------------------------------------------------- /sample_backend/lambda/lambda_api/python/alexa/skills/smarthome/alexa_power_controller.py: -------------------------------------------------------------------------------- 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 | import uuid 15 | from .alexa_utils import get_utc_timestamp 16 | 17 | 18 | class AlexaPowerController: 19 | 20 | def __init__(self, **kwargs): 21 | self.namespace = kwargs.get('namespace', 'Alexa') 22 | self.name = kwargs.get('name', 'Response') 23 | self.payload_version = kwargs.get('payload_version', '3') 24 | self.message_id = kwargs.get('message_id', str(uuid.uuid4())) 25 | self.correlation_token = kwargs.get('correlation_token', None) 26 | 27 | self.type = "BearerToken" 28 | self.token = kwargs.get('token', None) 29 | 30 | self.endpoint_id = kwargs.get('endpoint_id', 'INVALID') 31 | 32 | self.value = kwargs.get('value', 'TurnOff') 33 | 34 | def create_property(self, **kwargs): 35 | p = {} 36 | p['namespace'] = kwargs.get('namespace', 'Alexa.EndpointHealth') 37 | p['name'] = kwargs.get('name', 'connectivity') 38 | p['value'] = kwargs.get('value', {'value': 'OK'}) 39 | p['timeOfSample'] = get_utc_timestamp() 40 | p['uncertaintyInMilliseconds'] = kwargs.get('uncertainty_in_milliseconds', 0) 41 | return p 42 | 43 | def get_response(self): 44 | 45 | powerStateValue = 'OFF' if self.value == "TurnOff" else 'ON' 46 | 47 | properties = [] 48 | properties.append(self.create_property(namespace='Alexa.PowerController', name='powerState', value=powerStateValue)) 49 | properties.append(self.create_property(namespace='Alexa.EndpointHealth', name='connectivity', value={'value': 'OK'})) 50 | 51 | header = {} 52 | header['namespace'] = 'Alexa' 53 | header['name'] = 'Response' 54 | header['payloadVersion'] = self.payload_version 55 | header['messageId'] = self.message_id 56 | header['correlationToken'] = self.correlation_token 57 | 58 | endpoint = {} 59 | endpoint['scope'] = {} 60 | endpoint['scope']['type'] = 'BearerToken' 61 | endpoint['scope']['token'] = self.token 62 | endpoint['endpointId'] = self.endpoint_id 63 | 64 | payload = {} 65 | 66 | response = {} 67 | 68 | response['context'] = {} 69 | response['context']['properties'] = properties 70 | 71 | response['event'] = {} 72 | response['event']['header'] = header 73 | response['event']['endpoint'] = endpoint 74 | response['event']['payload'] = payload 75 | 76 | return response -------------------------------------------------------------------------------- /sample_backend/lambda/lambda_api/python/alexa/skills/smarthome/alexa_change_report.py: -------------------------------------------------------------------------------- 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 | import uuid 15 | from .alexa_utils import get_utc_timestamp 16 | 17 | 18 | class AlexaChangeReport: 19 | 20 | def __init__(self, **kwargs): 21 | self.namespace = kwargs.get('namespace', 'Alexa') 22 | self.name = kwargs.get('name', 'ChangeReport') 23 | self.payload_version = kwargs.get('payload_version', '3') 24 | self.message_id = kwargs.get('message_id', str(uuid.uuid4())) 25 | 26 | self.type = "BearerToken" 27 | self.token = kwargs.get('token', 'INVALID') 28 | 29 | self.endpoint_id = kwargs.get('endpoint_id', 'INVALID') 30 | 31 | self.cause_type = kwargs.get('cause_type', 'PHYSICAL_INTERACTION') 32 | 33 | def create_property(self, **kwargs): 34 | p = {} 35 | p['namespace'] = kwargs.get('namespace', 'Alexa') 36 | p['name'] = kwargs.get('name', 'powerState') 37 | p['value'] = kwargs.get('value', 'ON') 38 | p['timeOfSample'] = get_utc_timestamp() 39 | p['uncertaintyInMilliseconds'] = kwargs.get('uncertainty_in_milliseconds', 0) 40 | return p 41 | 42 | def get_response(self): 43 | response = {} 44 | response['context'] = {} 45 | response['context']['properties'] = [] 46 | response['context']['properties'].append(self.create_property(namespace='Alexa.PowerController', name='powerState', value='ON')) 47 | response['context']['properties'].append(self.create_property(namespace='Alexa.EndpointHealth', name='connectivity', value={'value': 'OK'})) 48 | response['event'] = {} 49 | response['event']['header'] = {} 50 | response['event']['header']['namespace'] = self.namespace 51 | response['event']['header']['name'] = self.name 52 | response['event']['header']['payloadVersion'] = self.payload_version 53 | response['event']['header']['messageId'] = self.message_id 54 | response['event']['endpoint'] = {} 55 | response['event']['endpoint']['scope'] = {} 56 | response['event']['endpoint']['scope']['type'] = self.type 57 | response['event']['endpoint']['scope']['token'] = self.token 58 | response['event']['endpoint']['endpointId'] = self.endpoint_id 59 | response['event']['payload'] = {} 60 | response['event']['payload']['change'] = {} 61 | response['event']['payload']['change']['cause'] = {} 62 | response['event']['payload']['change']['cause']['type'] = self.cause_type 63 | response['event']['payload']['change']['properties'] = [] 64 | response['event']['payload']['change']['properties'].append(self.create_property(namespace='Alexa.BrightnessController', name='brightness', value=65)) 65 | 66 | return response 67 | -------------------------------------------------------------------------------- /sample_backend/docs/005-setup-link-skill-smarthome.md: -------------------------------------------------------------------------------- 1 | # Step 5: Link the Alexa Smart Home Skill 2 | Finalize the Lambda configuration and link the Alexa Smart Home Skill to your account. 3 | 4 | #### 5.1 Update the Skill Lambda Environment variables 5 | With a completed Alexa Smart Home Skill configuration, the Alexa Skill Client ID and Client Secret will need to be passed to the backend. To accomplish this, you can set the environment variables of the Lambda that is handling the Endpoint interactions. 6 | 7 | 5.1.2 Browse to https://us-east-1.console.aws.amazon.com/lambda/home?region=us-east-1#/functions/SampleEndpointAdapter?tab=configuration. 8 | 9 | 5.1.3 Expand the **Environment variables** section add a Key called `client_id`. 10 | 11 | 5.1.4 For the _client\_id_ value enter the value stored in [Alexa Skill Messaging Client Id] from the `config.txt` file. 12 | 13 | 5.1.5 In the **Environment variables** section add another Key called `client_secret`. 14 | 15 | 5.1.6 For the _client\_secret_ value enter the value stored in [Alexa Messaging Skill Client Secret] from the `config.txt` file. 16 | 17 | 5.1.7 When both the _client\_id_ and _client\_secret_ environment variables are added, click **Save** at the top of the page. 18 | 19 | #### 5.2 Link the Alexa Smart Home Skill 20 | 21 | 5.2.1 Go to https://alexa.amazon.com/ and select **Skills** from the left menu. 22 | 23 | > Tip: Replace the skill value at the end of https://alexa.amazon.com/spa/index.html#skills/beta/ALEXA_SKILL_ID to go directly to a skill. 24 | > For example, https://alexa.amazon.com/spa/index.html#skills/beta/amzn1.ask.skill.203e1508-e33b-4b63-8e0e-70b97e45408d 25 | 26 | 27 | 5.2.2 Click **Your Skills** from the top right of the section. 28 | 29 | 5.2.3 Locate your Sample Smart Home Skill in the list of skills and click on it. 30 | 31 | ![Smart Home Skill Example](img/5.2.3-smart-home-skill.png "Smart Home Skill Example") 32 | 33 | 5.2.4 On the _Sample Smart Home Skill_ page, click **Enable** in the top right and authenticate with your Amazon account. 34 | 35 | 5.2.5 On authentication, verify you are presented with the _Sample Alexa Smart Home_ authentication dialog and authenticate using your Amazon account. The transitional page will look something like the following in a browser: 36 | 37 | ![Linking Authentication](img/5.2.5-linking-dialog.png "Linking Authentication") 38 | 39 | 5.2.6 Click **Allow** to link your Account with the _Sample Alexa Smart Home_ skill. 40 | 41 | 5.2.7 On success, you should be presented with a window that reads "To continue, close this window to discover devices you can control with Alexa." Close this page and return to the _Sample Smart Home Skill_. 42 | 43 | 5.2.8 When redirected back to the Skill page, you will be prompted 'Discover Devices'. Click **Discover Devices** to start the device discovery process. This may take up to 20 seconds to complete. 44 | 45 | > No new devices from the Sample Smart Home Skill will be returned. This is expected. 46 | 47 |
48 | 49 | ____ 50 | Go to [Step 6: Create the Endpoints](006-setup-create-endpoints.md). 51 | -------------------------------------------------------------------------------- /sample_backend/docs/003-setup-create-skill-smarthome.md: -------------------------------------------------------------------------------- 1 | # Step 3: Create the Alexa Smart Home Skill 2 | Create an Alexa Smart Home Skill that will process the Smart Home commands. 3 | 4 | #### 3.1 Navigate to the Alexa Skills page 5 | 6 | 3.1.1 In a web browser to the Amazon Developer Console at [https://developer.amazon.com/home.html](https://developer.amazon.com/home.html). If not already authenticated, you may have to _Sign In_ with your Amazon Developer Account . 7 | 8 | 3.1.2 Select the _Alexa_ tab from the top menu. 9 | 10 | 3.1.3 On the _Get started with Alexa_ page and in the _Alexa Skills Kit_ box, click the **Get Started >** button. 11 | 12 | 3.1.4 From the _Building Alexa Skills with the Alexa Skills Kit_ page, click the **Add a New Skill** button on the top right of the page. 13 | 14 | #### 3.2 Create a New Alexa Skill 15 | 16 | 3.2.1 On the _Skill Information_ tab, from the **Skill Type** radio buttons select **Smart Home Skill API** 17 | 18 | 3.2.2 Leave the language as English. 19 | 20 | > For more information on adding another language to your skill, see [Develop Smart Home Skills in Multiple Languages](https://developer.amazon.com/docs/smarthome/develop-smart-home-skills-in-multiple-languages.html). 21 | 22 | 3.2.3 Enter `Sample Smart Home Skill` as the Name of the skill. 23 | 24 | 3.2.4 Under Payload Version, verify **v3 (preferred)** is selected. 25 | 26 | 3.2.5 Click **Save** 27 | 28 | #### 3.3 Collect the Application Id of _Sample Smart Home Skill_ 29 | 30 | 3.3.1 When the skill is saved, the page will refresh. From the refreshed skill information, copy the _Application Id_ of the Alexa Skill to the `config.txt` file into the [Alexa Skill Application Id] value. The format of the Application Id will look like following: 31 | 32 | ``` 33 | amzn1.ask.skill.xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx 34 | ``` 35 | 36 | 3.3.2 Copy the Application Id value to the clipboard. 37 | 38 | #### 3.4 Add a Smart Home Trigger to the Alexa Smart Home Skill Lambda 39 | 40 | 3.4.1 In a new tab, browse to https://us-east-1.console.aws.amazon.com/lambda/home?region=us-east-1#/functions/SampleSkillAdapter?tab=triggers. 41 | 42 | 3.4.2 Click the **+ Add trigger** button. 43 | 44 | 3.4.3 In the _Add trigger_ dialog, click the empty box and then select **Alexa Smart Home** from the drop down menu. 45 | 46 | ![Trigger Dialog Example](img/3.4.3-lambda-trigger.png "Trigger Dialog Example") 47 | 48 | 3.4.4 Paste the Alexa Skill Application Id value from the clipboard into the _Application Id_ text box. If you no longer have the value on your clipboard, you can retrieve it from the [Alexa Skill Application Id] section of the `config.txt` file. 49 | 50 | 3.4.5 Verify **Enable trigger** is checked and then click **Submit**. If successful, a "Successfully added the trigger..." message will be returned. 51 | 52 | 3.4.6 Close this tab and return to the _Sample Smart Home Skill_ in the Alexa Skills section of the Amazon Developer Console. 53 | 54 | 3.4.7 Click **Next** to move to the _Sample Smart Home Skill_ Interaction model and then click **Next** again to move to the Configuration section. 55 | 56 | > TIP: If you need to return to the configuration page, you can replace SKILL_APPLICATION_ID in the following URL with your copied Application Id for a direct link. 57 | > ``` 58 | > https://developer.amazon.com/edw/home.html#/skill/SKILL_APPLICATION_ID/en_US/configuration 59 | > ``` 60 | 61 |
62 | 63 | ____ 64 | Go to [Step 4: Configure the Alexa Smart Home Skill](004-setup-configure-skill-smarthome.md). 65 | -------------------------------------------------------------------------------- /sample_backend/lambda/lambda_api/python/alexa/skills/smarthome/alexa_discover_response.py: -------------------------------------------------------------------------------- 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 | import random 15 | import uuid 16 | 17 | from .alexa_utils import get_utc_timestamp 18 | 19 | 20 | class AlexaDiscoverResponse: 21 | 22 | def __init__(self, request): 23 | self.message_id = str(uuid.uuid4()) 24 | self.name = 'Discover.Response' 25 | self.payload_version = request["directive"]["header"]["payloadVersion"] 26 | self.endpoints = [] 27 | 28 | def add_endpoint(self, thing): 29 | # Translate the AWS IoT Thing attributes 30 | endpoint_id_value = thing['thingName'] 31 | # HACK Using the thing name as the friendly name! 32 | friendly_name_value = thing['thingName'].replace('_', ' ') 33 | # NOTE SWITCH is currently hardcoded into the endpoint 34 | self.endpoints.append(self.create_endpoint(endpoint_id=endpoint_id_value, friendly_name=friendly_name_value, display_categories=['SWITCH'])) 35 | 36 | def create_capability(self, **kwargs): 37 | capability = {} 38 | capability['type'] = kwargs.get('type', 'AlexaInterface') 39 | capability['interface'] = kwargs.get('interface', 'Alexa') 40 | capability['version'] = kwargs.get('version', '3') 41 | supported = kwargs.get('supported', None) # [{"name": "powerState"}] 42 | if supported: 43 | capability['properties'] = {} 44 | capability['properties']['supported'] = supported 45 | capability['properties']['proactivelyReported'] = kwargs.get('proactively_reported', True) 46 | capability['properties']['retrievable'] = kwargs.get('retrievable', True) 47 | 48 | return capability 49 | 50 | def create_endpoint(self, **kwargs): 51 | endpoint = {} 52 | endpoint['endpointId'] = kwargs.get('endpoint_id', 'endpoint_' + "%0.6d" % random.randint(0, 999999)) 53 | endpoint['friendlyName'] = kwargs.get('friendly_name', 'Endpoint') 54 | endpoint['description'] = kwargs.get('description', 'Endpoint Description') 55 | endpoint['manufacturerName'] = kwargs.get('manufacturer_name', 'Unknown Manufacturer') 56 | endpoint['displayCategories'] = kwargs.get('display_categories', ['OTHER']) 57 | 58 | # NOTE: These capabilities are hardcoded, how might we expose them differently? 59 | endpoint['capabilities'] = [] 60 | endpoint['capabilities'].append(self.create_capability()) 61 | endpoint['capabilities'].append(self.create_capability(interface='Alexa.PowerController', supported=[{"name": "powerState"}])) 62 | endpoint['capabilities'].append(self.create_capability(interface='Alexa.EndpointHealth', supported=[{"name": "connectivity"}])) 63 | return endpoint 64 | 65 | def create_property(self, **kwargs): 66 | p = {} 67 | p['namespace'] = kwargs.get('namespace', 'Alexa') 68 | p['name'] = 'powerState' 69 | p['value'] = 'ON' 70 | p['timeOfSample'] = get_utc_timestamp() 71 | p['uncertaintyInMilliseconds'] = kwargs.get('uncertainty_in_milliseconds', 0) 72 | return p 73 | 74 | def get_response(self): 75 | response = {} 76 | # context = {} 77 | event = {} 78 | 79 | # properties = [] 80 | # properties.append(self.create_property(namespace='Alexa.PowerController')) 81 | # context['properties'] = properties 82 | 83 | header = {} 84 | header['namespace'] = 'Alexa.Discovery' 85 | header['name'] = self.name 86 | header['payloadVersion'] = self.payload_version 87 | header['messageId'] = self.message_id 88 | 89 | # self.endpoints.append(self.create_endpoint()) 90 | 91 | payload = {} 92 | payload['endpoints'] = self.endpoints 93 | 94 | event['header'] = header 95 | event['payload'] = payload 96 | 97 | # response['context'] = context 98 | response['event'] = event 99 | 100 | return response 101 | -------------------------------------------------------------------------------- /sample_lambda/python/jsonschema/tests/test_cli.py: -------------------------------------------------------------------------------- 1 | from jsonschema import Draft4Validator, ValidationError, cli 2 | from jsonschema.compat import StringIO 3 | from jsonschema.exceptions import SchemaError 4 | from jsonschema.tests.compat import mock, unittest 5 | 6 | 7 | def fake_validator(*errors): 8 | errors = list(reversed(errors)) 9 | 10 | class FakeValidator(object): 11 | def __init__(self, *args, **kwargs): 12 | pass 13 | 14 | def iter_errors(self, instance): 15 | if errors: 16 | return errors.pop() 17 | return [] 18 | 19 | def check_schema(self, schema): 20 | pass 21 | 22 | return FakeValidator 23 | 24 | 25 | class TestParser(unittest.TestCase): 26 | FakeValidator = fake_validator() 27 | 28 | def setUp(self): 29 | mock_open = mock.mock_open() 30 | patch_open = mock.patch.object(cli, "open", mock_open, create=True) 31 | patch_open.start() 32 | self.addCleanup(patch_open.stop) 33 | 34 | mock_json_load = mock.Mock() 35 | mock_json_load.return_value = {} 36 | patch_json_load = mock.patch("json.load") 37 | patch_json_load.start() 38 | self.addCleanup(patch_json_load.stop) 39 | 40 | def test_find_validator_by_fully_qualified_object_name(self): 41 | arguments = cli.parse_args( 42 | [ 43 | "--validator", 44 | "jsonschema.tests.test_cli.TestParser.FakeValidator", 45 | "--instance", "foo.json", 46 | "schema.json", 47 | ] 48 | ) 49 | self.assertIs(arguments["validator"], self.FakeValidator) 50 | 51 | def test_find_validator_in_jsonschema(self): 52 | arguments = cli.parse_args( 53 | [ 54 | "--validator", "Draft4Validator", 55 | "--instance", "foo.json", 56 | "schema.json", 57 | ] 58 | ) 59 | self.assertIs(arguments["validator"], Draft4Validator) 60 | 61 | 62 | class TestCLI(unittest.TestCase): 63 | def test_draft3_schema_draft4_validator(self): 64 | stdout, stderr = StringIO(), StringIO() 65 | with self.assertRaises(SchemaError): 66 | cli.run( 67 | { 68 | "validator": Draft4Validator, 69 | "schema": { 70 | "anyOf": [ 71 | {"minimum": 20}, 72 | {"type": "string"}, 73 | {"required": True}, 74 | ], 75 | }, 76 | "instances": [1], 77 | "error_format": "{error.message}", 78 | }, 79 | stdout=stdout, 80 | stderr=stderr, 81 | ) 82 | 83 | def test_successful_validation(self): 84 | stdout, stderr = StringIO(), StringIO() 85 | exit_code = cli.run( 86 | { 87 | "validator": fake_validator(), 88 | "schema": {}, 89 | "instances": [1], 90 | "error_format": "{error.message}", 91 | }, 92 | stdout=stdout, 93 | stderr=stderr, 94 | ) 95 | self.assertFalse(stdout.getvalue()) 96 | self.assertFalse(stderr.getvalue()) 97 | self.assertEqual(exit_code, 0) 98 | 99 | def test_unsuccessful_validation(self): 100 | error = ValidationError("I am an error!", instance=1) 101 | stdout, stderr = StringIO(), StringIO() 102 | exit_code = cli.run( 103 | { 104 | "validator": fake_validator([error]), 105 | "schema": {}, 106 | "instances": [1], 107 | "error_format": "{error.instance} - {error.message}", 108 | }, 109 | stdout=stdout, 110 | stderr=stderr, 111 | ) 112 | self.assertFalse(stdout.getvalue()) 113 | self.assertEqual(stderr.getvalue(), "1 - I am an error!") 114 | self.assertEqual(exit_code, 1) 115 | 116 | def test_unsuccessful_validation_multiple_instances(self): 117 | first_errors = [ 118 | ValidationError("9", instance=1), 119 | ValidationError("8", instance=1), 120 | ] 121 | second_errors = [ValidationError("7", instance=2)] 122 | stdout, stderr = StringIO(), StringIO() 123 | exit_code = cli.run( 124 | { 125 | "validator": fake_validator(first_errors, second_errors), 126 | "schema": {}, 127 | "instances": [1, 2], 128 | "error_format": "{error.instance} - {error.message}\t", 129 | }, 130 | stdout=stdout, 131 | stderr=stderr, 132 | ) 133 | self.assertFalse(stdout.getvalue()) 134 | self.assertEqual(stderr.getvalue(), "1 - 9\t1 - 8\t2 - 7\t") 135 | self.assertEqual(exit_code, 1) 136 | -------------------------------------------------------------------------------- /sample_backend/lambda/lambda_api/python/jsonschema/tests/test_cli.py: -------------------------------------------------------------------------------- 1 | from jsonschema import Draft4Validator, ValidationError, cli 2 | from jsonschema.compat import StringIO 3 | from jsonschema.exceptions import SchemaError 4 | from jsonschema.tests.compat import mock, unittest 5 | 6 | 7 | def fake_validator(*errors): 8 | errors = list(reversed(errors)) 9 | 10 | class FakeValidator(object): 11 | def __init__(self, *args, **kwargs): 12 | pass 13 | 14 | def iter_errors(self, instance): 15 | if errors: 16 | return errors.pop() 17 | return [] 18 | 19 | def check_schema(self, schema): 20 | pass 21 | 22 | return FakeValidator 23 | 24 | 25 | class TestParser(unittest.TestCase): 26 | FakeValidator = fake_validator() 27 | 28 | def setUp(self): 29 | mock_open = mock.mock_open() 30 | patch_open = mock.patch.object(cli, "open", mock_open, create=True) 31 | patch_open.start() 32 | self.addCleanup(patch_open.stop) 33 | 34 | mock_json_load = mock.Mock() 35 | mock_json_load.return_value = {} 36 | patch_json_load = mock.patch("json.load") 37 | patch_json_load.start() 38 | self.addCleanup(patch_json_load.stop) 39 | 40 | def test_find_validator_by_fully_qualified_object_name(self): 41 | arguments = cli.parse_args( 42 | [ 43 | "--validator", 44 | "jsonschema.tests.test_cli.TestParser.FakeValidator", 45 | "--instance", "foo.json", 46 | "schema.json", 47 | ] 48 | ) 49 | self.assertIs(arguments["validator"], self.FakeValidator) 50 | 51 | def test_find_validator_in_jsonschema(self): 52 | arguments = cli.parse_args( 53 | [ 54 | "--validator", "Draft4Validator", 55 | "--instance", "foo.json", 56 | "schema.json", 57 | ] 58 | ) 59 | self.assertIs(arguments["validator"], Draft4Validator) 60 | 61 | 62 | class TestCLI(unittest.TestCase): 63 | def test_draft3_schema_draft4_validator(self): 64 | stdout, stderr = StringIO(), StringIO() 65 | with self.assertRaises(SchemaError): 66 | cli.run( 67 | { 68 | "validator": Draft4Validator, 69 | "schema": { 70 | "anyOf": [ 71 | {"minimum": 20}, 72 | {"type": "string"}, 73 | {"required": True}, 74 | ], 75 | }, 76 | "instances": [1], 77 | "error_format": "{error.message}", 78 | }, 79 | stdout=stdout, 80 | stderr=stderr, 81 | ) 82 | 83 | def test_successful_validation(self): 84 | stdout, stderr = StringIO(), StringIO() 85 | exit_code = cli.run( 86 | { 87 | "validator": fake_validator(), 88 | "schema": {}, 89 | "instances": [1], 90 | "error_format": "{error.message}", 91 | }, 92 | stdout=stdout, 93 | stderr=stderr, 94 | ) 95 | self.assertFalse(stdout.getvalue()) 96 | self.assertFalse(stderr.getvalue()) 97 | self.assertEqual(exit_code, 0) 98 | 99 | def test_unsuccessful_validation(self): 100 | error = ValidationError("I am an error!", instance=1) 101 | stdout, stderr = StringIO(), StringIO() 102 | exit_code = cli.run( 103 | { 104 | "validator": fake_validator([error]), 105 | "schema": {}, 106 | "instances": [1], 107 | "error_format": "{error.instance} - {error.message}", 108 | }, 109 | stdout=stdout, 110 | stderr=stderr, 111 | ) 112 | self.assertFalse(stdout.getvalue()) 113 | self.assertEqual(stderr.getvalue(), "1 - I am an error!") 114 | self.assertEqual(exit_code, 1) 115 | 116 | def test_unsuccessful_validation_multiple_instances(self): 117 | first_errors = [ 118 | ValidationError("9", instance=1), 119 | ValidationError("8", instance=1), 120 | ] 121 | second_errors = [ValidationError("7", instance=2)] 122 | stdout, stderr = StringIO(), StringIO() 123 | exit_code = cli.run( 124 | { 125 | "validator": fake_validator(first_errors, second_errors), 126 | "schema": {}, 127 | "instances": [1, 2], 128 | "error_format": "{error.instance} - {error.message}\t", 129 | }, 130 | stdout=stdout, 131 | stderr=stderr, 132 | ) 133 | self.assertFalse(stdout.getvalue()) 134 | self.assertEqual(stderr.getvalue(), "1 - 9\t1 - 8\t2 - 7\t") 135 | self.assertEqual(exit_code, 1) 136 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 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. 97 | -------------------------------------------------------------------------------- /sample_backend/lambda/lambda_api/python/index.py: -------------------------------------------------------------------------------- 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 | import json 15 | import os 16 | from endpoint_cloud import ApiAuth, ApiHandler, ApiResponse, ApiResponseBody 17 | 18 | 19 | def get_api_url(api_id, aws_region, resource): 20 | return 'https://{0}.execute-api.{1}.amazonaws.com/prod/{2}'.format(api_id, aws_region, resource) 21 | 22 | 23 | def handler(request, context): 24 | """ 25 | Main Lambda Handler 26 | :param request Incoming API Request 27 | :param context Context for the Request 28 | """ 29 | 30 | print("LOG api.index.handler.request:", request) 31 | 32 | # An API Handler to handle internal operations to the endpoints 33 | api_handler = ApiHandler() 34 | 35 | # An API Response crafted to return to the caller - in this case the API Gateway 36 | # The API Gateway expects a specially formatted response 37 | api_response = ApiResponse() 38 | 39 | try: 40 | # Get the Environment Variables, these are used to dynamically compose the API URI and pass Alexa Skill Messaging credentials 41 | 42 | # Get the Region 43 | env_aws_default_region = os.environ.get('AWS_DEFAULT_REGION', None) 44 | if env_aws_default_region is None: 45 | print("ERROR skill.index.handler.aws_default_region is None default to us-east-1") 46 | env_aws_default_region = 'us-east-1' 47 | 48 | # Get the API ID, Client ID, and Client Secret 49 | env_api_id = os.environ.get('api_id', None) 50 | env_client_id = os.environ.get('client_id', None) 51 | env_client_secret = os.environ.get('client_secret', None) 52 | if env_api_id is None or env_client_id is None or env_client_secret is None: 53 | api_response.statusCode = 403 54 | api_response.body = ApiResponseBody(result="ERR", message="Environment variable is not set: api_id:{0} client_id:{1} client_secret:{2}".format(env_api_id, env_client_id, env_client_secret)) 55 | return api_response.create() 56 | 57 | # Reject the request if it isn't from our API 58 | api_id = request['requestContext']['apiId'] 59 | if api_id != env_api_id: 60 | api_response.statusCode = 403 61 | api_response.body = ApiResponseBody(result="ERR", message="api_id did not match") 62 | return api_response.create() 63 | 64 | # Route the inbound request by evaluating for the resource and HTTP method 65 | resource = request["resource"] 66 | http_method = request["httpMethod"] 67 | 68 | # POST to directives : Process an Alexa Directive - This will be used to implement Endpoint behavior and state 69 | if http_method == 'POST' and resource == '/directives': 70 | response = api_handler.directive.process(request, env_client_id, env_client_secret, get_api_url(env_api_id, env_aws_default_region, 'auth-redirect')) 71 | print('LOG api.index.handler.request.api_handler.directive.process.response:', response) 72 | response_name = json.loads(response) 73 | if response_name['event']['header']['name'] == 'ErrorResponse': 74 | api_response.statusCode = 500 75 | else: 76 | api_response.statusCode = 200 77 | api_response.body = response 78 | 79 | # POST to endpoints : Create an Endpoint 80 | if http_method == 'POST' and resource == '/endpoints': 81 | response = api_handler.endpoint.create(request) 82 | api_response.statusCode = 200 83 | api_response.body = json.dumps(response) 84 | 85 | # GET endpoints : List Endpoints 86 | if http_method == 'GET' and resource == '/endpoints': 87 | response = api_handler.endpoint.read(request) 88 | api_response.statusCode = 200 89 | api_response.body = json.dumps(response) 90 | 91 | # POST to event : Create an Event - This will be used to trigger a Proactive State Update 92 | if http_method == 'POST' and resource == '/events': 93 | response = api_handler.event.create(request) 94 | print('LOG api.index.handler.request.api_handler.event.create.response:', response) 95 | api_response.statusCode = 200 96 | api_response.body = json.dumps(response) 97 | 98 | except KeyError as key_error: 99 | # For a key Error, return an error message and HTTP Status of 400 Bad Request 100 | message_string = "KeyError: " + str(key_error) 101 | api_response.statusCode = 400 102 | api_response.body = ApiResponseBody(result="ERR", message=message_string) 103 | 104 | return api_response.create() 105 | -------------------------------------------------------------------------------- /sample_backend/docs/006-setup-create-endpoints.md: -------------------------------------------------------------------------------- 1 | # Step 6: Create the Endpoints 2 | Create endpoints to be discovered during the Alexa Smart Home Skill Discovery. 3 | 4 | 5 | #### 6.1 Set Up Postman 6 | Postman is a tool for managing and executing HTTP requests and is very useful for API development and usage. 7 | 8 | ##### 6.1.1 Install Postman 9 | 6.1.1 Go to [getpostman.com](https://www.getpostman.com) and download and install the correct Postman application for your platform. 10 | 11 | 6.1.2 Download the Postman Sample Smart Home Collection from https://raw.githubusercontent.com/alexa/alexa-smarthome/master/sample_backend/lambda/lambda_api/sample_backend.postman_collection.json into the `Alexa-SmartHome-Sample` directory on your Desktop. 12 | 13 | ##### 6.1.2 Import the _Alexa Smart Home (sample_backend)_ Postman collection 14 | 15 | 6.1.2.1 Open Postman. 16 | 17 | 6.1.2.2 In Postman, click **Import** from the main menu and browse to the `sample_backend.postman_collection.json` file or drag it onto the _Import_ dialog. 18 | 19 | ##### 6.1.3 Create a Postman environment 20 | To fill out the variable values of the configuration use a Postman environment to store configuration-specific values. 21 | 22 | 6.1.3.1 In the top right of Postman, click the gear icon to open the _Environment options_ drop down menu and select **Manage Environments**. 23 | 24 | ![Postman - Manage Environments](img/6.1.3.1-postman-manage-environments.png "Postman - Manage Environments") 25 | 26 | 6.1.3.2 In opened _MANAGE ENVIRONMENTS_ dialog, click the **Add** in the bottom right. 27 | 28 | 6.1.3.3 For the _Environment Name_ enter `Alexa Smart Home (sample_backend)`. 29 | 30 | 6.1.3.4 Add a **Key** value called `aws_region` and set its **Value** to `us-east-1`. 31 | 32 | 6.1.3.5 Add another **Key** value called `endpoint_api_id` and set its **Value** to the [EndpointApiId] value from the `config.txt` file. 33 | 34 | 6.1.3.6 Click the **Add** button again to save the environment settings. 35 | 36 | 6.1.3.6 Close the _MANAGE ENVIRONMENTS_ dialog and in the top right of Postman select the newly created _Alexa Smart Home (sample_backend)_ environment from the environment drop down menu. 37 | 38 | #### 6.2 Create Endpoints 39 | Use Postman to generate endpoints. 40 | 41 | 6.2.1 Browse to the AWS IoT console at https://console.aws.amazon.com/iotv2/home?region=us-east-1#/thinghub and note the existing _Things_, if any. 42 | 43 | 6.2.2 In Postman, select the **POST** _/endpoints_ resource from the left menu and then click the **Send** button in the top right. 44 | 45 | 6.2.3 Return to the [AWS IoT Things console](https://console.aws.amazon.com/iotv2/home?region=us-east-1#/thinghub) and refresh the page. A new `black_switch` Thing should be available. 46 | 47 | 6.2.4 Click on the `black_switch` Thing instance to inspect its attributes. They should look like the following: 48 | 49 | ![black_switch Thing Inpection - Manage Environments](img/6.2.4-thing-inspection.png "black_switch Thing Inspection") 50 | 51 | > Note that the user_id is set to 0. This is a default value useful for development. However, Discovery for the Smart Home Skill would not find this device since it is expecting a user_id in the form of a profile from Login with Amazon. 52 | 53 | 6.2.5 Go to https://console.aws.amazon.com/dynamodb/home?region=us-east-1#tables:selected=SampleUsers and select the **Items** tab. 54 | 55 | 6.2.6 In the list of _SampleUsers_ select the first entry UserId that looks like the following format: 56 | ``` 57 | amzn1.account.XXXXXXXXXXXXXXXXXXXXXXXXXXXX 58 | ``` 59 | 6.2.7 Copy the UserId value and save it to the [user_id] section of the `config.txt` file. 60 | 61 | 6.2.8 Return to the [AWS IoT Things console](https://console.aws.amazon.com/iotv2/home?region=us-east-1#/thinghub) and select the `black_switch` Thing to view its attributes. 62 | 63 | 6.2.9 In the top right of the `black_switch` attributes click **Edit**. 64 | 65 | 6.2.10 On the _Edit black_switch_ attributes page, set the value of the _user_id_ **Attribute key** to the [user_id] stored in the `config.txt` file. This associates the `black_switch` thing with that user profile. 66 | 67 | > If you want to create other devices, repeat this section and update the POST body of the /endpoints call to look something like the following and click **Send** to POST it to the endpoint API: 68 | 69 | ``` 70 | { 71 | "event": { 72 | "endpoint": { 73 | "userId" : "INSERT_YOUR_USER_ID_FROM_CONFIG.TXT", 74 | "id": "white_switch", 75 | "state": "OFF", 76 | "type": "SWITCH" 77 | } 78 | } 79 | } 80 | ``` 81 | 82 |
83 | 84 | ____ 85 | Go to [Step 7: Test the Endpoints](007-setup-test-endpoints.md). 86 | -------------------------------------------------------------------------------- /sample_lambda/python/jsonschema/schemas/draft3.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-03/schema#", 3 | "dependencies": { 4 | "exclusiveMaximum": "maximum", 5 | "exclusiveMinimum": "minimum" 6 | }, 7 | "id": "http://json-schema.org/draft-03/schema#", 8 | "properties": { 9 | "$ref": { 10 | "format": "uri", 11 | "type": "string" 12 | }, 13 | "$schema": { 14 | "format": "uri", 15 | "type": "string" 16 | }, 17 | "additionalItems": { 18 | "default": {}, 19 | "type": [ 20 | { 21 | "$ref": "#" 22 | }, 23 | "boolean" 24 | ] 25 | }, 26 | "additionalProperties": { 27 | "default": {}, 28 | "type": [ 29 | { 30 | "$ref": "#" 31 | }, 32 | "boolean" 33 | ] 34 | }, 35 | "default": { 36 | "type": "any" 37 | }, 38 | "dependencies": { 39 | "additionalProperties": { 40 | "items": { 41 | "type": "string" 42 | }, 43 | "type": [ 44 | "string", 45 | "array", 46 | { 47 | "$ref": "#" 48 | } 49 | ] 50 | }, 51 | "default": {}, 52 | "type": [ 53 | "string", 54 | "array", 55 | "object" 56 | ] 57 | }, 58 | "description": { 59 | "type": "string" 60 | }, 61 | "disallow": { 62 | "items": { 63 | "type": [ 64 | "string", 65 | { 66 | "$ref": "#" 67 | } 68 | ] 69 | }, 70 | "type": [ 71 | "string", 72 | "array" 73 | ], 74 | "uniqueItems": true 75 | }, 76 | "divisibleBy": { 77 | "default": 1, 78 | "exclusiveMinimum": true, 79 | "minimum": 0, 80 | "type": "number" 81 | }, 82 | "enum": { 83 | "minItems": 1, 84 | "type": "array", 85 | "uniqueItems": true 86 | }, 87 | "exclusiveMaximum": { 88 | "default": false, 89 | "type": "boolean" 90 | }, 91 | "exclusiveMinimum": { 92 | "default": false, 93 | "type": "boolean" 94 | }, 95 | "extends": { 96 | "default": {}, 97 | "items": { 98 | "$ref": "#" 99 | }, 100 | "type": [ 101 | { 102 | "$ref": "#" 103 | }, 104 | "array" 105 | ] 106 | }, 107 | "format": { 108 | "type": "string" 109 | }, 110 | "id": { 111 | "format": "uri", 112 | "type": "string" 113 | }, 114 | "items": { 115 | "default": {}, 116 | "items": { 117 | "$ref": "#" 118 | }, 119 | "type": [ 120 | { 121 | "$ref": "#" 122 | }, 123 | "array" 124 | ] 125 | }, 126 | "maxDecimal": { 127 | "minimum": 0, 128 | "type": "number" 129 | }, 130 | "maxItems": { 131 | "minimum": 0, 132 | "type": "integer" 133 | }, 134 | "maxLength": { 135 | "type": "integer" 136 | }, 137 | "maximum": { 138 | "type": "number" 139 | }, 140 | "minItems": { 141 | "default": 0, 142 | "minimum": 0, 143 | "type": "integer" 144 | }, 145 | "minLength": { 146 | "default": 0, 147 | "minimum": 0, 148 | "type": "integer" 149 | }, 150 | "minimum": { 151 | "type": "number" 152 | }, 153 | "pattern": { 154 | "format": "regex", 155 | "type": "string" 156 | }, 157 | "patternProperties": { 158 | "additionalProperties": { 159 | "$ref": "#" 160 | }, 161 | "default": {}, 162 | "type": "object" 163 | }, 164 | "properties": { 165 | "additionalProperties": { 166 | "$ref": "#", 167 | "type": "object" 168 | }, 169 | "default": {}, 170 | "type": "object" 171 | }, 172 | "required": { 173 | "default": false, 174 | "type": "boolean" 175 | }, 176 | "title": { 177 | "type": "string" 178 | }, 179 | "type": { 180 | "default": "any", 181 | "items": { 182 | "type": [ 183 | "string", 184 | { 185 | "$ref": "#" 186 | } 187 | ] 188 | }, 189 | "type": [ 190 | "string", 191 | "array" 192 | ], 193 | "uniqueItems": true 194 | }, 195 | "uniqueItems": { 196 | "default": false, 197 | "type": "boolean" 198 | } 199 | }, 200 | "type": "object" 201 | } 202 | -------------------------------------------------------------------------------- /sample_backend/lambda/lambda_api/python/jsonschema/schemas/draft3.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-03/schema#", 3 | "dependencies": { 4 | "exclusiveMaximum": "maximum", 5 | "exclusiveMinimum": "minimum" 6 | }, 7 | "id": "http://json-schema.org/draft-03/schema#", 8 | "properties": { 9 | "$ref": { 10 | "format": "uri", 11 | "type": "string" 12 | }, 13 | "$schema": { 14 | "format": "uri", 15 | "type": "string" 16 | }, 17 | "additionalItems": { 18 | "default": {}, 19 | "type": [ 20 | { 21 | "$ref": "#" 22 | }, 23 | "boolean" 24 | ] 25 | }, 26 | "additionalProperties": { 27 | "default": {}, 28 | "type": [ 29 | { 30 | "$ref": "#" 31 | }, 32 | "boolean" 33 | ] 34 | }, 35 | "default": { 36 | "type": "any" 37 | }, 38 | "dependencies": { 39 | "additionalProperties": { 40 | "items": { 41 | "type": "string" 42 | }, 43 | "type": [ 44 | "string", 45 | "array", 46 | { 47 | "$ref": "#" 48 | } 49 | ] 50 | }, 51 | "default": {}, 52 | "type": [ 53 | "string", 54 | "array", 55 | "object" 56 | ] 57 | }, 58 | "description": { 59 | "type": "string" 60 | }, 61 | "disallow": { 62 | "items": { 63 | "type": [ 64 | "string", 65 | { 66 | "$ref": "#" 67 | } 68 | ] 69 | }, 70 | "type": [ 71 | "string", 72 | "array" 73 | ], 74 | "uniqueItems": true 75 | }, 76 | "divisibleBy": { 77 | "default": 1, 78 | "exclusiveMinimum": true, 79 | "minimum": 0, 80 | "type": "number" 81 | }, 82 | "enum": { 83 | "minItems": 1, 84 | "type": "array", 85 | "uniqueItems": true 86 | }, 87 | "exclusiveMaximum": { 88 | "default": false, 89 | "type": "boolean" 90 | }, 91 | "exclusiveMinimum": { 92 | "default": false, 93 | "type": "boolean" 94 | }, 95 | "extends": { 96 | "default": {}, 97 | "items": { 98 | "$ref": "#" 99 | }, 100 | "type": [ 101 | { 102 | "$ref": "#" 103 | }, 104 | "array" 105 | ] 106 | }, 107 | "format": { 108 | "type": "string" 109 | }, 110 | "id": { 111 | "format": "uri", 112 | "type": "string" 113 | }, 114 | "items": { 115 | "default": {}, 116 | "items": { 117 | "$ref": "#" 118 | }, 119 | "type": [ 120 | { 121 | "$ref": "#" 122 | }, 123 | "array" 124 | ] 125 | }, 126 | "maxDecimal": { 127 | "minimum": 0, 128 | "type": "number" 129 | }, 130 | "maxItems": { 131 | "minimum": 0, 132 | "type": "integer" 133 | }, 134 | "maxLength": { 135 | "type": "integer" 136 | }, 137 | "maximum": { 138 | "type": "number" 139 | }, 140 | "minItems": { 141 | "default": 0, 142 | "minimum": 0, 143 | "type": "integer" 144 | }, 145 | "minLength": { 146 | "default": 0, 147 | "minimum": 0, 148 | "type": "integer" 149 | }, 150 | "minimum": { 151 | "type": "number" 152 | }, 153 | "pattern": { 154 | "format": "regex", 155 | "type": "string" 156 | }, 157 | "patternProperties": { 158 | "additionalProperties": { 159 | "$ref": "#" 160 | }, 161 | "default": {}, 162 | "type": "object" 163 | }, 164 | "properties": { 165 | "additionalProperties": { 166 | "$ref": "#", 167 | "type": "object" 168 | }, 169 | "default": {}, 170 | "type": "object" 171 | }, 172 | "required": { 173 | "default": false, 174 | "type": "boolean" 175 | }, 176 | "title": { 177 | "type": "string" 178 | }, 179 | "type": { 180 | "default": "any", 181 | "items": { 182 | "type": [ 183 | "string", 184 | { 185 | "$ref": "#" 186 | } 187 | ] 188 | }, 189 | "type": [ 190 | "string", 191 | "array" 192 | ], 193 | "uniqueItems": true 194 | }, 195 | "uniqueItems": { 196 | "default": false, 197 | "type": "boolean" 198 | } 199 | }, 200 | "type": "object" 201 | } 202 | -------------------------------------------------------------------------------- /sample_lambda/python/jsonschema/_reflect.py: -------------------------------------------------------------------------------- 1 | # -*- test-case-name: twisted.test.test_reflect -*- 2 | # Copyright (c) Twisted Matrix Laboratories. 3 | # See LICENSE for details. 4 | 5 | """ 6 | Standardized versions of various cool and/or strange things that you can do 7 | with Python's reflection capabilities. 8 | """ 9 | 10 | import sys 11 | 12 | from jsonschema.compat import PY3 13 | 14 | 15 | class _NoModuleFound(Exception): 16 | """ 17 | No module was found because none exists. 18 | """ 19 | 20 | 21 | 22 | class InvalidName(ValueError): 23 | """ 24 | The given name is not a dot-separated list of Python objects. 25 | """ 26 | 27 | 28 | 29 | class ModuleNotFound(InvalidName): 30 | """ 31 | The module associated with the given name doesn't exist and it can't be 32 | imported. 33 | """ 34 | 35 | 36 | 37 | class ObjectNotFound(InvalidName): 38 | """ 39 | The object associated with the given name doesn't exist and it can't be 40 | imported. 41 | """ 42 | 43 | 44 | 45 | if PY3: 46 | def reraise(exception, traceback): 47 | raise exception.with_traceback(traceback) 48 | else: 49 | exec("""def reraise(exception, traceback): 50 | raise exception.__class__, exception, traceback""") 51 | 52 | reraise.__doc__ = """ 53 | Re-raise an exception, with an optional traceback, in a way that is compatible 54 | with both Python 2 and Python 3. 55 | 56 | Note that on Python 3, re-raised exceptions will be mutated, with their 57 | C{__traceback__} attribute being set. 58 | 59 | @param exception: The exception instance. 60 | @param traceback: The traceback to use, or C{None} indicating a new traceback. 61 | """ 62 | 63 | 64 | def _importAndCheckStack(importName): 65 | """ 66 | Import the given name as a module, then walk the stack to determine whether 67 | the failure was the module not existing, or some code in the module (for 68 | example a dependent import) failing. This can be helpful to determine 69 | whether any actual application code was run. For example, to distiguish 70 | administrative error (entering the wrong module name), from programmer 71 | error (writing buggy code in a module that fails to import). 72 | 73 | @param importName: The name of the module to import. 74 | @type importName: C{str} 75 | @raise Exception: if something bad happens. This can be any type of 76 | exception, since nobody knows what loading some arbitrary code might 77 | do. 78 | @raise _NoModuleFound: if no module was found. 79 | """ 80 | try: 81 | return __import__(importName) 82 | except ImportError: 83 | excType, excValue, excTraceback = sys.exc_info() 84 | while excTraceback: 85 | execName = excTraceback.tb_frame.f_globals["__name__"] 86 | # in Python 2 execName is None when an ImportError is encountered, 87 | # where in Python 3 execName is equal to the importName. 88 | if execName is None or execName == importName: 89 | reraise(excValue, excTraceback) 90 | excTraceback = excTraceback.tb_next 91 | raise _NoModuleFound() 92 | 93 | 94 | 95 | def namedAny(name): 96 | """ 97 | Retrieve a Python object by its fully qualified name from the global Python 98 | module namespace. The first part of the name, that describes a module, 99 | will be discovered and imported. Each subsequent part of the name is 100 | treated as the name of an attribute of the object specified by all of the 101 | name which came before it. For example, the fully-qualified name of this 102 | object is 'twisted.python.reflect.namedAny'. 103 | 104 | @type name: L{str} 105 | @param name: The name of the object to return. 106 | 107 | @raise InvalidName: If the name is an empty string, starts or ends with 108 | a '.', or is otherwise syntactically incorrect. 109 | 110 | @raise ModuleNotFound: If the name is syntactically correct but the 111 | module it specifies cannot be imported because it does not appear to 112 | exist. 113 | 114 | @raise ObjectNotFound: If the name is syntactically correct, includes at 115 | least one '.', but the module it specifies cannot be imported because 116 | it does not appear to exist. 117 | 118 | @raise AttributeError: If an attribute of an object along the way cannot be 119 | accessed, or a module along the way is not found. 120 | 121 | @return: the Python object identified by 'name'. 122 | """ 123 | if not name: 124 | raise InvalidName('Empty module name') 125 | 126 | names = name.split('.') 127 | 128 | # if the name starts or ends with a '.' or contains '..', the __import__ 129 | # will raise an 'Empty module name' error. This will provide a better error 130 | # message. 131 | if '' in names: 132 | raise InvalidName( 133 | "name must be a string giving a '.'-separated list of Python " 134 | "identifiers, not %r" % (name,)) 135 | 136 | topLevelPackage = None 137 | moduleNames = names[:] 138 | while not topLevelPackage: 139 | if moduleNames: 140 | trialname = '.'.join(moduleNames) 141 | try: 142 | topLevelPackage = _importAndCheckStack(trialname) 143 | except _NoModuleFound: 144 | moduleNames.pop() 145 | else: 146 | if len(names) == 1: 147 | raise ModuleNotFound("No module named %r" % (name,)) 148 | else: 149 | raise ObjectNotFound('%r does not name an object' % (name,)) 150 | 151 | obj = topLevelPackage 152 | for n in names[1:]: 153 | obj = getattr(obj, n) 154 | 155 | return obj 156 | -------------------------------------------------------------------------------- /sample_backend/lambda/lambda_api/python/jsonschema/_reflect.py: -------------------------------------------------------------------------------- 1 | # -*- test-case-name: twisted.test.test_reflect -*- 2 | # Copyright (c) Twisted Matrix Laboratories. 3 | # See LICENSE for details. 4 | 5 | """ 6 | Standardized versions of various cool and/or strange things that you can do 7 | with Python's reflection capabilities. 8 | """ 9 | 10 | import sys 11 | 12 | from jsonschema.compat import PY3 13 | 14 | 15 | class _NoModuleFound(Exception): 16 | """ 17 | No module was found because none exists. 18 | """ 19 | 20 | 21 | 22 | class InvalidName(ValueError): 23 | """ 24 | The given name is not a dot-separated list of Python objects. 25 | """ 26 | 27 | 28 | 29 | class ModuleNotFound(InvalidName): 30 | """ 31 | The module associated with the given name doesn't exist and it can't be 32 | imported. 33 | """ 34 | 35 | 36 | 37 | class ObjectNotFound(InvalidName): 38 | """ 39 | The object associated with the given name doesn't exist and it can't be 40 | imported. 41 | """ 42 | 43 | 44 | 45 | if PY3: 46 | def reraise(exception, traceback): 47 | raise exception.with_traceback(traceback) 48 | else: 49 | exec("""def reraise(exception, traceback): 50 | raise exception.__class__, exception, traceback""") 51 | 52 | reraise.__doc__ = """ 53 | Re-raise an exception, with an optional traceback, in a way that is compatible 54 | with both Python 2 and Python 3. 55 | 56 | Note that on Python 3, re-raised exceptions will be mutated, with their 57 | C{__traceback__} attribute being set. 58 | 59 | @param exception: The exception instance. 60 | @param traceback: The traceback to use, or C{None} indicating a new traceback. 61 | """ 62 | 63 | 64 | def _importAndCheckStack(importName): 65 | """ 66 | Import the given name as a module, then walk the stack to determine whether 67 | the failure was the module not existing, or some code in the module (for 68 | example a dependent import) failing. This can be helpful to determine 69 | whether any actual application code was run. For example, to distiguish 70 | administrative error (entering the wrong module name), from programmer 71 | error (writing buggy code in a module that fails to import). 72 | 73 | @param importName: The name of the module to import. 74 | @type importName: C{str} 75 | @raise Exception: if something bad happens. This can be any type of 76 | exception, since nobody knows what loading some arbitrary code might 77 | do. 78 | @raise _NoModuleFound: if no module was found. 79 | """ 80 | try: 81 | return __import__(importName) 82 | except ImportError: 83 | excType, excValue, excTraceback = sys.exc_info() 84 | while excTraceback: 85 | execName = excTraceback.tb_frame.f_globals["__name__"] 86 | # in Python 2 execName is None when an ImportError is encountered, 87 | # where in Python 3 execName is equal to the importName. 88 | if execName is None or execName == importName: 89 | reraise(excValue, excTraceback) 90 | excTraceback = excTraceback.tb_next 91 | raise _NoModuleFound() 92 | 93 | 94 | 95 | def namedAny(name): 96 | """ 97 | Retrieve a Python object by its fully qualified name from the global Python 98 | module namespace. The first part of the name, that describes a module, 99 | will be discovered and imported. Each subsequent part of the name is 100 | treated as the name of an attribute of the object specified by all of the 101 | name which came before it. For example, the fully-qualified name of this 102 | object is 'twisted.python.reflect.namedAny'. 103 | 104 | @type name: L{str} 105 | @param name: The name of the object to return. 106 | 107 | @raise InvalidName: If the name is an empty string, starts or ends with 108 | a '.', or is otherwise syntactically incorrect. 109 | 110 | @raise ModuleNotFound: If the name is syntactically correct but the 111 | module it specifies cannot be imported because it does not appear to 112 | exist. 113 | 114 | @raise ObjectNotFound: If the name is syntactically correct, includes at 115 | least one '.', but the module it specifies cannot be imported because 116 | it does not appear to exist. 117 | 118 | @raise AttributeError: If an attribute of an object along the way cannot be 119 | accessed, or a module along the way is not found. 120 | 121 | @return: the Python object identified by 'name'. 122 | """ 123 | if not name: 124 | raise InvalidName('Empty module name') 125 | 126 | names = name.split('.') 127 | 128 | # if the name starts or ends with a '.' or contains '..', the __import__ 129 | # will raise an 'Empty module name' error. This will provide a better error 130 | # message. 131 | if '' in names: 132 | raise InvalidName( 133 | "name must be a string giving a '.'-separated list of Python " 134 | "identifiers, not %r" % (name,)) 135 | 136 | topLevelPackage = None 137 | moduleNames = names[:] 138 | while not topLevelPackage: 139 | if moduleNames: 140 | trialname = '.'.join(moduleNames) 141 | try: 142 | topLevelPackage = _importAndCheckStack(trialname) 143 | except _NoModuleFound: 144 | moduleNames.pop() 145 | else: 146 | if len(names) == 1: 147 | raise ModuleNotFound("No module named %r" % (name,)) 148 | else: 149 | raise ObjectNotFound('%r does not name an object' % (name,)) 150 | 151 | obj = topLevelPackage 152 | for n in names[1:]: 153 | obj = getattr(obj, n) 154 | 155 | return obj 156 | -------------------------------------------------------------------------------- /sample_lambda/python/jsonschema/_utils.py: -------------------------------------------------------------------------------- 1 | import itertools 2 | import json 3 | import pkgutil 4 | import re 5 | 6 | from jsonschema.compat import str_types, MutableMapping, urlsplit 7 | 8 | 9 | class URIDict(MutableMapping): 10 | """ 11 | Dictionary which uses normalized URIs as keys. 12 | 13 | """ 14 | 15 | def normalize(self, uri): 16 | return urlsplit(uri).geturl() 17 | 18 | def __init__(self, *args, **kwargs): 19 | self.store = dict() 20 | self.store.update(*args, **kwargs) 21 | 22 | def __getitem__(self, uri): 23 | return self.store[self.normalize(uri)] 24 | 25 | def __setitem__(self, uri, value): 26 | self.store[self.normalize(uri)] = value 27 | 28 | def __delitem__(self, uri): 29 | del self.store[self.normalize(uri)] 30 | 31 | def __iter__(self): 32 | return iter(self.store) 33 | 34 | def __len__(self): 35 | return len(self.store) 36 | 37 | def __repr__(self): 38 | return repr(self.store) 39 | 40 | 41 | class Unset(object): 42 | """ 43 | An as-of-yet unset attribute or unprovided default parameter. 44 | 45 | """ 46 | 47 | def __repr__(self): 48 | return "" 49 | 50 | 51 | def load_schema(name): 52 | """ 53 | Load a schema from ./schemas/``name``.json and return it. 54 | 55 | """ 56 | 57 | data = pkgutil.get_data('jsonschema', "schemas/{0}.json".format(name)) 58 | return json.loads(data.decode("utf-8")) 59 | 60 | 61 | def indent(string, times=1): 62 | """ 63 | A dumb version of :func:`textwrap.indent` from Python 3.3. 64 | 65 | """ 66 | 67 | return "\n".join(" " * (4 * times) + line for line in string.splitlines()) 68 | 69 | 70 | def format_as_index(indices): 71 | """ 72 | Construct a single string containing indexing operations for the indices. 73 | 74 | For example, [1, 2, "foo"] -> [1][2]["foo"] 75 | 76 | Arguments: 77 | 78 | indices (sequence): 79 | 80 | The indices to format. 81 | 82 | """ 83 | 84 | if not indices: 85 | return "" 86 | return "[%s]" % "][".join(repr(index) for index in indices) 87 | 88 | 89 | def find_additional_properties(instance, schema): 90 | """ 91 | Return the set of additional properties for the given ``instance``. 92 | 93 | Weeds out properties that should have been validated by ``properties`` and 94 | / or ``patternProperties``. 95 | 96 | Assumes ``instance`` is dict-like already. 97 | 98 | """ 99 | 100 | properties = schema.get("properties", {}) 101 | patterns = "|".join(schema.get("patternProperties", {})) 102 | for property in instance: 103 | if property not in properties: 104 | if patterns and re.search(patterns, property): 105 | continue 106 | yield property 107 | 108 | 109 | def extras_msg(extras): 110 | """ 111 | Create an error message for extra items or properties. 112 | 113 | """ 114 | 115 | if len(extras) == 1: 116 | verb = "was" 117 | else: 118 | verb = "were" 119 | return ", ".join(repr(extra) for extra in extras), verb 120 | 121 | 122 | def types_msg(instance, types): 123 | """ 124 | Create an error message for a failure to match the given types. 125 | 126 | If the ``instance`` is an object and contains a ``name`` property, it will 127 | be considered to be a description of that object and used as its type. 128 | 129 | Otherwise the message is simply the reprs of the given ``types``. 130 | 131 | """ 132 | 133 | reprs = [] 134 | for type in types: 135 | try: 136 | reprs.append(repr(type["name"])) 137 | except Exception: 138 | reprs.append(repr(type)) 139 | return "%r is not of type %s" % (instance, ", ".join(reprs)) 140 | 141 | 142 | def flatten(suitable_for_isinstance): 143 | """ 144 | isinstance() can accept a bunch of really annoying different types: 145 | * a single type 146 | * a tuple of types 147 | * an arbitrary nested tree of tuples 148 | 149 | Return a flattened tuple of the given argument. 150 | 151 | """ 152 | 153 | types = set() 154 | 155 | if not isinstance(suitable_for_isinstance, tuple): 156 | suitable_for_isinstance = (suitable_for_isinstance,) 157 | for thing in suitable_for_isinstance: 158 | if isinstance(thing, tuple): 159 | types.update(flatten(thing)) 160 | else: 161 | types.add(thing) 162 | return tuple(types) 163 | 164 | 165 | def ensure_list(thing): 166 | """ 167 | Wrap ``thing`` in a list if it's a single str. 168 | 169 | Otherwise, return it unchanged. 170 | 171 | """ 172 | 173 | if isinstance(thing, str_types): 174 | return [thing] 175 | return thing 176 | 177 | 178 | def unbool(element, true=object(), false=object()): 179 | """ 180 | A hack to make True and 1 and False and 0 unique for ``uniq``. 181 | 182 | """ 183 | 184 | if element is True: 185 | return true 186 | elif element is False: 187 | return false 188 | return element 189 | 190 | 191 | def uniq(container): 192 | """ 193 | Check if all of a container's elements are unique. 194 | 195 | Successively tries first to rely that the elements are hashable, then 196 | falls back on them being sortable, and finally falls back on brute 197 | force. 198 | 199 | """ 200 | 201 | try: 202 | return len(set(unbool(i) for i in container)) == len(container) 203 | except TypeError: 204 | try: 205 | sort = sorted(unbool(i) for i in container) 206 | sliced = itertools.islice(sort, 1, None) 207 | for i, j in zip(sort, sliced): 208 | if i == j: 209 | return False 210 | except (NotImplementedError, TypeError): 211 | seen = [] 212 | for e in container: 213 | e = unbool(e) 214 | if e in seen: 215 | return False 216 | seen.append(e) 217 | return True 218 | -------------------------------------------------------------------------------- /sample_backend/lambda/lambda_api/python/jsonschema/_utils.py: -------------------------------------------------------------------------------- 1 | import itertools 2 | import json 3 | import pkgutil 4 | import re 5 | 6 | from jsonschema.compat import str_types, MutableMapping, urlsplit 7 | 8 | 9 | class URIDict(MutableMapping): 10 | """ 11 | Dictionary which uses normalized URIs as keys. 12 | 13 | """ 14 | 15 | def normalize(self, uri): 16 | return urlsplit(uri).geturl() 17 | 18 | def __init__(self, *args, **kwargs): 19 | self.store = dict() 20 | self.store.update(*args, **kwargs) 21 | 22 | def __getitem__(self, uri): 23 | return self.store[self.normalize(uri)] 24 | 25 | def __setitem__(self, uri, value): 26 | self.store[self.normalize(uri)] = value 27 | 28 | def __delitem__(self, uri): 29 | del self.store[self.normalize(uri)] 30 | 31 | def __iter__(self): 32 | return iter(self.store) 33 | 34 | def __len__(self): 35 | return len(self.store) 36 | 37 | def __repr__(self): 38 | return repr(self.store) 39 | 40 | 41 | class Unset(object): 42 | """ 43 | An as-of-yet unset attribute or unprovided default parameter. 44 | 45 | """ 46 | 47 | def __repr__(self): 48 | return "" 49 | 50 | 51 | def load_schema(name): 52 | """ 53 | Load a schema from ./schemas/``name``.json and return it. 54 | 55 | """ 56 | 57 | data = pkgutil.get_data('jsonschema', "schemas/{0}.json".format(name)) 58 | return json.loads(data.decode("utf-8")) 59 | 60 | 61 | def indent(string, times=1): 62 | """ 63 | A dumb version of :func:`textwrap.indent` from Python 3.3. 64 | 65 | """ 66 | 67 | return "\n".join(" " * (4 * times) + line for line in string.splitlines()) 68 | 69 | 70 | def format_as_index(indices): 71 | """ 72 | Construct a single string containing indexing operations for the indices. 73 | 74 | For example, [1, 2, "foo"] -> [1][2]["foo"] 75 | 76 | Arguments: 77 | 78 | indices (sequence): 79 | 80 | The indices to format. 81 | 82 | """ 83 | 84 | if not indices: 85 | return "" 86 | return "[%s]" % "][".join(repr(index) for index in indices) 87 | 88 | 89 | def find_additional_properties(instance, schema): 90 | """ 91 | Return the set of additional properties for the given ``instance``. 92 | 93 | Weeds out properties that should have been validated by ``properties`` and 94 | / or ``patternProperties``. 95 | 96 | Assumes ``instance`` is dict-like already. 97 | 98 | """ 99 | 100 | properties = schema.get("properties", {}) 101 | patterns = "|".join(schema.get("patternProperties", {})) 102 | for property in instance: 103 | if property not in properties: 104 | if patterns and re.search(patterns, property): 105 | continue 106 | yield property 107 | 108 | 109 | def extras_msg(extras): 110 | """ 111 | Create an error message for extra items or properties. 112 | 113 | """ 114 | 115 | if len(extras) == 1: 116 | verb = "was" 117 | else: 118 | verb = "were" 119 | return ", ".join(repr(extra) for extra in extras), verb 120 | 121 | 122 | def types_msg(instance, types): 123 | """ 124 | Create an error message for a failure to match the given types. 125 | 126 | If the ``instance`` is an object and contains a ``name`` property, it will 127 | be considered to be a description of that object and used as its type. 128 | 129 | Otherwise the message is simply the reprs of the given ``types``. 130 | 131 | """ 132 | 133 | reprs = [] 134 | for type in types: 135 | try: 136 | reprs.append(repr(type["name"])) 137 | except Exception: 138 | reprs.append(repr(type)) 139 | return "%r is not of type %s" % (instance, ", ".join(reprs)) 140 | 141 | 142 | def flatten(suitable_for_isinstance): 143 | """ 144 | isinstance() can accept a bunch of really annoying different types: 145 | * a single type 146 | * a tuple of types 147 | * an arbitrary nested tree of tuples 148 | 149 | Return a flattened tuple of the given argument. 150 | 151 | """ 152 | 153 | types = set() 154 | 155 | if not isinstance(suitable_for_isinstance, tuple): 156 | suitable_for_isinstance = (suitable_for_isinstance,) 157 | for thing in suitable_for_isinstance: 158 | if isinstance(thing, tuple): 159 | types.update(flatten(thing)) 160 | else: 161 | types.add(thing) 162 | return tuple(types) 163 | 164 | 165 | def ensure_list(thing): 166 | """ 167 | Wrap ``thing`` in a list if it's a single str. 168 | 169 | Otherwise, return it unchanged. 170 | 171 | """ 172 | 173 | if isinstance(thing, str_types): 174 | return [thing] 175 | return thing 176 | 177 | 178 | def unbool(element, true=object(), false=object()): 179 | """ 180 | A hack to make True and 1 and False and 0 unique for ``uniq``. 181 | 182 | """ 183 | 184 | if element is True: 185 | return true 186 | elif element is False: 187 | return false 188 | return element 189 | 190 | 191 | def uniq(container): 192 | """ 193 | Check if all of a container's elements are unique. 194 | 195 | Successively tries first to rely that the elements are hashable, then 196 | falls back on them being sortable, and finally falls back on brute 197 | force. 198 | 199 | """ 200 | 201 | try: 202 | return len(set(unbool(i) for i in container)) == len(container) 203 | except TypeError: 204 | try: 205 | sort = sorted(unbool(i) for i in container) 206 | sliced = itertools.islice(sort, 1, None) 207 | for i, j in zip(sort, sliced): 208 | if i == j: 209 | return False 210 | except (NotImplementedError, TypeError): 211 | seen = [] 212 | for e in container: 213 | e = unbool(e) 214 | if e in seen: 215 | return False 216 | seen.append(e) 217 | return True 218 | --------------------------------------------------------------------------------