The clue you need is at the top of the page you linked. "This is unofficial (and partial) documentation of the Chamberlain/Liftmaster MyQ API". They do not have even close to complete documentation.
The API is not really compatible with ISY.
You first need to make a request to an authentication endpoint using a username and password.
https://myqexternal.myqdevice.com/Membership/ValidateUserWithCulture?appId=Vj8pQggXLhLy0WHahglCD4N1nAkkXQtGYpq2HrHD7H1nvmbT55KqtN6RSF4ILB%2fi&securityToken=null&username=**account_username**&password=**account_password**&culture=en
JSON is returned with a securityToken.
{"UserId":254586,"SecurityToken":"**security_token**","ReturnCode":"0","ErrorMessage":"","ExecutionTimes":"0;78.001;202.8026;187.2024","BrandId":2,"BrandName":"Chamberlain","RegionId":1}
You then use that token to enumerate devices:
https://myqexternal.myqdevice.com/api/UserDeviceDetails?appId=Vj8pQggXLhLy0WHahglCD4N1nAkkXQtGYpq2HrHD7H1nvmbT55KqtN6RSF4ILB%2fi&securityToken=**securityToken**
The security token expires after - I think - an hour. So if you call /api/UserDeviceDetails and get 'Access Denied' you need to go back to /Membership/ValidateUserWithCulture with the username and password to get a new token.
Control of the device is done using the Device/setDeviceAttribute (https://sites.google.com/a/arrayent.com/api/reference/device/setdeviceattribute). Looks like the name to send should be 'desireddoorstate'.
Here is some code for another platform that implements this - I'm sure you can find the actual details in here:
--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)
The ISY cannot do this though. I am hopeful UDI will include parsing JSON responses in the network module in the future.
On the I/O Linc:
There are issues introduced into MyQ when you short the cables together with an I/O linc (if that's what you are using or planning to use). Every time you operate the door thru I/O linc, the MyQ system resets and you don't get any status thru MyQ. That's a shame - thru MyQ I can see the actual position of the door. I've hacked into a car control and wired that up (I use an Elk output - not an I/O link - but the issue is the same). It kinda works - but it breaks MyQ.
So - in summary - it can be done with an external helper server on your lan. But it's not trivial. This is why I hacked an opener button... so I could avoid this nastiness.
Michael.