Amazon Dash Doorbell Update

I had previously posted about how to use an Amazon Dash button to turn it into a doorbell. Well, recently, Amazon has added a new camera device type to their smarhome skills api. This is great for us because it has the ability to receive doorbell press events.

What I set out to do is to leverage this with my Amazon Dash button hack so that Alexa can recognize a button press as a doorbell and then ding all the Echos in the house. There are a couple of moving pieces that need to be set into place for this to happen:

  1. Setup an Oauth app with Amazon.
  2. Create the Smarthome Skill
  3. Setup a DB with dynamo (this part is optional, but it makes things easier)
  4. Setup the a lambda function for discovery and authentication.
  5. Modify the event handler from our previous post so that it sends the event to Alexa.

Setting up the Oauth app with Amazon

Amazon has a great tutorial on their github page that I will reference (RE: copy+paste) here. There is also another (better in my opinion) tutorial that you can reference.

  1. Connect to https://developer.amazon.com/login.html and authenticate with your Amazon credentials.
  2. Click on “Apps & Services”, then “Login with Amazon”
  3. Click on “Create a New Security Profile”
  4. Fill in all three required fields to create your security profile and click “Save”.
  5. Before you complete this step, be sure to click on the link named “Show Client ID and Client Secret” and save these values to a secure location so they’re easily available later. You’ll need these values later in a future step.

So at this point we have our credentials that we will use so the user (us) can authenticate against Amazon. It is worth mentioning that because we’re not going to deploy this skill, this is just done to satisfy the requirement that Amazon has around smarthome skills needing Oauth.

Create a Smart Home Skill

Again, referencing Amazon’s tutorial:

  1. Go to https://developer.amazon.com/login.html and sign in
  2. Go to Alexa > Alexa Skills Kit (Get Started) > Add a New Skill
  3. In the Skill Information tab:
    • Skill Type = Smart Home Skill API
    • Name = Hacked Doorbell (or any other name)
    • Payload Version = v3
    • Click Save

Note the skill ID near the top, underneath your skill name.

Setup DynamoDB

We will setup a dynamodb table to store tokens. These tokens is what allows our application to talk to Amazon and make sure it’s us. It will hold two different tokens. One will be for authenticating with Alexa using “Login with Amazon” and the other will be for refreshing the token when it expires.

  1. Go to https://console.aws.amazon.com/console/home and sign in
  2. Go to Services > Compute > DynamoDB
  3. Click on Create Table
    1. Use doorbelltokens as Table name
    2. Use user as Primary key
    3. Click on Create

Setup the Lambda Function

Download this code as a zip file from my github repository.

  1. Go to https://console.aws.amazon.com/console/home and sign in
  2. Go to Services > Compute > Lambda
  3. Click on Create Function
  4. Step 1: Click on Author from scratch
  5. Step 2: Configure your Lambda function
    • Name = dashbuttondoorbell
    • Role = Create a Custom Role which will launch a new tab. Click Allow to create a new role named lambda_basic_execution and automatically insert this role into the Lambda basic information dialog.
    • Click Create Function
  6. Step 3: Click Triggers -> Add Trigger and select Alexa Smart Home
    • Application Id = skill ID of your test skill that you noted above
    • Enable trigger = checked
    • Click Submit
  7. Step 4: Click Configuration
    • Runtime = Python 3.6
    • Code entry type = Upload a .ZIP file
    • Click on Upload and find the zip file you downloaded from my github
    • Handler = lambda.lambda_handler
    • Click Next
    • Click Save
    • On the top right corner, note the Lambda ARN
  8. Edit the lambda.py file (you can do it in-line with the built in editor) and change lines 98 and 99 to reflect your client id and secret you got from the “Setting up the Oauth app with Amazon” step.

Modify the Event Handler

Our event receiver needs to have a way to send door press events to Alexa. This is surprisingly simple. In my previous post I showed you how to send telegram events or send emails when the button was pressed. What we’re going to do is add another action: send events to Alexa.

