INSTALLATION INSTRUCTIONS:
The first step is to copy the files in the zip archive found below to your Vera
Navigate to the APPS tab in the Vera UI and then click on ‘Develop Apps’
Click on ‘Luup files’ and then upload the 6 lua, xml and json files:
-D_MyQGateway.json
-D_MyQGateway.xml
-I_MyQGateway.xml
-L_MyQGateway.lua
-L_MyQGateway_json.lua
-S_MyQGateway.xml
Scroll down a bit and check ‘Restart Luup after upload’ and then click the ‘Go’ button
If you want to use the icon with the device, use WinSCP or another app to ssh to your Vera box.
Copy the “MyQ_Gateway.png” file to /www/cmh/skins/default/icons/
]Now we have to create the device:
Navigate to the APPS tab in the Vera UI and then click on ‘Develop Apps’
Click ‘Create device’ and:
- In the Description field type “MyQ Gateway”
- In the Upnp Device Filename type “D_MyQGateway.xml”
Finally, click the “Create device” button. A pop-up should occur with “Device created: Dev #”
Click “Reload” on the top right of the Vera UI
If all went right, you should see a warning at the top of the page:
MyQGateway: Username not configured. Password not configured.
Click on the wrench of the MyQ Gateway device and then click on the “Advanced” tab
If you don’t see a “Username” or “Password” variable variable under Variables, you’ll need to click on ‘Reload’ again.
Check the Advanced tab of the “MyQ Gateway” device and enter your username and password for MyQ and then click on Save
Your Vera should startup again and create your children device, which are as many garage doors as you have associated to the gateway.
05/19/2014 Update
Displayed last auth (last time an authentication event occurred) and last check (last time garage door status was checked) to the MyQ Gateway device display. Added some error handling
05/18/2014 Update
Added a new typeId constant to GARAGE_DOOR_OPENER
05/12/2014 Update
I changed the re-authentication procedure a few days ago and left the code out to do it, doh! This version has it
I’ve changed everything to be referred to as “MyQGateway” instead of LiftMasterOpener – thought that might make some more sense
05/09/2014 Update
I’ve changed the code to re-authenticate every 23 hours as it seems that somewhere after 24 hours I have a failure. Not sure if this will fix it
Initial attempt at using task to give feedback through the Vera UI
Added an icon that can be uploaded to /www/cmh/skins/default/icons
04/25/2014 Update
Am at a pre-alpha stage: The second page of this thread has a zip file with code to create a MyQ Gateway device along with creating any garage door openers found. Looking for constructive feedback on my first foray into Lua/LUUP programming. So far, the devices work in that they:
a) Report status of openers
b) Allow openers to be opened/closed through a doorlock control
Look for more documentation at a future date as I learn more about plugins
My Subject line is a little bold as I don’t have a plugin to offer, but… I believe there’s hope
I have LUA code (found in this thread: http://forum.micasaverde.com/index.php/topic,9398.0.html) that can open and close my garage door along with checking their state.
I plan on trying to create a plugin (though I’m fairly clueless in this department.). I gather this topic will be more of a documentation of my struggles in creating the plugin. I’d like to thank @guessed for his help to date – recall, I’m a bumbling buffoon in this
anyway, for this to work:
You need to purchase their gateway device: http://www.liftmaster.com/lmcv2/accessoryfamily/213/myq-enabled-accessories/ [it’s around $31 at amazon] and create an account for their service:
http://www.liftmaster.com/lmcv2/products/introducingliftmasterinternetgateway.htm
Unfortunately, there are no MyQ supported APIs on how to interface with their services but some smart folks have reverse engineered it and that’s what I’m using. Here’s the latest iteration of code, which I documented in the previous thread:
I have now created 4 separate functions:
retrieveSecurityToken: Has two returns- authResult and authText
authResult is true or false, if true authText has the SecurityToken if false, it has the reason for a failure in retrieving the security token
Is the way I’m returning the result(s) bad form? So there would be a timer calling this and keeping this in “state” in Vera eventually
inspectDevices: Has two returns ( though should be 3 ) one is a table of tables that builds up info about the devices [ID, Names, Description, door status] – I imagine this general code could create all the original devices [left garage door opener with associated device id and device name] – on reloads does a plug check all of these again so that you can automatically add new openers if they are found?
changeGarageDoorState: opens or closes a given garage door. The action buttons on a plugin, I’d presume
getGarageDoorStatus: shows the current state of a given garage door [open, opening, closing, closed]. Showing the current state on the plugin
and the horrible code follows:
--[[
The beginning of a plugin to connect to the Chamberlain Liftmaster MyQ API
Unofficial documentation of the API is found here:
http://docs.unofficialliftmastermyq.apiary.io/
There is also a Ruby gem implementation at GitHub:
https://github.com/pfeffed/liftmaster_myq
]]
--Change these to match the authentication used to access your MyQ account
local username=""
local password = ""
--Function found: http://lua-users.org/wiki/StringRecipes
--Used to encode username / password for submission
function url_encode(str)
if (str) then
str = string.gsub (str, "\n", "\r\n")
str = string.gsub (str, "([^%w %-%_%.%~])",
function (c) return string.format ("%%%02X", string.byte(c)) end)
str = string.gsub (str, " ", "+")
end
return str
end
--Libraries
local json = require("json")
local https = require("ssl.https")
--These should not change but who knows, follow UPPERCASE convention for names
local APPID = "Vj8pQggXLhLy0WHahglCD4N1nAkkXQtGYpq2HrHD7H1nvmbT55KqtN6RSF4ILB%2fi"
local BASEURL = "https://myqexternal.myqdevice.com/"
local VALIDATIONPATH = "Membership/ValidateUserWithCulture"
local USERDEVICEDETAILS = "api/UserDeviceDetails"
--CONSTANTS for TypeIds in the Devices list
local GARAGEDOOROPENER = 47
local GATEWAYDEVICE = 49
--Access URLs
local auth_string = VALIDATIONPATH .. "?appId=" .. APPID .. "&username=" .. username .. "&password=" .. password .. "&culture=en"
--Statuses for doors
local doorStatuses = {["1"] = "open",
["2"] = "closed",
["4"] = "opening",
["5"] = "closing"}
local SecurityToken
--[[
Connects to the LiftMaster/Chamberlain MyQ API
Returns result and resultText
]]
local function retrieveSecurityToken(authURL, username, password)
local result --Did we successfully connect and get the SecurityToken, is true or false
local resultText --If false, the error, if true, the SecurityToken
--Table to hold our response from the call
auth_response = { }
local response, status, header = https.request
{
url = authURL,
method = "GET",
headers = {
["Content-Type"] = "application/json",
["Content-Length"] = 0
},
sink = ltn12.sink.table(auth_response)
}
--Check out the response
if( response == 1 ) then
--Decode our JSON, we have a response
authResponseData = json.decode( auth_response[1] )
--Check our return code
if( authResponseData.ReturnCode == "0" ) then
result = true
resultText = authResponseData.SecurityToken
else
result = false
resultText = "Authentication Error!"
end
else
result = false
resultText = "Unsuccessful at connecting with the authorization URL!"
end
return result, resultText
end
--[[
Inspect all devices associated with the MyQ
]]
local function inspectDevices(deviceURL, SecurityToken)
local connectionResult --True if successful, false if not
local connectionText --Holds issue with connection
local openerInfo = {} --Table to hold info about openers
local device_response = { } --Table to hold our response from the call
--Fire up our connection
local response, status, header = https.request
{
url = deviceURL,
method = "GET",
headers = {
["Content-Type"] = "application/json",
["Content-Length"] = 0
},
sink = ltn12.sink.table(device_response)
}
--Check out our response
if( response==1 ) then
--Decode our JSON: Am unclear as to why device_response[1] threw an error
--local deviceContent = json.decode( device_response[1] )
local deviceContent = json.decode(table.concat(device_response))
--A 0 appears to indicate we have had a success
if(deviceContent.ReturnCode == "0" ) then
connectionResult = true
local numOpeners = 0
--Time to loop over our device collection
for i,d in ipairs (deviceContent.Devices) do
local DeviceName = d.DeviceName
local DeviceId = d.DeviceId
local ParentName
--TypeId of 49 appears to be the gateway device
--Useful here might be the desc which is the name in MyQ (e.g. Home)
--Perhaps that should be the name of the parent device?
if( d.TypeId == GATEWAYDEVICE ) then
for k, attr in ipairs( d.Attributes ) do
if( attr.Name == "desc" ) then
ParentName = attr.Value
end
end
end
--TypeId of 47 appears to be the individual garage door openers
if( d.TypeId == GARAGEDOOROPENER ) then
--Stop the presses, we found an opener
numOpeners = numOpeners +1
local doorState
--Each device has an attributes collection, over it we go
for j, attr in ipairs ( d.Attributes) do
if( attr.Name == "desc") then
openerName = attr.Value
elseif( attr.Name == "doorstate" ) then
local doorstateValue = attr.Value
if( doorStatuses[doorstateValue] ~= nill) then
doorState = doorStatuses[doorstateValue]
else
doorState = "ERROR: Unknown State! " .. doorstateValue
end
end
end
--Keep track of all the openers along with their state
table.insert(openerInfo, numOpeners, {
DeviceId = DeviceId,
DeviceName = DeviceName,
DoorState = doorState,
OpenerName = openerName
})
end
end
else
connectionResult = false
connectionText = "Failed call to the MyQ API, perhaps refresh of token needed?"
end
else
connectionResult = false
connectionText = "Unsuccessful at connecting with device URL!"
end
return connectionResult, openerInfo
end
--[[
Check on the status of a given garage door opener
]]
local function getGarageDoorStatus(deviceURL, SecurityToken, DeviceId)
local connectionResult --True if successful, false if not
local connectionText --Holds issue with connection
local device_response = { } --Table to hold our response from the call
--Fire up our connection
local response, status, header = https.request
{
url = deviceURL,
method = "GET",
headers = {
["Content-Type"] = "application/json",
["Content-Length"] = 0
},
sink = ltn12.sink.table(device_response)
}
--Check out our response
if( response==1 ) then
--Decode our JSON: Am unclear as to why device_response[1] threw an error
--local deviceContent = json.decode( device_response[1] )
local deviceContent = json.decode(table.concat(device_response))
--A 0 appears to indicate we have had a success
if(deviceContent.ReturnCode == "0" ) then
connectionResult = true
--Time to loop over our device collection
for i,d in ipairs (deviceContent.Devices) do
-- Only interested in our specified garage door opener
if( d.TypeId == GARAGEDOOROPENER and d.DeviceId == DeviceId ) then
local doorState
for j, attr in ipairs ( d.Attributes ) do
if( attr.Name == "doorstate" ) then
local doorstateValue = attr.Value
if( doorStatuses[doorstateValue] ~= nill) then
connectionText = doorStatuses[doorstateValue]
else
connectionText = "ERROR: Unknown State! " .. doorstateValue
end
end
end
end
end
else
connectionResult = false
connectionText = "Failed call to the MyQ API, perhaps refresh of token needed?"
end
else
connectionResult = false
connectionText = "Unsuccessful at connecting with device URL!"
end
return connectionResult, connectionText
end
--[[
Change the state of a garage door. Basically a doorstate of 1 is open and a doorstate of 0 is close
DeviceId is found in the output of inspectDevices
]]
local function changeGarageDoorState(DoorDeviceId, AppId, DoorAction, SecurityToken)
local result --Result of the action, true or false
local resultText --Summary of the result
local doorActions = {[0] = "close",
[1] = "open"}
--Our JSON to be delivered...
jsonPut = {
AttributeName = "desireddoorstate",
DeviceId = DoorDeviceId,
ApplicationId = AppId,
AttributeValue = DoorAction,
SecurityToken = SecurityToken
}
--JSON encode it for delivery
json_data = json.encode(jsonPut)
local response_body = {}
--Fire up our request, noting that we are using the PUT method and setting our content length in the header
local response, status, header = https.request{
method = "PUT",
url = "https://myqexternal.myqdevice.com/Device/setDeviceAttribute",
headers = {
["Content-Type"] = "application/json",
["Content-Length"] = string.len(json_data)
},
source = ltn12.source.string(json_data),
sink = ltn12.sink.table(response_body)
}
if( response == 1) then
local output = json.decode( response_body[1] )
if ( output.ReturnCode == "0" ) then
result = true
resultText = "Successfully changed status to " .. doorActions[DoorAction]
else
result = false
resultText = "Authentication error. Perhaps token expired?"
end
else
result = false
resultText = "Unsuccessful at communicating with the setDeviceAttribute service"
end
return result, resultText
end
------------------------------------------
--
-- Time to use the functions...
--
------------------------------------------
--Get our security token, making sure we encode our username and password
local authResult, authText = retrieveSecurityToken(BASEURL .. auth_string, url_encode(username), url_encode(password))
--Is everything ok here?
if(authResult == true ) then
print("Success, security token is: " .. authText)
SecurityToken = authText
else
print("ERROR: Message is " .. authText)
end
--Swing away, let's see what we have for devices..
local connectionResult, openerInfo = inspectDevices(BASEURL .. USERDEVICEDETAILS .. "?appId=" .. APPID .. "&securityToken=" .. SecurityToken, SecurityToken)
print("I see that you have " .. #openerInfo .. " garage door openers:")
for i=1, #openerInfo do
print( openerInfo[i].OpenerName .. " is currently " .. openerInfo[i].DoorState)
end
--Action: 1=Open, 0=Close
--Let's close a door from above..
result, resultText = changeGarageDoorState(openerInfo[2].DeviceId, APPID, 0, SecurityToken)
print(result)
print(resultText)
socket.sleep(3)
--Check on the status..
local gdStatus, gdStatusText = getGarageDoorStatus(BASEURL .. USERDEVICEDETAILS .. "?appId=" .. APPID .. "&securityToken=" .. SecurityToken, SecurityToken, openerInfo[2].DeviceId)
print(gdStatusText)
I’d be grateful for anyone’s comments/suggestions/thoughts along the way.