Keith Caskey
Published © GPL3+

Dooreo - The Alexa Powered Automatic Door Opener

This device will allow you to open your door by asking Alexa to open it for you.

AdvancedFull instructions provided12 hours4,192
Dooreo - The Alexa Powered Automatic Door Opener

Things used in this project

Hardware components

XinaBox Stepper Motor
×1
Bigger Stepper Motor
×1
Big Easy Driver
SparkFun Big Easy Driver
×1
SparkFun easy driver
×1
Arduino Pro Mini 328 - 5V/16MHz
SparkFun Arduino Pro Mini 328 - 5V/16MHz
×1
Raspberry Pi Zero Wireless
Raspberry Pi Zero Wireless
×1
power supply 24V
×1

Software apps and online services

AWS Lambda
Amazon Web Services AWS Lambda
AWS IoT
Amazon Web Services AWS IoT
Alexa Skills Kit
Amazon Alexa Alexa Skills Kit

Hand tools and fabrication machines

3D Printer (generic)
3D Printer (generic)
Soldering iron (generic)
Soldering iron (generic)

Story

Read more

Custom parts and enclosures

Doorknob Back Plate STL

Plate for back of doorknob to attach doorknob gear to. STL for printing

Spool Part

Spool to mount on larger motor for winch assembly

Spool STL

Spool to mount on larger motor for Winch Assembly

Motor Mount

Mounting bracket for Winch Assembly

Motor Mount STL

mounting bracket for Winch Assembly, STL for printing

Motor Gear Part

Gear to mount on smaller stepper motor

Motor Gear STL

Gear to mount on smaller motor, STL for printing

Doorknob Gear Part

Gear to mount on doorknob

Doorknob Gear STL

Gear to mount on doorknob, STL for printing

Doorknob Back Plate

Plate for back of doorknob to attach doorknob gear to.

Schematics

Main Wiring Diagram

Code

dooreo.ino

Arduino
Code to run on arduino for motor control for Dooreo
//Declare pin functions on Arduino
#define stp 2
#define dir 3
#define MS1 4
#define MS2 5
#define EN  6

#define stpb 12
#define dirb 7
#define ENb 11
#define MS1b 8
#define MS2b 9
#define MS3b 10

//Declare variables for functions
char user_input;
int x;
int y;
int state;
int onOffState=0;

void setup() {
  pinMode(stp, OUTPUT);
  pinMode(dir, OUTPUT);
  pinMode(MS1, OUTPUT);
  pinMode(MS2, OUTPUT);
  pinMode(EN, OUTPUT);

  pinMode(stpb, OUTPUT);
  pinMode(dirb, OUTPUT);
  pinMode(MS1b, OUTPUT);
  pinMode(MS2b, OUTPUT);
  pinMode(MS3b, OUTPUT);
  pinMode(ENb, OUTPUT);
  
  resetEDPins(); //Set step, direction, microstep and enable pins to default states
  Serial.begin(9600); //Open Serial connection for debugging
  Serial.println("Begin motor control");
  Serial.println();
  //Print function list for user selection
  Serial.println("Enter number for control option:");
  Serial.println("5. open the door.");
  Serial.println("6. close the door.");
  Serial.println();
}

//Main loop
void loop() {
  while(Serial.available()){
      user_input = Serial.read(); //Read user input and trigger appropriate function
      if(user_input =='5')
      {
        if(onOffState==0){
          openDoor();
        }
        onOffState=1;
      }
      else if(user_input=='6')
      {
        if(onOffState==1){
          closeDoor();
        }
        onOffState=0;
      }
      else
      {
        Serial.println("Invalid option entered.");
      }
  }
}

//Reset Easy Driver pins to default states
void resetEDPins()
{
  digitalWrite(stp, LOW);
  digitalWrite(dir, LOW);
  digitalWrite(MS1, LOW);
  digitalWrite(MS2,HIGH);
  digitalWrite(EN, HIGH);

  digitalWrite(stpb, LOW);
  digitalWrite(dirb, LOW);
  digitalWrite(MS1b, HIGH);
  digitalWrite(MS2b,HIGH);
  digitalWrite(MS3b,HIGH);
  digitalWrite(ENb, HIGH);
}