For this we need two functions:

  1. Get the token from DynamoDB
  2. Send the event to Alexa

Getting the token from DynamoDB

For this we will need the client id and secret from the skill we created earlier. Open the skill and go to the permissions tab. Enable “send alexa events” and you should see the id and secret under “Alexa Skill Messaging”.

We will use the secret and client below. Look at lines 19 and 20.

def getToken():
    dynamodb = boto3.resource('dynamodb')
    table = dynamodb.Table('doorbelltokens')
    response = table.get_item(
        Key={
            'user': 'roberto'
        }
    )

    item = response['Item']
    if item['access_token']['received'] + item['access_token']['expires_in'] <= decimal.Decimal(time.time()):
        ### get new token and store it
        print("trying to get a new token")
        url = 'https://api.amazon.com/auth/o2/token'
        headers = {'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8'}
        params = {
            'grant_type': 'refresh_token',
            'refresh_token' : item['access_token']['refresh_token'],
            'client_id' : 'PUT CLIENT ID HERE',
            'client_secret': 'PUT CLIENT SECRET HERE'
        }
        r = requests.post(url=url, headers=headers, data=params)
        access_token = r.json()
        access_token['received'] = decimal.Decimal(time.time())
        item['access_token'] = access_token
        table.put_item(
        Item=item
        )
    return item['access_token']['access_token']

Send the Event to Alexa

This section is what actually sends out the event. As you can see, we reference the token on the second line of the function.

def updateAlexa():
	### send update to alexa
	token = getToken()
	payload = {
		  "event": {
		    "header": {
		      "messageId": "abc-123-def-456",
		      "namespace": "Alexa.DoorbellEventSource",
		      "name": "DoorbellPress",
		      "payloadVersion": "3"
		    },
		    "endpoint": {
		      "scope": {
		        "type":"BearerToken",
		        "token": token
		       },
		       "endpointId" :  "dashbuttondoorbell" ,
		    },
		    "payload": {
	        	"cause": {
		          "type": "PHYSICAL_INTERACTION"
		        },
		        "timestamp": datetime.datetime.now().isoformat()[0:-4] + "Z"
		    }
		  }
		}

	url = 'https://api.amazonalexa.com/v3/events'
	headers = {'Authorization': 'Bearer '+ token}
	r = requests.post(url=url, headers=headers, json=payload)
	print(r.status_code)
	print(r.text)


### Send messages to the event gateway
updateAlexa()

So in essence, what happens when the button is pressed is that our service runs the above which:

  1. Gets the latest valid token
  2. Sends the event with the token to Alexa.

Modify the Skill

  1. Go back to https://developer.amazon.com/home.html and sign in as needed
  2. Go to Alexa > Alexa Skills Kit > the test skill you created earlier
  3. In the Configuration tab:
    • Lambda ARN default = enter your Lambda ARN noted from the previous step
    • Authorization URI = https://www.amazon.com/ap/oa
    • Client ID = your client ID from LWA noted in a previous step
    • Scope: profile (click Add Scope first to add)
    • Access Token URI: https://api.amazon.com/auth/o2/token
    • Client Secret: your client secret from LWA noted in a previous step
    • Client Authentication Scheme: HTTP Basic (Recommended)
    • Click Save
  4. Provide Redirect URL’s to LWA:
    • The Configuration page for your Skill lists several Redirect URL’s. Open the LWA security profile you created earlier and visit the Web Settings dialog. Provide each of the Redirect URL values from your Skill in the Allowed Return URL’s field.

Add the skill to your Alexa

  1. Go to https://alexa.amazon.com in your browser
  2. Login with same Amazon developer account
  3. Make sure you have an Alexa device associated with the account.
  4. Go to Skills > Your Skills (on the top right corner) and search for your test skill, it should be there with a “devUS” tag
  5. Click on the skill and then click Enable
  6. Log in with the same Amazon credentials when presented with a LWA login page
  7. Allow LWA access, and you should see a message that says you can close this window. Close that window and you should be presented with a popup asking to discovery devices. Click Discover Devices.
  8. Go to Smart Home > Devices and you should see your doorbell

