TawfikDaim / home_automation

home automation user stories
0 stars 0 forks source link

8. Alexa integration #38

Open TawfikDaim opened 4 months ago

TawfikDaim commented 4 months ago

Retry 11 Feb 2024 tawfik.daim@outlook.com 192.168.1.13 with dyararco.duckdns.org

  1. Alexa Skills -> Create skill (e.g. Alexa13). SmartHome -> provision your own
  2. AWS; Create role, AWS Service -> Lambda -> add 'AWSLambdaBasicExecutionRole' name it: AWSLambdaBasicExecutionRole-SmartHome
  3. Create Lambda function (N. Virginia), Functions, Create Function, author from scratch - > function name (python 3.9 as run time) -> Expand default execution role and selected an existing role (AWSLambdaBasicExecutionRole-SmartHome)
  4. Add trigger -> click Alexa Smart Home (get skill ID from Developer console) amzn1.ask.skill.f821d9fc-7788-4097-ae6b-98004f737c04
  5. Clear the example code, and copy the Python script from: https://gist.github.com/matt2005/744b5ef548cc13d88d0569eea65f5e5b -> then click Deploy """ Copyright 2019 Jason Hu

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import os import json import logging import urllib3

_debug = bool(os.environ.get('DEBUG'))

_logger = logging.getLogger('HomeAssistant-SmartHome') _logger.setLevel(logging.DEBUG if _debug else logging.INFO)

def lambda_handler(event, context): """Handle incoming Alexa directive."""

_logger.debug('Event: %s', event)

base_url = os.environ.get('BASE_URL')
assert base_url is not None, 'Please set BASE_URL environment variable'
base_url = base_url.strip("/")

directive = event.get('directive')
assert directive is not None, 'Malformatted request - missing directive'
assert directive.get('header', {}).get('payloadVersion') == '3', \
    'Only support payloadVersion == 3'

scope = directive.get('endpoint', {}).get('scope')
if scope is None:
    # token is in grantee for Linking directive 
    scope = directive.get('payload', {}).get('grantee')
if scope is None:
    # token is in payload for Discovery directive 
    scope = directive.get('payload', {}).get('scope')
assert scope is not None, 'Malformatted request - missing endpoint.scope'
assert scope.get('type') == 'BearerToken', 'Only support BearerToken'

token = scope.get('token')
if token is None and _debug:
    token = os.environ.get('LONG_LIVED_ACCESS_TOKEN')  # only for debug purpose

verify_ssl = not bool(os.environ.get('NOT_VERIFY_SSL'))

http = urllib3.PoolManager(
    cert_reqs='CERT_REQUIRED' if verify_ssl else 'CERT_NONE',
    timeout=urllib3.Timeout(connect=2.0, read=10.0)
)

response = http.request(
    'POST', 
    '{}/api/alexa/smart_home'.format(base_url),
    headers={
        'Authorization': 'Bearer {}'.format(token),
        'Content-Type': 'application/json',
    },
    body=json.dumps(event).encode('utf-8'),
)
if response.status >= 400:
    return {
        'event': {
            'payload': {
                'type': 'INVALID_AUTHORIZATION_CREDENTIAL' 
                        if response.status in (401, 403) else 'INTERNAL_ERROR',
                'message': response.data.decode("utf-8"),
            }
        }
    }
_logger.debug('Response: %s', response.data.decode("utf-8"))
return json.loads(response.data.decode('utf-8'))
  1. Navigate to Configuration-> Environment variables: (required) Key = BASEURL, Value = https://dyararco.duckdns.org (Do not include the trailing /). (optional) Key = NOT_VERIFY_SSL, Value = True. You can set this to True to ignore SSL issues, for example if you don’t have a valid SSL certificate or you are using a self-signed certificate. (optional) Key = DEBUG, Value = True. Set this variable to log the debug message and to allow the LONG_LIVED_ACCESS_TOKEN (optional, not recommend) Key = LONG_LIVED_ACCESSTOKEN,
  2. Test code: { "directive": { "header": { "namespace": "Alexa.Discovery", "name": "Discover", "payloadVersion": "3", "messageId": "1bd5d003-31b9-476f-ad03-71d471922820" }, "payload": { "scope": { "type": "BearerToken", "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiI0MGQ0OGVjNTQ3M2I0ZmVkYWQ2M2M0ZDU4ZTdiODU0NiIsImlhdCI6MTcwNzY1Njg4OSwiZXhwIjoyMDIzMDE2ODg5fQ.zn_rcVhlDBCuXDNZE1Fth9Q7eCF0BLyVcBhmn6oNtIc" } } } }
  3. in Alexa Developer Console developer console, Your Skill ID: amzn1.ask.skill.ffc7776e-da07-4285-9ebf-d17ebb58d3bd Default endpoint: arn:aws:lambda:us-east-1:815421264176:function:Lambda_HA_13 North America: arn:aws:lambda:us-east-1:815421264176:function:Lambda_HA_13 Your Web Authorization URI: https://dyararco.duckdns.org/auth/authorize Access Token URI : https://dyararco.duckdns.org/auth/token Your Client ID: https://pitangui.amazon.com/ Your secret: anything Scope: smart_home

First run Test on Lambda function, result should be succesful and listing all devices from home assistant then do account linking on Develop console then account linking on alexa app

Image

Image