void turnKnob(int dirValue, int steps, int motorSpeed)
{
  digitalWrite(dir,dirValue);
  for(x=1;x<steps;x++)
  {
    digitalWrite(stp,HIGH);
    delayMicroseconds(motorSpeed);
    digitalWrite(stp,LOW);
    delayMicroseconds(motorSpeed);
  }
}

void turnWinch(int dirValue, int steps, int motorSpeed)
{
  digitalWrite(dirb,dirValue);
  for(x=1;x<steps;x++)
  {
    digitalWrite(stpb,HIGH);
    delayMicroseconds(motorSpeed);
    digitalWrite(stpb,LOW);
    delayMicroseconds(motorSpeed);
  }
}

void openDoor()
{
  digitalWrite(EN, LOW); //Pull enable pin low to allow motor control
  digitalWrite(ENb, LOW); //Pull enable pin low to allow motor control
  Serial.println("opening door");
  turnKnob(0,250,1000);
  //wind in winch
  turnWinch(0,20000,90);
  turnWinch(0,20000,90);
  turnWinch(0,20000,90);
  turnWinch(0,20000,90);
    
}

void closeDoor()
{
  Serial.println("closing door");
  digitalWrite(EN,HIGH);
  //winch out door
  turnWinch(1,20000,60);
  turnWinch(1,20000,60);
  turnWinch(1,20000,60);
  turnWinch(1,20000,60);
  digitalWrite(ENb,HIGH);
}

Lambda.py

Python
Lambda Code to interact with Dooreo thing shadow and Dooreo Alexa Skill
import logging
import time
import urllib2
import json
import boto3
import uuid
import datetime

#define client for interact with IoT Thing
client = boto3.client('iot-data')

# Setup logger
logger = logging.getLogger()
logger.setLevel(logging.INFO)

#entry point for lambda function, receives commands from alexa as json
def lambda_handler(event, context):
    logger.info("Received v3 directive!")
    logger.info(json.dumps(event))
    response = "null"
    
    #check to see that event is an intent from alexa
    if event["request"]["type"] == "IntentRequest":
        logger.info("Control")
        #call handler function for intent
        response = on_intent(event["request"], event["session"])
        
    logger.info("response" + json.dumps(response)) 
    return response

#handler for intent received from alexa        
def on_intent(intent_request, session):
    #extract the intent part of the json
    intent = intent_request["intent"]
    #get the name of the intent from the json
    intent_name = intent_request["intent"]["name"]
    
    #get the current state of the IoT thing to use its pin
    shadowResponse=client.get_thing_shadow(thingName='Dooreo2')
    shadowStream=shadowResponse["payload"]
    shadowJson=json.loads(shadowStream.read())
    
    #check if the IoT thing already has a pin or not
    try:
        currentPin = shadowJson["state"]["desired"]["pin"]
    except KeyError:
        currentPin = "nopin"
    
    #handler for setPin intent
    if intent_name == "setPin":
        if currentPin=="nopin":
            #call function to update IoT thing as desired
            updateShadowPin(intent)
            responseText = "updated pin"
        
        #don't set the pin if there already is one
        else:
            responseText = "pin already set, clear pin using the clear pin command or contact support to reset pin"
    
    #handler for clearPin intent
    elif intent_name == "clearPin":
        if currentPin=="nopin":
            responseText = "no pin to clear"
        
        #if there is a pin in the IoT thing, check if the user provided the correct pin, if so delete it from the IoT thing
        else:
            pinValue=intent["slots"]["pin"]["value"]
            if pinValue==currentPin:
                updateShadowPinClear()
                responseText = "pin cleared"
            else:
                responseText = "old pin does not match, to reset contact support"
    
    #handler for open and close commands
    elif intent_name == "changeDoorState":
        #if this is the first command from alexa we check if a pin is provided
        if intent_request["dialogState"] == "STARTED":
            try: 
                pinValue=intent["slots"]["pin"]["value"]
            #there was no pin, ask for it using the Dialog.ElicitSlot mechanism    
            except KeyError:
                response = {
                    "version": "1.0",
                    "sessionAttributes": {},
                    "response": {
                        "outputSpeech": {
                          "type": "PlainText",
                          "text": "What's the pin?"
                        },
                        "shouldEndSession": False,
                        "directives": [
                         {
                            "type": "Dialog.ElicitSlot",
                            "slotToElicit":"pin"
                        }
                        ]
                    }
                }
                return response
        
        #there was a pin, or it's not the first command and we assume there's a pin
        pinValue=intent["slots"]["pin"]["value"]
        if currentPin == "nopin":
            responseText = "no pin set, please set pin by saying set pin and a four digit number"
        #check if user provided pin matches that in IoT thing and if so, change IoT thing state appropriately
        else:
            if currentPin == pinValue:
                updateShadow(intent)
                value = intent["slots"]["doorState"]["value"]
                responseText = "door " + value
            else:
                responseText = "pin does not match"
    else:
        responseText= "not a known command"
    #build an appropriate response for Alexa using the text from the above cases
    response = {
        "version": "1.0",
        "sessionAttributes": {},
        "response": {
            "outputSpeech": {
                "type": "PlainText",
                "text": responseText
            },
            "shouldEndSession": True
        }
        }
    return response
   
#function to update the IoT thing shadow for door open and close     
def updateShadow(intent):
    #get the desired state from the Alexa json command
    state=intent["slots"]["doorState"]["value"]
    
    #update the thing shadow using boto3 library with a json object containing the new state
    response = client.update_thing_shadow(
        thingName = 'Dooreo2', 
        payload = json.dumps({
            'state': {
                'desired': {
                    'door': state
                }
            }
            }
            )
        )

#function to update the IoT thing shadow to set a new pin        
def updateShadowPin(intent):
    #get the desired state from the Alexa json command
    state=intent["slots"]["pin"]["value"]
    #update the thing shadow using boto3 library with a json object containing the new state
    response = client.update_thing_shadow(
        thingName = 'Dooreo2', 
        payload = json.dumps({
            'state': {
                'desired': {
                    'pin': state
                }
            }
            }
            )
        )

#function to update the IoT thing shadow to clear the old pin        
def updateShadowPinClear():
    response = client.delete_thing_shadow(thingName='Dooreo2')
    response = client.update_thing_shadow(
        thingName = 'Dooreo2', 
        payload = json.dumps({
            'state': {
                'desired': {
                    'door': 'closed'
                }
            }
            }
            )
        )

dooreo.py

Python
This will run on the Raspberry Pi in order to listen for changes to the IoT Thing and respond accordingly.
'''
/*
 * Copyright 2010-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License").
 * You may not use this file except in compliance with the License.
 * A copy of the License is located at
 *
 *  http://aws.amazon.com/apache2.0
 *
 * or in the "license" file accompanying this file. This file 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.
 */
 '''

from AWSIoTPythonSDK.MQTTLib import AWSIoTMQTTShadowClient
import logging
import time
import json
import argparse
import serial

ser=serial.Serial("/dev/ttyUSB0",9600)
ser.flushInput()


# Shadow JSON schema:
#
# Name: Bot
# {
#	"state": {
#		"desired":{
#			"property":<INT VALUE>
#		}
#	}
# }

# Custom Shadow callback
def customShadowCallback_Delta(payload, responseStatus, token):
	# payload is a JSON string ready to be parsed using json.loads(...)
	# in both Py2.x and Py3.x
	print(responseStatus)
	payloadDict = json.loads(payload)
	print("++++++++DELTA++++++++++")
	#print("property: " + str(payloadDict["state"]["property"]))
	#print("version: " + str(payloadDict["version"]))
	print(payload)
	print(str(payloadDict["state"]["light"]))
	print("+++++++++++++++++++++++\n\n")
	onOff = payloadDict["state"]["light"]
	
	if onOff == "on":
		#if(status=='open'):
		ser.write(b'5')
	elif onOff == "off":
		#elif(status=='closed'):
		ser.write(b'6')
	
rootCAPath = "root-CA.crt"
certificatePath = "certificate.pem.crt"
privateKeyPath = "private.pem.key"
thingName = "Dooreo"
host = "a370wwfaias9e1.iot.us-east-1.amazonaws.com"
clientId = "DooreoPi"

# Configure logging
logger = logging.getLogger("AWSIoTPythonSDK.core")
logger.setLevel(logging.DEBUG)
streamHandler = logging.StreamHandler()
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
streamHandler.setFormatter(formatter)
logger.addHandler(streamHandler)

# Init AWSIoTMQTTShadowClient
myAWSIoTMQTTShadowClient = AWSIoTMQTTShadowClient(clientId)
myAWSIoTMQTTShadowClient.configureEndpoint(host, 8883)
myAWSIoTMQTTShadowClient.configureCredentials(rootCAPath, privateKeyPath, certificatePath)

# Connect to AWS IoT
myAWSIoTMQTTShadowClient.connect()

# Create a deviceShadow with persistent subscription
deviceShadowHandler = myAWSIoTMQTTShadowClient.createShadowHandlerWithName(thingName, True)

# Listen on deltas
deviceShadowHandler.shadowRegisterDeltaCallback(customShadowCallback_Delta)

# Loop forever
while True:
	time.sleep(1)

Alexa Skill

JSON
Code that can be used to build Alexa skill
{
  "languageModel": {
    "types": [
      {
        "name": "state",
        "values": [
          {
            "id": null,
            "name": {
              "value": "open",
              "synonyms": [
                "opened"
              ]
            }
          },
          {
            "id": null,
            "name": {
              "value": "closed",
              "synonyms": [
                "close"
              ]
            }
          }
        ]
      }
    ],
    "intents": [
      {
        "name": "AMAZON.CancelIntent",
        "samples": []
      },
      {
        "name": "AMAZON.HelpIntent",
        "samples": []
      },
      {
        "name": "AMAZON.StopIntent",
        "samples": []
      },
      {
        "name": "changeDoorState",
        "samples": [
          "door {doorState} with pin {pin}",
          "{doorState} door with pin {pin}",
          "{doorState} door",
          "door {doorState}"
        ],
        "slots": [
          {
            "name": "doorState",
            "type": "state",
            "samples": [
              "{doorState}"
            ]
          },
          {
            "name": "pin",
            "type": "AMAZON.FOUR_DIGIT_NUMBER",
            "samples": [
              "{pin}"
            ]
          }
        ]
      },
      {
        "name": "clearPin",
        "samples": [
          "clear pin {pin}"
        ],
        "slots": [
          {
            "name": "pin",
            "type": "AMAZON.FOUR_DIGIT_NUMBER",
            "samples": [
              "{pin}"
            ]
          }
        ]
      },
      {
        "name": "setPin",
        "samples": [
          "set pin to {pin}",
          "set pin {pin}"
        ],
        "slots": [
          {
            "name": "pin",
            "type": "AMAZON.FOUR_DIGIT_NUMBER",
            "samples": [
              "{pin}"
            ]
          }
        ]
      }
    ],
    "invocationName": "dooreo"
  },
  "prompts": [
    {
      "id": "Elicit.Intent-changeDoorState.IntentSlot-doorState",
      "variations": [
        {
          "type": "PlainText",
          "value": "do you want the door open or closed?"
        }
      ]
    },
    {
      "id": "Elicit.Intent-changeDoorState.IntentSlot-pin",
      "variations": [
        {
          "type": "PlainText",
          "value": "please provide pin, if you have not set one, say set pin and then a four digit number"
        }
      ]
    },
    {
      "id": "Elicit.Intent-clearPin.IntentSlot-pin",
      "variations": [
        {
          "type": "PlainText",
          "value": "what is the old pin?"
        }
      ]
    },
    {
      "id": "Elicit.Intent-setPin.IntentSlot-pin",
      "variations": [
        {
          "type": "PlainText",
          "value": "what did you want the pin to be?"
        }
      ]
    }
  ],
  "dialog": {
    "intents": [
      {
        "name": "changeDoorState",
        "confirmationRequired": false,
        "prompts": {},
        "slots": [
          {
            "name": "doorState",
            "type": "state",
            "elicitationRequired": true,
            "confirmationRequired": false,
            "prompts": {
              "elicitation": "Elicit.Intent-changeDoorState.IntentSlot-doorState"
            }
          },
          {
            "name": "pin",
            "type": "AMAZON.FOUR_DIGIT_NUMBER",
            "elicitationRequired": true,
            "confirmationRequired": false,
            "prompts": {
              "elicitation": "Elicit.Intent-changeDoorState.IntentSlot-pin"
            }
          }
        ]
      },
      {
        "name": "clearPin",
        "confirmationRequired": false,
        "prompts": {},
        "slots": [
          {
            "name": "pin",
            "type": "AMAZON.FOUR_DIGIT_NUMBER",
            "elicitationRequired": true,
            "confirmationRequired": false,
            "prompts": {
              "elicitation": "Elicit.Intent-clearPin.IntentSlot-pin"
            }
          }
        ]
      },
      {
        "name": "setPin",
        "confirmationRequired": false,
        "prompts": {},
        "slots": [
          {
            "name": "pin",
            "type": "AMAZON.FOUR_DIGIT_NUMBER",
            "elicitationRequired": true,
            "confirmationRequired": false,
            "prompts": {
              "elicitation": "Elicit.Intent-setPin.IntentSlot-pin"
            }
          }
        ]
      }
    ]
  }
}

