To be honest I've been super busy lately among other things so going to keep this one short and simple and to the point. I've added a stupid amount of comments in the code below so anyone should be able to follow. But just hacking around with my wifi connected garage door there isn't really a official alexa skill or google action setup for it and started piecing one together myself. This isn't by any means complete but should help anyone working on a similar project.
Enjoy!
You will need Python 3.6+ for this code as it uses asyncio.
#!/usr/bin/env python
"""
In progress Lambda func for integration with
Alexa for MyQ Garage Door opening
"""
import asyncio
import logging
import sys
import uvloop
from aiohttp import ClientSession
import pymyq
# Setup some basic logging stuff
log_format = '%(asctime)-15s %(levelname)s %(message)s'
logging.basicConfig(format=log_format, level=logging.DEBUG)
logger = logging.getLogger('myq')
# Garage door states
VALID_STATES = [
'open',
'opening',
'close',
'closing',
]
# Actions a user can take
VALID_ACTIONS = [
'open',
'close',
]
CLOSED_STATE = 'closed'
OPENED_STATE = 'open'
def is_valid_state_change(current_state, desired_action):
"""
Sanity checking on desired actions
:param current_state: device.state
:param desired_action: open/close
:return: Boolean
"""
# make sure something reasonable is asked for
if desired_action not in VALID_ACTIONS:
return False
# you can only do one or the other buddy
# based on the current state
if desired_action == 'open':
# must be closed
if current_state == CLOSED_STATE:
return True
if desired_action == 'close':
# must be open
if current_state == OPENED_STATE:
return True
return False
def is_valid_brand(brand):
"""
Ensure proper brand is being used before trying
to auth
:param brand: Brand of device
:return: Boolean
"""
VALID_BRANDS = [
'chamberlain',
'craftsman',
'liftmaster',
'merlin',
]
if brand in VALID_BRANDS:
return True
logger.error("User supplied invalid branch: %s", brand)
return False
async def init():
"""
Creates http session to run
commands
"""
# add your auth here
email = ''
password = ''
brand = ''
# setup http session
async with ClientSession() as session:
if is_valid_brand(brand):
# auth with myq api
myq = await pymyq.login(
email,
password,
brand,
session
)
# myq methods:
# authenticate
# get_devices
# get devices - returns array
devices = await myq.get_devices()
for device in devices:
# available properties:
# 'brand', 'device_id', 'name',
# 'parent_id', 'serial', 'state', 'type'
logger.debug('Brand: %s', device.brand)
logger.debug('Device ID: %s', device.device_id)
logger.debug('Name: %s', device.name)
logger.debug('Parent ID: %s', device.parent_id)
logger.debug('Serial Number: %s', device.serial)
logger.debug('State: %s', device.state)
logger.debug('Type: %s', device.type)
# prompt the user
try:
desired_action = input('Would you like to open or close the door? ')
except KeyboardInterrupt:
logger.info("Exiting cleanly; have a good day!")
sys.exit(0)
if is_valid_state_change(device.state, desired_action):
if desired_action == 'open':
status = await device.open()
if desired_action == 'close':
status = await device.close()
if status:
logger.info("Performing requested action: %s", desired_action)
else:
logger.error("Received a invalid response from the server: %s", status)
else:
logger.error("Sorry, that was not a valid option based on the current state of your machine")
if __name__ == '__main__':
asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
loop = asyncio.get_event_loop()
loop.run_until_complete(init())