Created
February 29, 2024 20:09
-
-
Save aclud/e70ef2acc48521c8a0e723f54232cb8b to your computer and use it in GitHub Desktop.
Alexa garage control skill + raspberry pi script
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Intent schema: | |
{ | |
"intents": [ | |
{ | |
"intent": "GetGarageState" | |
}, | |
{ | |
"intent": "GetTemp" | |
}, | |
{ | |
"intent": "GarageAction", | |
"slots": [ | |
{ | |
"name": "Action", | |
"type": "LITERAL" | |
} | |
] | |
} | |
] | |
} | |
Sample utterances: | |
GetGarageState state | |
GetGarageState door state | |
GetGarageState status | |
GetGarageState door status | |
GarageAction {open|Action} | |
GarageAction to {open|Action} | |
GarageAction {close|Action} | |
GarageAction to {close|Action} | |
GarageAction {shut|Action} | |
GarageAction to {shut|Action} | |
GetTemp for the temp | |
GetTemp for the temperature | |
GetTemp the temp | |
GetTemp the temperature | |
GetTemp temp | |
GetTemp temperature |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/python | |
from bottle import route, run, template, error, request, static_file, post, get | |
import time, datetime, logging | |
from logging.handlers import RotatingFileHandler | |
import threading | |
import sys | |
import RPi.GPIO as io | |
import Adafruit_DHT as ada | |
try: | |
io.setmode(io.BCM) | |
io.setwarnings(False) | |
#key | |
myKey = 'blahh' | |
# Define io to use on Pi | |
io_temp = 24 | |
#io_PIR = 17 | |
#io_LED = 21 | |
io_DOORSWITCH = 22 | |
#io_LIGHTSENSOR = 6 | |
io_GARAGEREMOTE1 = 19 | |
intDoorCloseTime = 30 | |
# Set pin in/out | |
#io.setup(io_PIR, io.IN) | |
#io.setup(io_LED, io.OUT, initial=0) | |
io.setup(io_DOORSWITCH, io.IN, pull_up_down=io.PUD_UP) | |
io.setup(io_GARAGEREMOTE1, io.OUT, initial=0) | |
# logging | |
enableConsoleDebug = True # set to true to write output to console | |
logFileName = '/home/pi/gsvc/gweb.log' | |
fmtDateTime = '%Y-%m-%d %H:%M:%S' | |
# Create logging object | |
log = logging.getLogger('gweb') | |
logFormatter = logging.Formatter("%(asctime)s [%(threadName)-12.12s] [%(levelname)-5.5s] %(message)s") | |
log.setLevel(logging.DEBUG) | |
# Add the log message handler to the logger | |
handler = logging.handlers.RotatingFileHandler(logFileName, maxBytes=1024000, backupCount=5) | |
handler.setFormatter(logFormatter) | |
log.addHandler(handler) | |
# Write all the messages out to console for debugging purposes | |
if enableConsoleDebug: | |
consoleHandler = logging.StreamHandler() | |
consoleHandler.setFormatter(logFormatter) | |
log.addHandler(consoleHandler) | |
# Function to return formatted date time | |
def fnDateTimeNow(): | |
global dttmnow | |
dttmnow = datetime.datetime.now().strftime(fmtDateTime) | |
return dttmnow | |
# Function to control garage door. action = open/close, pin = pin # door connected to, source used for debugging | |
def fnGarageControl(action, doorswitchpin, remotepin): | |
#log.debug("in fn1 door switch pin: "+str(doorswitchpin)+", remote pin: "+str(remotepin)+", action: "+action+".") | |
intTryCount = 0 | |
intDoorState = io.input(doorswitchpin) | |
intDoorStatePrev = io.input(doorswitchpin) | |
while intDoorState == intDoorStatePrev and intTryCount < 3: | |
intTryCount += 1 | |
intDoorState = io.input(doorswitchpin) | |
if action == "open": | |
if intDoorState: | |
log.info("door is already open...") | |
return "already open" | |
elif not intDoorState: | |
log.info("opening door...") | |
io.setup(remotepin, io.OUT) | |
io.output(remotepin, io.HIGH) | |
time.sleep(2) | |
io.output(remotepin, io.LOW) | |
time.sleep(intDoorCloseTime) | |
#if not intDoorState: | |
#send_ifttt(BASE_URL, "garage1wrong", KEY) | |
#log.warning("something went wrong opening the door, pausing script for " + | |
# str(intSleeptimeMins) + " minutes") | |
#time.sleep(intSleeptimeMins * 60) | |
if action == "close": | |
if not intDoorState: | |
log.info("door is already closed...") | |
return "already closed" | |
elif intDoorState: | |
log.info("closing door...") | |
io.setup(remotepin, io.OUT) | |
io.output(remotepin, io.HIGH) | |
time.sleep(2) | |
io.output(remotepin, io.LOW) | |
time.sleep(intDoorCloseTime) # give the door time to close | |
intDoorState = io.input(doorswitchpin) | |
#if intDoorState: | |
#send_ifttt(BASE_URL, "garage1wrong", KEY) | |
#log.warning("something went wrong closing the door, pausing script for " + | |
# str(intSleeptimeMins) + " minutes.") | |
#time.sleep(intSleeptimeMins * 60) | |
# Refresh door state for while loop | |
intDoorState = io.input(doorswitchpin) | |
if intTryCount == 3: | |
log.error("Error occurred changing door state to "+action+".") | |
# Log a startup message | |
log.info("Script startup.") | |
@error(404) | |
def error404(error): | |
return ('404') | |
@route('/garage/toggle/<pin:int>/<seconds:int>/<key>') | |
def toggle(pin, seconds, key): | |
try: | |
if key == myKey: | |
remote_ip = request.environ.get('REMOTE_ADDR') | |
log.info("IP: %s is toggling pin %s for %s seconds." % (remote_ip, pin, seconds)) | |
io.setup(pin, io.OUT) | |
io.output(pin, io.HIGH) | |
time.sleep(seconds) | |
io.output(pin, io.LOW) | |
return {'pin':pin, 'seconds':seconds, 'success':True} | |
else: | |
log.info("IP: %s is attempting to toggle pin %s for %s seconds without a valid key." % (remote_ip, pin, seconds)) | |
return {'pin':'no_key', 'seconds':'no_key', 'success':False} | |
except: | |
return {'pin':pin, 'seconds':seconds, 'success':False} | |
@route('/garage/status/<pin:int>/<key>') | |
def status(pin, key): | |
try: | |
if key == myKey: | |
remote_ip = request.environ.get('REMOTE_ADDR') | |
if remote_ip != '127.0.0.1': | |
log.info("IP: %s is requesting status of pin %s." % (remote_ip, pin)) | |
return {'pin':pin, 'status':io.input(pin), 'success':True} | |
else: | |
log.error("IP: %s is requesting status of pin %s without a valid key." % (remote_ip, pin)) | |
return {'pin':pin, 'success':False} | |
except: | |
return {'pin':pin, 'success':False} | |
@route('/garage/<action:re:open|close>/<key>') | |
def openClose(action, key): | |
try: | |
remote_ip = request.environ.get('REMOTE_ADDR') | |
if key == myKey: | |
log.info("IP: %s is requesting to %s the garage." % (remote_ip, action)) | |
garageCurrentState = io.input(io_DOORSWITCH) #0=closed, 1=open | |
if not garageCurrentState and action == 'open': | |
log.info("IP: %s opened the garage." % (remote_ip)) | |
#fnGarageControl("open", io_DOORSWITCH, io_GARAGEREMOTE1) | |
thr = threading.Thread(target=fnGarageControl, args=("open", io_DOORSWITCH, io_GARAGEREMOTE1), kwargs={}) | |
thr.start() | |
return {'response':'Opening the garage'} | |
elif garageCurrentState and action == 'open': | |
log.info("IP: %s garage is already open." % (remote_ip)) | |
return {'response':'the garage is already open.'} | |
elif garageCurrentState and action == 'close': | |
log.info("IP: %s %s the garage." % (remote_ip, action)) | |
#fnGarageControl("close", io_DOORSWITCH, io_GARAGEREMOTE1) | |
thr = threading.Thread(target=fnGarageControl, args=("close", io_DOORSWITCH, io_GARAGEREMOTE1), kwargs={}) | |
thr.start() | |
return {'response':'Closing the garage'} | |
elif not garageCurrentState and action == 'close': | |
log.info("IP: %s garage is already closed." % (remote_ip)) | |
return {'response':'the garage is already closed.'} | |
else: | |
log.error("IP: %s attempted to %s the garage with an invalid key." % (remote_ip, action)) | |
return 'no key' | |
except: | |
return {'action':action, 'success':False} | |
@route('/garage/temp/') | |
def gettemp(): | |
remote_ip = request.environ.get('REMOTE_ADDR') | |
humidity, temperature = ada.read_retry(ada.AM2302, io_temp) | |
temperature = temperature * 9/5.0 + 32 | |
if humidity is not None and temperature is not None: | |
temperature = '{:.1f} degrees'.format(temperature) | |
humidity = '{:.1f}%'.format(humidity) | |
log.info("IP: %s is requesting temp data (%s, %s humidity)." % (remote_ip, temperature, humidity)) | |
return {'temp':temperature, 'humidity':humidity} | |
else: | |
#print('Failed to get reading. Try again!') | |
log.error("IP: %s failed to retrieve temp data." % (remote_ip)) | |
return {'temp':unknown, 'humidity':unknown} | |
run(host='0.0.0.0', port=8412, reloader=True) | |
except Exception: | |
exc_type, exc_obj, exc_tb = sys.exc_info() | |
log.error("Caught Exception at line " + str(exc_tb.tb_lineno) + ": " + str(sys.exc_info())) | |
# todo: send alert or email w/ detail before dying | |
pass | |
finally: | |
log.info("Script exit") | |
io.cleanup() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var http = require('http'); | |
var url = ''; | |
// Route the incoming request based on type (LaunchRequest, IntentRequest, | |
// etc.) The JSON body of the request is provided in the event parameter. | |
exports.handler = function (event, context) { | |
try { | |
console.log("event.session.application.applicationId=" + event.session.application.applicationId); | |
/** | |
* Uncomment this if statement and replace application.id with yours | |
* to prevent other voice applications from using this function. | |
*/ | |
if (event.session.application.applicationId !== "amzn1.ask.skill.blah") { | |
context.fail("Invalid Application ID"); | |
} | |
if (event.session.new) { | |
onSessionStarted({requestId: event.request.requestId}, event.session); | |
} | |
if (event.request.type === "LaunchRequest") { | |
onLaunch(event.request, | |
event.session, | |
function callback(sessionAttributes, speechletResponse) { | |
context.succeed(buildResponse(sessionAttributes, speechletResponse)); | |
}); | |
} else if (event.request.type === "IntentRequest") { | |
onIntent(event.request, | |
event.session, | |
function callback(sessionAttributes, speechletResponse) { | |
context.succeed(buildResponse(sessionAttributes, speechletResponse)); | |
}); | |
} else if (event.request.type === "SessionEndedRequest") { | |
onSessionEnded(event.request, event.session); | |
context.succeed(); | |
} | |
} catch (e) { | |
context.fail("Exception: " + e); | |
} | |
}; | |
/** | |
* Called when the session starts. | |
*/ | |
function onSessionStarted(sessionStartedRequest, session) { | |
console.log("onSessionStarted requestId=" + sessionStartedRequest.requestId | |
+ ", sessionId=" + session.sessionId); | |
} | |
/** | |
* Called when the user launches the app without specifying what they want. | |
*/ | |
function onLaunch(launchRequest, session, callback) { | |
console.log("onLaunch requestId=" + launchRequest.requestId | |
+ ", sessionId=" + session.sessionId); | |
//Have Alexa say a welcome message | |
getWelcomeResponse(callback); | |
} | |
/** | |
* Called when the user specifies an intent for this application. | |
*/ | |
function onIntent(intentRequest, session, callback) { | |
console.log("onIntent requestId=" + intentRequest.requestId | |
+ ", sessionId=" + session.sessionId); | |
var intent = intentRequest.intent, | |
intentName = intentRequest.intent.name; | |
if ("GarageAction" === intentName) { | |
console.log("Garage action was requested..."); | |
ControlGarage(intent, session, callback); | |
} else if ("GetGarageState" === intentName) { | |
console.log("Garage state was requested..."); | |
GetGarageState(intent, session, callback); | |
} else if ("GetTemp" === intentName) { | |
console.log("Garage temperature was requested..."); | |
GetTemp(intent, session, callback); | |
} else { | |
throw "Invalid intent"; | |
} | |
} | |
/** | |
* Called when the user ends the session. | |
* Is not called when the app returns shouldEndSession=true. | |
*/ | |
function onSessionEnded(sessionEndedRequest, session) { | |
console.log("onSessionEnded requestId=" + sessionEndedRequest.requestId | |
+ ", sessionId=" + session.sessionId); | |
// Add cleanup logic here | |
} | |
/** | |
* Helpers that build all of the responses. | |
*/ | |
function buildSpeechletResponse(title, output, repromptText, shouldEndSession) { | |
return { | |
outputSpeech: { | |
type: "PlainText", | |
text: output | |
}, | |
card: { | |
type: "Simple", | |
title: title, | |
content: output | |
}, | |
reprompt: { | |
outputSpeech: { | |
type: "PlainText", | |
text: repromptText | |
} | |
}, | |
shouldEndSession: shouldEndSession | |
} | |
} | |
function buildResponse(sessionAttributes, speechletResponse) { | |
return { | |
version: "1.0", | |
sessionAttributes: sessionAttributes, | |
response: speechletResponse | |
} | |
} | |
/** | |
* Functions that control the app's behavior. | |
*/ | |
function getWelcomeResponse(callback) { | |
// If we wanted to initialize the session to have some attributes we could add those here. | |
var sessionAttributes = {}; | |
var cardTitle = "Welcome"; | |
var speechOutput = "You can ask me to open the garage, close the garage, door state, or the temperature. " | |
+ "Please tell me what you want to do with your garage"; | |
// If the user either does not reply to the welcome message or says something that is not | |
// understood, they will be prompted again with this text. | |
var repromptText = "Please tell me what you want to do with your garage"; | |
var shouldEndSession = false; | |
callback(sessionAttributes, | |
buildSpeechletResponse(cardTitle, speechOutput, repromptText, shouldEndSession)); | |
} | |
function ControlGarage(intent, session, callback) { | |
var cardTitle = "Garage Control"; | |
var repromptText = ""; | |
var sessionAttributes = {}; | |
var shouldEndSession = true; | |
var speechOutput = ""; | |
var action = intent.slots.Action; | |
console.log("Requested to "+action.value+" the garage."); | |
if (action.value == "open") { | |
//console.log("Requested to open the garage"); | |
url = 'blah'; | |
} else if (action.value == "shut" || action.value == "close") { | |
//console.log("Requested to close the garage"); | |
url = 'blah'; | |
} else { | |
throw "Invalid slot"; | |
} | |
console.log("connecting to: " + url); | |
http.get( url, function( response ) { | |
var data = ''; | |
response.on( 'data', function( x ) { data += x; } ); | |
response.on( 'end', function() { | |
var obj = JSON.parse(data); | |
console.log(obj); | |
speechOutput = obj.response; | |
callback(sessionAttributes, buildSpeechletResponse(cardTitle, speechOutput, repromptText, shouldEndSession)); | |
} ); | |
} ); | |
} | |
function GetGarageState(intent, session, callback) { | |
var cardTitle = "Garage Status"; | |
var repromptText = ""; | |
var sessionAttributes = {}; | |
var shouldEndSession = true; | |
var speechOutput = ""; | |
url = "blah"; | |
console.log("connecting to: " + url); | |
http.get( url, function( response ) { | |
var data = ''; | |
response.on( 'data', function( x ) { data += x; } ); | |
response.on( 'end', function() { | |
var obj = JSON.parse(data); | |
switch(obj.status) | |
{ | |
case 0: | |
text = "The garage is closed."; | |
break; | |
case 1: | |
text = "The garage is open."; | |
break; | |
default: | |
text = "Could not connect to the garage"; | |
} | |
console.log(obj); | |
speechOutput = text; | |
callback(sessionAttributes, buildSpeechletResponse(cardTitle, speechOutput, repromptText, shouldEndSession)); | |
} ); | |
} ); | |
} | |
function GetTemp(intent, session, callback) { | |
var cardTitle = "Outside temperature"; | |
var repromptText = ""; | |
var sessionAttributes = {}; | |
var shouldEndSession = true; | |
var speechOutput = ""; | |
var url = "blah"; | |
console.log("connecting to: " + url); | |
http.get( url, function( response ) { | |
var data = ''; | |
response.on( 'data', function( x ) { data += x; } ); | |
response.on( 'end', function() { | |
var obj = JSON.parse(data); | |
console.log(obj); | |
speechOutput = "The temperature is "+obj.temp+", humidity at "+obj.humidity; | |
callback(sessionAttributes, buildSpeechletResponse(cardTitle, speechOutput, repromptText, shouldEndSession)); | |
} ); | |
} ); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment