Introduction: Amazon Echo Controlled Curtains
This instructable explains how to integrate an electric curtain rail, a Raspberry Pi and an Amazon Echo, so that you can ask Alexa to open the curtains.
Once completed, you should be able to say: 'Alexa, ask the curtains to open' and 'Alexa, ask the curtains to close'.
Step 1: Design Overview
This design consists of several components:
- Integrating the electric curtain rail with a Raspberry Pi
- Writing a Python script to open and close the curtain
- Writing an Amazon skill to allow your Echo to communicate with the Raspberry Pi
I used the following components:
- An Amazon Echo
- An electric curtain rail https://www.amazon.co.uk/gp/product/B013X0BZF2
- A replacement controller cable to connect to the Raspberry Pi GPIO http://www.ebay.co.uk/itm/190738599768?_trksid=p2...
- A Raspberry Pi
- Components necessary to integrate with the Raspberry Pi GPIO (I ordered from Proto-Pic)
- Raspberry Pi - GPIO Shrouded Header (2x13)
- GPIO Ribbon Cable for Raspberry Pi
- 3 x BJT Transistors - NPN BC547
- 3 x 1K resistors
- A suitable sided veroboard
- Some wire
Step 2: Integrating the Electric Curtain Rail With the Raspberry Pi
The curtain rail I purchased came with two controllers - a wired one and wireless one. I didn't touch the wireless one, choosing to integrate the Raspberry Pi with the wired one.
Once disassembled, you can see that the wired one consists of a four switches (see diagram) - by tracing the wires you can see that the buttons connect three of the wires to ground (the fourth wire). A short to the wire labelled 'open' causes the motor to start one way; a short to the wire labelled 'close' causes the motor to drive other way. A short to the wire labelled 'close' (caused by pressing either stop button) causes the the motor to stop.
Cut one end of the replacement wire off and strip the wires.
Compare the backs of the original controller plug with the replacement plug to work out which wire is which (in the diagram, the original controller wire is white; the replacement grey). Four wires are used; two will are not required. Confirm you have the correct wires by shorting the open, close and stop wires against the ground wire. The curtain rail should open, close and stop.
The potential between the wires and ground is 5V - I assume this is a logic input pulled high by a resistor, which switches to low when grounded.
Integrating with the Raspberry Pi GPIO is relatively simple. Any basic transistor can be used as a switch to temporarily ground each wire, thereby simulating the switch press.
An example circuit is shown. Note the wire ensuring the Raspberry Pi and the curtain share a common ground (Raspberry PI physical pin 6).
I used standard BJT Transistors - NPN BC547 and 1K resistors to connect the base of the transistors to the Raspberry Pi.
I connected to physical pins 11, 15 and 22, which map to BCM GPIO pins 17, 22 and 25. More about the relationship between physical and logical pins is available at: https://projects.drogon.net/raspberry-pi/wiringpi/...
Step 3: Writing a Python Script to Open and Close the Curtain
The Raspberry Pi has a very flexible General Purpose Input/Output port - GPIO for short. There are many great Instructables and articles on using the Raspberry Pi GPIO.
There is also a wonderful library, available in Python and C, that makes driving the GPIO very easy - Wiring Pi. The original webpage is available at http://wiringpi.com/
There is a useful set of instructions for installing available at http://raspi.tv/how-to-install-wiringpi2-for-pytho...
Once installed, the following code should make the curtains open and close with a two second gap between each change. The code simulates a press of the button by taking the GPIO pin high for 0.5s, raising the base voltage on the transistor in question allowing current to flow between collector and emitter - shorting the wire to ground.
import RPi.GPIO as GPIO import time GPIO.setmode(GPIO.BCM) openPin=17 closePin=25 stopPin=22 GPIO.setup(openPin, GPIO.OUT) GPIO.setup(closePin, GPIO.OUT) GPIO.setup(stopPin, GPIO.OUT) time.sleep(2) while (1==1) : print "Opening\n" GPIO.output(openPin, GPIO.HIGH) time.sleep(0.5) GPIO.output(openPin, GPIO.LOW) time.sleep(2) print "Stopping\n" GPIO.output(openPin, GPIO.HIGH) time.sleep(0.5) GPIO.output(openPin, GPIO.LOW) time.sleep(2) print "Stopping\n" GPIO.output(stopPin, GPIO.HIGH) time.sleep(0.5) GPIO.output(stopPin, GPIO.LOW) time.sleep(2) print "Closing\n" GPIO.output(closePin, GPIO.HIGH) time.sleep(0.5) GPIO.output(closePin, GPIO.LOW) time.sleep(2) print "Stopping\n" GPIO.output(stopPin, GPIO.HIGH) time.sleep(0.5) GPIO.output(stopPin, GPIO.LOW) time.sleep(2)
Step 4: Writing an Amazon Skill to Communicate With the Raspberry Pi
Asking Alexa
Ideally, we'd ask Alexa to open the curtains. But I'm not sure that type of request is possible with the voice interface available on the Echo right now. So, instead we'll say: 'Alexa, ask the curtains to open'. Then 'Alexa, ask the curtains to close'.
There are three steps to this:
- Creating a local script on the raspberry Pi and implementing port forwarding on your router
- Creating an Alexa skill on the Amazon Developer Website
- Creating an Amazon AWS Lambda proxy
There are some great Instructables written already on this topic - I learnt all I needed to know from the following resource - kodos to Patrick!
https://www.instructables.com/id/Control-Raspberry...
The referenced instructable explains how to create a skill on the Amazon developer website, point the Amazon generated output at ngrok (an ssl to http proxy), then process the request on a Raspberry Pi using a library called Flask-Ask. ngrok is required, as the developer website will only send requests over ssl - the Flask-ask listens on http.
ngrok is great as a temporary proxy, but unless you want to pay an annual fee, the URL you need to point the Amazon script at will unfortunately change every time you restart the ngrok client on your pi.
An alternative is to create a basic proxy on Amazon AWS, which then can redirect the request to your Pi (assuming you have port forwarding configured on your firewall to allow the request through).
I used a proxy created by Matt - https://forums.developer.amazon.com/questions/8155...
Thanks Matt!
Step 5: Creating a Local Script on the Raspberry Pi and Implementing Port Forwarding on Your Router
Create the following script your Raspberry Pi:
from flask import Flask
from flask_ask import Ask, statement, convert_errors import RPi.GPIO as GPIO import logging import time
GPIO.setmode(GPIO.BCM)
openPin=17
closePin=25
stopPin=22
GPIO.setup(openPin, GPIO.OUT)
GPIO.setup(closePin, GPIO.OUT)
GPIO.setup(stopPin, GPIO.OUT)
app = Flask(__name__) ask = Ask(app, '/')
app.config['ASK_VERIFY_REQUESTS'] = False
app.config['ASK_APPLICATION_ID'] = 'Unique amazon skill reference''
logging.getLogger("flask_ask").setLevel(logging.DEBUG)
def openCurtains():
print 'Opening curtains'
GPIO.output(openPin, GPIO.HIGH)
time.sleep(0.5)
GPIO.output(openPin, GPIO.LOW)
time.sleep(5)
GPIO.output(stopPin, GPIO.HIGH)
time.sleep(0.5)
GPIO.output(stopPin, GPIO.LOW)
def closeCurtains():
print 'Closing curtains'
GPIO.output(closePin, GPIO.HIGH)
time.sleep(0.5)
GPIO.output(closePin, GPIO.LOW)
time.sleep(5)
GPIO.output(stopPin, GPIO.HIGH)
time.sleep(0.5)
GPIO.output(stopPin, GPIO.LOW)
@ask.intent('GPIOControlIntent', mapping={'status': 'status'})
def gpio_control(status):
if status in ['open', 'yield']: openCurtains() return statement('Good morning Hart Household')
if status in ['close', 'shield']: closeCurtains() return statement('Good evening Hart Household')
if __name__ == '__main__': app.run(host='IP address of Pi', port=5000, debug=True)
Start the process running on Pi. Set up port forwarding on your router, so that requests sent to port 5000 of your public address are forwarded to port 5000 of your Pi. Note that you may need to subscribe to a service such as DYNDNS if your IP address is not static.
Step 6: Creating an Alexa Skill on the Amazon Developer Website
Let's follow Patrick's great Instructable - starting at step 4:
https://www.instructables.com/id/Control-Raspberry...
Patrick's Step 4 - create an Amazon Developer account
Patrick's Step 5 - create a new skill - I used the invocation name 'The curtains', so that Alexa would respond to 'Alexa, ask the curtains...'
Note the application ID - you'll need to paste that into the Curtains script you wrote, plus into the Lambda proxy you are going to write
Patrick's Step 6 - some subtle changes here, as shown in the screenshot:
The intents are simpler (no selection of a GPIO pin needed for the curtains):
{
"intents": [{
"intent": "GPIOControlIntent",
"slots": [{
"name": "status",
"type": "GPIO_CONTROL"
}]
}]
}
And the slot values different - open and close
And the invocation simpler:
GPIOControlIntent to {status}
Patrick's step 7 - this is where we are going to use a Lambda proxy rather than Ngrok - see the next stage
Step 7: Creating an Amazon AWS Lambda Proxy
The best guidance for setting up an AWS account and creating a lambda function is available from Amazon - https://developer.amazon.com/public/solutions/alex...
Follow these instructions until you are ready to enter node.js code into your function.
Then use the code from Matt's proxy https://forums.developer.amazon.com/questions/8155...
(I'm missing a trick somewhere with formatting code - apologies):
var http = require('http');
var URLParser = require('url');
console.log('Loading proxy function');
exports.handler = function (json, context)
{
try {
// A list of URL's to call for each applicationId
var handlers = { 'appId':'url', 'your amazon app id':'http://your router address:5000/' };
// Look up the url to call based on the appId
var url = handlers[json.session.application.applicationId];
if (!url) { context.fail("No url found for application id"); }
console.log('Trying url')
console.log(url)
var parts = URLParser.parse(url);
var post_data = JSON.stringify(json);
// An object of options to indicate where to post to
var post_options = { host: parts.hostname, port: (parts.port || 80), path: parts.path, method: 'POST', headers: { 'Content-Type': 'application/json', 'Content-Length': post_data.length } };
// Initiate the request to the HTTP endpoint
console.log(post_options)
var req = http.request(post_options,function(res) {
var body = "";
// Data may be chunked
res.on('data', function(chunk) { body += chunk; });
res.on('end', function() { // When data is done, finish the request
context.succeed(JSON.parse(body)); }); });
req.on('error', function(e) { context.fail('problem with request: ' + e.message); });
// Send the JSON data console.log('Sending data') req.write(post_data); req.end(); } catch (e) { context.fail("Exception: " + e); } };
I had also to increase the timeout in the Advanced configuration to 7 seconds - otherwise the Lambda proxy will timeout prior to the curtains finishing their 5 second activity.
Step 8: Joining It All Together
Copy the Lambda ID - you'll need to paste this into the Amazon development, instructing Amazon to direct requests to your new Lambda proxy, that should forward them onto the Raspberry Pi - see the 1st screenshot.
Test the the skill from the Amazon Developer website - you should see a response as per the 2nd screenshot.
If it doesn't work, check the Lambda logs for invocation errors and the Raspberry Pi script output for errors.
Then enable your development skill on your Echo - don't publish it to the world or you'll lose control of your curtains.
Sit back and enjoy automated curtains!