dooreo.py

Python
Python code to run on the Raspberry Pi to listen for updates to the IoT Thing Shadow
'''
/*
 * Copyright 2010-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License").
 * You may not use this file except in compliance with the License.
 * A copy of the License is located at
 *
 *  http://aws.amazon.com/apache2.0
 *
 * or in the "license" file accompanying this file. This file 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.
 */
 '''

from AWSIoTPythonSDK.MQTTLib import AWSIoTMQTTShadowClient
import logging
import time
import json
import argparse
import serial

ser=serial.Serial("/dev/ttyUSB0",9600)
ser.flushInput()


# Shadow JSON schema:
#
# Name: Bot
# {
#	"state": {
#		"desired":{
#			"property":<INT VALUE>
#		}
#	}
# }

# Custom Shadow callback
def customShadowCallback_Delta(payload, responseStatus, token):
	# payload is a JSON string ready to be parsed using json.loads(...)
	# in both Py2.x and Py3.x
	print(responseStatus)
	payloadDict = json.loads(payload)
	print("++++++++DELTA++++++++++")
	#print("property: " + str(payloadDict["state"]["property"]))
	#print("version: " + str(payloadDict["version"]))
	print(payload)
	print(str(payloadDict["state"]["door"]))
	print("+++++++++++++++++++++++\n\n")
	onOff = payloadDict["state"]["door"]
	
	if onOff == "open":
		#if(status=='open'):
		print( "opening door")
		ser.write(b'5')
	elif onOff == "close":
		#elif(status=='closed'):
		print("closing door")
		ser.write(b'6')
	
rootCAPath = "root-CA.crt"
certificatePath = "certificate.pem.crt"
privateKeyPath = "private.pem.key"
thingName = "Dooreo2"
host = "a370wwfaias9e1.iot.us-east-1.amazonaws.com"
clientId = "DooreoPi"

# Configure logging
logger = logging.getLogger("AWSIoTPythonSDK.core")
logger.setLevel(logging.DEBUG)
streamHandler = logging.StreamHandler()
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
streamHandler.setFormatter(formatter)
logger.addHandler(streamHandler)

# Init AWSIoTMQTTShadowClient
myAWSIoTMQTTShadowClient = AWSIoTMQTTShadowClient(clientId)
myAWSIoTMQTTShadowClient.configureEndpoint(host, 8883)
myAWSIoTMQTTShadowClient.configureCredentials(rootCAPath, privateKeyPath, certificatePath)

# Connect to AWS IoT
myAWSIoTMQTTShadowClient.connect()

# Create a deviceShadow with persistent subscription
deviceShadowHandler = myAWSIoTMQTTShadowClient.createShadowHandlerWithName(thingName, True)

# Listen on deltas
deviceShadowHandler.shadowRegisterDeltaCallback(customShadowCallback_Delta)

# Loop forever
while True:
	time.sleep(1)

Credits

Keith Caskey

Keith Caskey

4 projects • 6 followers

Comments