HOme assistant config file: alexa: smart_home: locale: en-US endpoint: https://api.amazonalexa.com/v3/events client_id: https://pitangui.amazon.com/ client_secret: anything filter: include_entities:

TawfikDaim commented 4 months ago

Old tries

Instructions here...

https://www.youtube.com/watch?v=Ww2LI59IQ0A

  1. sign-up AWS account (tawfikabdeldaim@gmail.com)
  2. sign-up Alex developer account developer.amazon.com
  3. AWS management console, search for Iam, go to roles, create a new role (Lambda service -> next -> permissions -> tick 'AWSLambdaExecutionrule' -> create a role and name it "home_assistant_pilot_1_Lambda"
  4. go to Alexa Developer console, create a new skill name it "HomeAssistant_Pilot_1", click on smart home skill, provision your own - leave this page open for now!!
  5. in AWS set the region to one of the supported regions "North Virginia", search for lambda in the services
  6. at top right create a new function, name it "HomeAssistant_Pilot_1", runtime select python 3.8, in execution role change the default execution role to the one we created (use an existing role, select it from there 'home_assistant_pilot_1_Lambda') -> create function
  7. Add trigger, select Alexa then Alexa Smart Home -> then add the skill ID from developer console, click add
  8. select the HomeAssistant_Pilot_1 lambda function, remove the code and get code it from GitHub: https://gist.github.com/matt2005/744b5ef548cc13d88d0569eea65f5e5b
  9. paste in above code and then deploy, message should say code was successfully added
  10. click on 'configure test event' (call it Discovery), code is here https://www.home-assistant.io/integrations/alexa.smart_home/#test-the-lambda-function
  11. add this to config file

    smart home integration

    alexa: smart_home:

  12. at AWS edit environment variable, add a new environment variable, name the key BASE_URL and value is the url to access home assistant from outside the home "https://automately.duckdns.org:8123" must have https
  13. generate long access token at home assistant, call it Alexa, copy the random string it generates and add it to AWS console under 'configure test event' add this token as "payload": { "scope": { "type": "BearerToken", "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ" ### I have removed default config: line to test
  14. it should be successful and listing the home assistant devices
  15. copy the ARN value from top
  16. Go back the Alexa Developer console from step 4 above, add the ARN to the Default End Point and click save.
  17. click on Account Linking, go to the "Your Web Authorization URI and fill with https://automately.duckdns.org:443/auth/authorize (not 8123 as it will fail in linking later)
  18. click on Account Linking, go to the "Access Token URI" and fill with https://automately.duckdns.org:443/auth/token
  19. Your Client ID get the US, usually second option listed next to Alexa Redirect URLs "only up to 3rd forward slash", currently its https://pitangui.amazon.com/
  20. secret is not used so type anything
  21. under scope, add scope and write smart_home

Code from github...https://gist.github.com/matt2005/744b5ef548cc13d88d0569eea65f5e5b """ Copyright 2019 Jason Hu Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import os import json import logging import urllib3

_debug = bool(os.environ.get('DEBUG'))

_logger = logging.getLogger('HomeAssistant-SmartHome') _logger.setLevel(logging.DEBUG if _debug else logging.INFO)

def lambda_handler(event, context): """Handle incoming Alexa directive."""

_logger.debug('Event: %s', event)

base_url = os.environ.get('BASE_URL')
assert base_url is not None, 'Please set BASE_URL environment variable'
base_url = base_url.strip("/")

directive = event.get('directive')
assert directive is not None, 'Malformatted request - missing directive'
assert directive.get('header', {}).get('payloadVersion') == '3', \
    'Only support payloadVersion == 3'

scope = directive.get('endpoint', {}).get('scope')
if scope is None:
    # token is in grantee for Linking directive 
    scope = directive.get('payload', {}).get('grantee')
if scope is None:
    # token is in payload for Discovery directive 
    scope = directive.get('payload', {}).get('scope')
assert scope is not None, 'Malformatted request - missing endpoint.scope'
assert scope.get('type') == 'BearerToken', 'Only support BearerToken'

token = scope.get('token')
if token is None and _debug:
    token = os.environ.get('LONG_LIVED_ACCESS_TOKEN')  # only for debug purpose

verify_ssl = not bool(os.environ.get('NOT_VERIFY_SSL'))

http = urllib3.PoolManager(
    cert_reqs='CERT_REQUIRED' if verify_ssl else 'CERT_NONE',
    timeout=urllib3.Timeout(connect=2.0, read=10.0)
)

response = http.request(
    'POST', 
    '{}/api/alexa/smart_home'.format(base_url),
    headers={
        'Authorization': 'Bearer {}'.format(token),
        'Content-Type': 'application/json',
    },
    body=json.dumps(event).encode('utf-8'),
)
if response.status >= 400:
    return {
        'event': {
            'payload': {
                'type': 'INVALID_AUTHORIZATION_CREDENTIAL' 
                        if response.status in (401, 403) else 'INTERNAL_ERROR',
                'message': response.data.decode("utf-8"),
            }
        }
    }
_logger.debug('Response: %s', response.data.decode("utf-8"))
return json.loads(response.data.decode('utf-8'))

Test code: https://www.home-assistant.io/integrations/alexa.smart_home/#test-the-lambda-function { "directive": { "header": { "namespace": "Alexa.Discovery", "name": "Discover", "payloadVersion": "3", "messageId": "1bd5d003-31b9-476f-ad03-71d471922820" }, "payload": { "scope": { "type": "BearerToken" } } } }