Conclusion

At the end of this exercise you have turned your $1 dashbutton into a doorbell. You also learned how to create smart skills, use Lambda, a bit of Oauth, and DynamoDB. I’d like to give a special shout out to whomever created the original tutorial for smart skills since a copied and modified a lot of the content from there (why re-invent the wheel?).

15 thoughts on “Amazon Dash Doorbell Update

  1. Hi, This is a great use of an Amazon Dash Button but having some issues with the location of some of this code.

    Where exactly should the ‘def getToken():’ and ‘def updateAlexa():’ code exist?

    Does it reside in doorbell.py on the Raspberry Pi?

    Really appreciate any guidance on this.

      1. Thanks Rob,

        Sadly, I am encountering issues once the code has run from a button press event.

        Getting this output:

        File “/home/pi/pythonscripts/doorbell/doorbell.py”, line 44, in getToken
        dynamodb = boto3.resource(‘dynamodb’)
        NameError: name ‘boto3’ is not defined

        I am also unable to link the skill to my account which in itself may be contributing to this possibly.

          1. Well, I’ve got as far as being able to enable the skill and do account linking. I’ve discovered the dash button as a device and it is showing as type: Doorbell. I’ve linked it to a speaker group and I’ve also tried running a routine but for the life of me, when I press the button I get nothing from the Echo devices.

            The only error I can see is in DynamoDB. There is an error under the User access_token.

            { “error” : { “S” : “invalid_grant” }, “error_description” : { “S” : “The request has an invalid grant parameter : code” }, “received” : { “N” : “1551619261.4844481945037841796875” } }

            I’ve checked and rechecked all the Client IDs are correct in all scripts.

            The only thing I can see from ‘googling’ this is that I may have to somewhere refer to the redirect_uri in the code.

          2. Can you paste the whole doorbell.py file here?

            Also, did you put in the ID and Secret on the lambda function?

            And finally, what does your entry in Dynamo look like?

            You can mask the secrets and ids

  2. Hi Rob,

    Here is doorbell.py

    ######################

    #!/usr/bin/env python3
    import requests, smtplib
    import boto3

    # ——— User Settings ———
    gmail_user = ‘****@gmail.com’
    gmail_password = ‘****’
    gmail_recipients = [‘****@gmail.com’]
    #CHAT_ID = “”
    #BOT_TOKEN = “”
    #TELEGRAM_URL = “https://api.telegram.org/bot”+BOT_TOKEN+”/”
    # ———————————

    #method = “sendMessage”
    #text = “Ding Dong! Someone is at the door!”
    #payload = {“chat_id” : CHAT_ID, “text”: text}
    #r = requests.post(url=TELEGRAM_URL+method,json=payload)

    sent_from = gmail_user
    to = gmail_recipients
    subject = ‘Ding Dong! There is someone at the door!’
    body = ‘Sent from ****’

    email_text = “””\
    From: %s
    To: %s
    Subject: %s

    %s
    “”” % (sent_from, “, “.join(to), subject, body)

    try:
    server = smtplib.SMTP_SSL(‘smtp.gmail.com’, 465)
    server.ehlo()
    server.login(gmail_user, gmail_password)
    server.sendmail(sent_from, to, email_text)
    server.close()

    print(‘Email sent!’)
    except:
    print(‘Something went wrong…’)

    def getToken():
    dynamodb = boto3.resource(‘dynamodb’)
    table = dynamodb.Table(‘doorbelltokens’)
    response = table.get_item(
    Key={
    ‘user’: ‘pi’
    }
    )

    item = response[‘Item’]
    if item[‘access_token’][‘received’] + item[‘access_token’][‘expires_in’] <= decimal.Decimal(time.time()):
    ### get new token and store it
    print("trying to get a new token")
    url = 'https://api.amazon.com/auth/o2/token&#039;
    headers = {'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8'}
    params = {
    'grant_type': 'refresh_token',
    'refresh_token' : item['access_token']['refresh_token'],
    'client_id' : '****',
    'client_secret': '****'
    }
    r = requests.post(url=url, headers=headers, data=params)
    access_token = r.json()
    access_token['received'] = decimal.Decimal(time.time())
    item['access_token'] = access_token
    table.put_item(
    Item=item
    )
    return item['access_token']['access_token']

    def updateAlexa():
    ### send update to alexa
    token = getToken()
    payload = {
    "event": {
    "header": {
    "messageId": "abc-123-def-456",
    "namespace": "Alexa.DoorbellEventSource",
    "name": "DoorbellPress",
    "payloadVersion": "3"
    },
    "endpoint": {
    "scope": {
    "type":"BearerToken",
    "token": token
    },
    "endpointId" : "dashbuttondoorbell" ,
    },
    "payload": {
    "cause": {
    "type": "PHYSICAL_INTERACTION"
    },
    "timestamp": datetime.datetime.now().isoformat()[0:-4] + "Z"
    }
    }
    }

    url = 'https://api.amazonalexa.com/v3/events&#039;
    headers = {'Authorization': 'Bearer '+ token}
    r = requests.post(url=url, headers=headers, json=payload)
    print(r.status_code)
    print(r.text)

    ### Send messages to the event gateway
    updateAlexa()

    ######################

    I've added the Oauth Client ID and Secret to lambda.py.

    The DynamoDB entry is as follows:

    {
    "access_token": {
    "M": {
    "error": {
    "S": "invalid_grant"
    },
    "error_description": {
    "S": "The request has an invalid grant parameter : code"
    },
    "received": {
    "N": "1551619261.4844481945037841796875"
    }
    }
    },
    "grant": {
    "M": {
    "code": {
    "S": "RHVJqxqhbwOppcVNRnVH"
    },
    "type": {
    "S": "OAuth2.AuthorizationCode"
    }
    }
    },
    "grantee": {
    "M": {
    "token": {
    "S": "Atza|IwEBIOze9ov-uOwv55YT_NpLN442TrygEayu3XJYPGREVCkPqtj0ABis5tMK7BhSiFTFS_qix4stEnnFMyrcjvieKm_W3EVSlMpZAyL992DuLkAeTU_7Wga7aKkRx7N0r_UJN6uvYcM7WpOUG3UCGDD6VPdQ2xehI0VaHtFWtsh4qZQkgqQDZVwyZx6AiIK_iRkIC2p9Q2WZdMeeFiYy2fG0lXNOWSfecEFQ4ywgmDy_AZNU3uwBFOl0KzbJQLBsLsBd3X749mRyk2myl0ZFAjQ_XuE6jEAODDmakD2VRx4EMKxbc70K2h-TtQxOjYaNGPtvJSR1TK0goyPhi4UAsh5hPx–evCY07eI1hDUzKiL0Dj9E_lL6GwQl2ZbbdAlOaU2iObMCwZVsu7N664hxcO7dr6xl01KJK5kd2PfOYKumDeNSukQs3sHZxoHD13VPeXi7062QzbapklUs9YJlMV8lb9iqQrJvhTW8AAfdOq4y2zcLf2vhzRXvCYEqSRPKFxzvsa4FZaOypg59i9bPXSrN34m"
    },
    "type": {
    "S": "BearerToken"
    }
    }
    },
    "user": {
    "S": "pi"
    }
    }

    I really appreciate you taking the time to look at this Rob.

    Andy

    1. Andy, have you tried simplifying doorbell.py by removing the email piece/ Just leave the updateAlexa function and the updateAlexa() line,

      Then, execute it manually by running “python3 doorbell.py”. What error do you get on the console?

      The redirect uri should be part of when you setup OAuth. The first tutorial goes over it.

      1. Hi Rob, good thinking.

        If I just retain the updateAlexa funtion, I get a getToken error as it is called in that function.

        Traceback (most recent call last):
        File “doorbell.py”, line 110, in
        updateAlexa()
        File “doorbell.py”, line 77, in updateAlexa
        token = getToken()
        NameError: name ‘getToken’ is not defined

        If I run it with the getToken function enabled too, I get a region error relating to boto (as per below), will investigate this further today as I didn’t do anything with boto except install it on the Pi.

        Traceback (most recent call last):
        File “doorbell.py”, line 110, in
        updateAlexa()
        File “doorbell.py”, line 77, in updateAlexa
        token = getToken()
        File “doorbell.py”, line 45, in getToken
        dynamodb = boto3.resource(‘dynamodb’)
        File “/home/pi/.local/lib/python3.5/site-packages/boto3/__init__.py”, line 100, in resource
        return _get_default_session().resource(*args, **kwargs)
        File “/home/pi/.local/lib/python3.5/site-packages/boto3/session.py”, line 389, in resource
        aws_session_token=aws_session_token, config=config)
        File “/home/pi/.local/lib/python3.5/site-packages/boto3/session.py”, line 263, in client
        aws_session_token=aws_session_token, config=config)
        File “/home/pi/.local/lib/python3.5/site-packages/botocore/session.py”, line 838, in create_client
        client_config=config, api_version=api_version)
        File “/home/pi/.local/lib/python3.5/site-packages/botocore/client.py”, line 86, in create_client
        verify, credentials, scoped_config, client_config, endpoint_bridge)
        File “/home/pi/.local/lib/python3.5/site-packages/botocore/client.py”, line 328, in _get_client_args
        verify, credentials, scoped_config, client_config, endpoint_bridge)
        File “/home/pi/.local/lib/python3.5/site-packages/botocore/args.py”, line 47, in get_client_args
        endpoint_url, is_secure, scoped_config)
        File “/home/pi/.local/lib/python3.5/site-packages/botocore/args.py”, line 117, in compute_client_args
        service_name, region_name, endpoint_url, is_secure)
        File “/home/pi/.local/lib/python3.5/site-packages/botocore/client.py”, line 402, in resolve
        service_name, region_name)
        File “/home/pi/.local/lib/python3.5/site-packages/botocore/regions.py”, line 122, in construct_endpoint
        partition, service_name, region_name)
        File “/home/pi/.local/lib/python3.5/site-packages/botocore/regions.py”, line 135, in _endpoint_for_partition
        raise NoRegionError()
        botocore.exceptions.NoRegionError: You must specify a region.

          1. Hi Rob,

            Update from what I have been working through. I did fix the region error but was then thrown errors relating to midding credentials.

            In the end I installed AWSCLI on the Pi.

            sudo pip3 install –upgrade awscli

            I has also previously given the Lambda :user/dashbuttondoorbell admin level access in the IAM console. I then used the IAM console to create an Access key ID and Access Secret Key ID for that user.

            Then, on the Pi I ran

            $ aws configure

            This requests four pieces if information as per this article: https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-configure.html

            AWS Access Key ID [None]: ###
            AWS Secret Access Key [None]: ###
            Default region name [None]: eu-west-1
            Default output format [None]: json

            I am still not out of trouble yet though. I am now getting KeyError: ‘expires_in

            Traceback (most recent call last):
            File “/home/pi/pythonscripts/doorbell/doorbell.py”, line 71, in
            updateAlexa()
            File “/home/pi/pythonscripts/doorbell/doorbell.py”, line 38, in updateAlexa
            token = getToken()
            File “/home/pi/pythonscripts/doorbell/doorbell.py”, line 15, in getToken
            if item[‘access_token’][‘received’] + item[‘access_token’][‘expires_in’] <= decimal.Decimal(time.time()):
            KeyError: 'expires_in'

          2. I think you’re going to have to debug the code every step of the way by doing print(). I’m not sure what you’re running into specifically, but it is clear you’re not getting the refresh token.

  3. Hi Rob,

    I am an absolute Python dummy, will need to take some time to learn the syntax for debugging in that code.

    Will post back updates when I’ve worked out what to do.

    Cheers

    Andy

Leave a Reply

Your email address will not be published. Required fields are marked *