Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MyQ Lite is no longer able to login to MyQ.com #126

Closed
osborne1248 opened this issue Aug 25, 2021 · 114 comments
Closed

MyQ Lite is no longer able to login to MyQ.com #126

osborne1248 opened this issue Aug 25, 2021 · 114 comments

Comments

@osborne1248
Copy link

Looks like this just happened again today. When trying to log in through MyQ in SmartThings getting the "The username or password you entered is incorrect. Go back and try again."

@brbeaird - can you work your magic please sir?

@chsull
Copy link

chsull commented Aug 25, 2021

Ditto -- login is failing as of today.

@osborne1248
Copy link
Author

Got message on another thread. He's working on it now. May be a few days before he knows anything.

@chsull
Copy link

chsull commented Aug 25, 2021

Thanks!

@clmyers2019
Copy link

same here started getting before i updated everything

@phaldor8
Copy link

phaldor8 commented Aug 25, 2021 via email

@brbeaird
Copy link
Owner

Yep, MyQ changed something, and this is currently broken. I’m not sure at this point if it’s a permanent change - this has happened occasionally in the past where it comes back up the next day.

The good news is there is a newer version of the API login flow that some other integrations (HomeBridge, pymyq) are using successfully. The bad news is it is not a simple change to implement - a couple parts in particular I’m not really sure will work within Groovy. I will see what I can do, but it may be several days before I make any real progress.

@jasonkaruza
Copy link

Looks like per https://github.com/hjdhjd/homebridge-myq/search?q=v6 the main difference is around authentication for v6 using OAuth.

Starting with v6 of the myQ API, myQ now uses OAuth 2.0 + PKCE to authenticate users and
provide access tokens for future API calls. In order to successfully use the API, we need
to first authenticate to the myQ API using OAuth, get the access token, and use that for
future API calls.

I'm not sure if the Smartthings Groovy implementation is the same as normal Java's, but it looks like there is an AuthConfig class that might help with Oauth: http://javadox.com/org.codehaus.groovy.modules.http-builder/http-builder/0.6/groovyx/net/http/AuthConfig.html

@brbeaird
Copy link
Owner

brbeaird commented Aug 25, 2021 via email

@Moogacoder
Copy link

Moogacoder commented Aug 25, 2021 via email

@heisler3030
Copy link

actually the app is still working fine for me. Came over from thomasmunduchira/myq-api#34 which is another project that started flapping today unfortunately.

@oferbarzakai
Copy link

oferbarzakai commented Aug 26, 2021

same here - getting "MyQ command failed due to bad login". MyQ app directly works fine.

here is the log from the IDE:

image

@colinrblake
Copy link

colinrblake commented Aug 26, 2021

My (own) iOS app is also failing as of yesterday. Have they turned off the V5 support?

CBL: response status code: 400
CBL: response data: {
    "SecurityToken": "",
    "ReturnCode": "0",
    "ErrorMessage": "please contact customer care, supportID: 14941968167351633782",
    "CorrelationId": ""
}

@dandrake2901
Copy link

Me too. Getting login errors from Smartthings when I try to control my garage door. Tried changing the password in the MyQ app. Can log into the MyQ app, but can't update the password in the MyQ Lite SmartApp in the Smartthings app.

@brbeaird
Copy link
Owner

Yes, it's going to be broken for everyone - sorry. MyQ made a change within the last 24 hours that breaks the version (v5) of the API we are using. At least one other project in github has successfully moved to v6, but it's a major change. I do think I'll figure out how to do it here, but with my schedule right now, it's not going to be quick, unfortunately.

@vrchlabak
Copy link

Thanks for getting on this!! Can't be without it!

@wtblock
Copy link

wtblock commented Aug 26, 2021

Good to know this is being addressed.

@benji1077
Copy link

Appreciate the investigation @brbeaird! I had just gotten automation back up and working and then ran into the “login error” noted above. Excited to get back to normal.

@wtblock
Copy link

wtblock commented Aug 31, 2021

I bypassed Chamberlain a long time ago by using a Z-Wave relay in parallel with the wired switch panel on my garage door controllers.

My Chamberlain gate controller is another problem however because it does not have a wired panel switch and can only be controlled via a remote or the MyQ hub.

While we are waiting for Brian to address the latest changes from Chamberlain, I decided to brut force a fix by modifying one of my remotes to be controlled by a ZigBee relay. If anyone is interested, I published a video here:

https://youtu.be/ImFzcnYiSzY

Regardless of whether or not the issue is resolved, having remote access to a remote will provide a solid backup if anything should go wrong in the future.

@oferbarzakai
Copy link

I bypassed Chamberlain a long time ago by using a Z-Wave relay in parallel with the wired switch panel on my garage door controllers.

My Chamberlain gate controller is another problem however because it does not have a wired panel switch and can only be controlled via a remote or the MyQ hub.

While we are waiting for Brian to address the latest changes from Chamberlain, I decided to brut force a fix by modifying one of my remotes to be controlled by a ZigBee relay. If anyone is interested, I published a video here:

https://youtu.be/ImFzcnYiSzY

Regardless of whether or not the issue is resolved, having remote access to a remote will provide a solid backup if anything should go wrong in the future.

i have done the same a while back trying to get out of the dependency of MyQ - while it works nicely as a backup, it breaks with automation. here is how: i have two people in my household (me and my wife) with automation rules to open and close the garage based on geolocation. with the MyQ app and the code brbeaird@ wrote the commands sent to the controller are specific to open/close so if both me and my wife are leaving at the same time (in the same car) the app will send two "close" commands to the controller which will simply close it, whereas with the workaround it will simulate two clicks on the remote which in affect will leave the garage door open (one will try to close yet the other will open it). same will happen when we come back: the app send two "open" commands which will open the garage while the workaround will simulate two clicks resulting in the garage door wither closed or stopped midway.

looking forward too see a fix from brbeaird@

@wtblock
Copy link

wtblock commented Aug 31, 2021

i have done the same a while back trying to get out of the dependency of MyQ - while it works nicely as a backup, it breaks with automation. here is how: i have two people in my household (me and my wife) with automation rules to open and close the garage based on geolocation. with the MyQ app and the code brbeaird@ wrote the commands sent to the controller are specific to open/close so if both me and my wife are leaving at the same time (in the same car) the app will send two "close" commands to the controller which will simply close it, whereas with the workaround it will simulate two clicks on the remote which in affect will leave the garage door open (one will try to close yet the other will open it). same will happen when we come back: the app send two "open" commands which will open the garage while the workaround will simulate two clicks resulting in the garage door wither closed or stopped midway.

looking forward too see a fix from brbeaird@

oferbarzakai@ I appreciate your concern but there is a simple workaround by creating two virtual switches ("Gate Open" and "Gate Close" in my case) using the WebCore smart app in SmartThings to create conditions based on the gate's sensor status where the virtual switches will open or close the gate.

Here is a screen shot of my WebCore piston:

image

Instead of having your automation activate the "Gate Remote" directly, it would only need to activate "Gate Open" or "Gate Close" and if either one of them are activated twice, the second activation will have no effect.

@oferbarzakai
Copy link

i have done the same a while back trying to get out of the dependency of MyQ - while it works nicely as a backup, it breaks with automation. here is how: i have two people in my household (me and my wife) with automation rules to open and close the garage based on geolocation. with the MyQ app and the code brbeaird@ wrote the commands sent to the controller are specific to open/close so if both me and my wife are leaving at the same time (in the same car) the app will send two "close" commands to the controller which will simply close it, whereas with the workaround it will simulate two clicks on the remote which in affect will leave the garage door open (one will try to close yet the other will open it). same will happen when we come back: the app send two "open" commands which will open the garage while the workaround will simulate two clicks resulting in the garage door wither closed or stopped midway.
looking forward too see a fix from brbeaird@

oferbarzakai@ I appreciate your concern but there is a simple workaround by creating two virtual switches ("Gate Open" and "Gate Close" in my case) using the WebCore smart app in SmartThings to create conditions based on the gate's sensor status where the virtual switches will open or close the gate.

Here is a screen shot of my WebCore piston:

image

Instead of having your automation activate the "Gate Remote" directly, it would only need to activate "Gate Open" or "Gate Close" and if either one of them are activated twice, the second activation will have no effect.

nicely done! i will have to give it a try ;)

@oferbarzakai
Copy link

oferbarzakai commented Sep 1, 2021

i have done the same a while back trying to get out of the dependency of MyQ - while it works nicely as a backup, it breaks with automation. here is how: i have two people in my household (me and my wife) with automation rules to open and close the garage based on geolocation. with the MyQ app and the code brbeaird@ wrote the commands sent to the controller are specific to open/close so if both me and my wife are leaving at the same time (in the same car) the app will send two "close" commands to the controller which will simply close it, whereas with the workaround it will simulate two clicks on the remote which in affect will leave the garage door open (one will try to close yet the other will open it). same will happen when we come back: the app send two "open" commands which will open the garage while the workaround will simulate two clicks resulting in the garage door wither closed or stopped midway.
looking forward too see a fix from brbeaird@

oferbarzakai@ I appreciate your concern but there is a simple workaround by creating two virtual switches ("Gate Open" and "Gate Close" in my case) using the WebCore smart app in SmartThings to create conditions based on the gate's sensor status where the virtual switches will open or close the gate.

Here is a screen shot of my WebCore piston:

image

Instead of having your automation activate the "Gate Remote" directly, it would only need to activate "Gate Open" or "Gate Close" and if either one of them are activated twice, the second activation will have no effect.

i am testing this but i do not i follow you explanation for "if either one of them are activated twice, the second activation will have no effect." - why is that? say both me and my wife leaving the house, there will be two event that trigger "Close Gate" while the sensor is reporting "Open" hence there will be two "clicks" on the remote no? which will result in the door either open or stopped half way. what am i missing?

@wtblock
Copy link

wtblock commented Sep 1, 2021

i am testing this but i do not i follow you explanation for "if either one of them are activated twice, the second activation will have no effect." - why is that? say both me and my wife leaving the house, there will be two event that trigger "Close Gate" while the sensor is reporting "Open" hence there will be two "clicks" on the remote no? which will result in the door either open or stopped half way. what am i missing?

I have been looking at this and there is a problem with just looking for a "switch changes", but instead should be "physically changes to on" which would prevent the second phone from activating it again. After a delay, the code needs to reset the switch to the off state but I am still looking at the best way to do that. Another issue is the sensor changes immediately when opening, but does not change on closing until the gate is fully closed. Hopefully I will post a better version soon.

@oferbarzakai
Copy link

i am testing this but i do not i follow you explanation for "if either one of them are activated twice, the second activation will have no effect." - why is that? say both me and my wife leaving the house, there will be two event that trigger "Close Gate" while the sensor is reporting "Open" hence there will be two "clicks" on the remote no? which will result in the door either open or stopped half way. what am i missing?

I have been looking at this and there is a problem with just looking for a "switch changes", but instead should be "physically changes to on" which would prevent the second phone from activating it again. After a delay, the code needs to reset the switch to the off state but I am still looking at the best way to do that. Another issue is the sensor changes immediately when opening, but does not change on closing until the gate is fully closed. Hopefully I will post a better version soon.

FWIW - here is a piston i created that does the needful. i would still go back to use brbeaird@ code and the MyQ APIs as it is much more elegant and simple.

image

@wtblock
Copy link

wtblock commented Sep 1, 2021

i am testing this but i do not i follow you explanation for "if either one of them are activated twice, the second activation will have no effect." - why is that? say both me and my wife leaving the house, there will be two event that trigger "Close Gate" while the sensor is reporting "Open" hence there will be two "clicks" on the remote no? which will result in the door either open or stopped half way. what am i missing?

I have been looking at this and there is a problem with just looking for a "switch changes", but instead should be "physically changes to on" which would prevent the second phone from activating it again. After a delay, the code needs to reset the switch to the off state but I am still looking at the best way to do that. Another issue is the sensor changes immediately when opening, but does not change on closing until the gate is fully closed. Hopefully I will post a better version soon.

FWIW - here is a piston i created that does the needful. i would still go back to use brbeaird@ code and the MyQ APIs as it is much more elegant and simple.

image

@oferbarzakai My goal was a little different, I wanted the device to work with Alexa which required the virtual switches to be returned to the off state as soon as the gate stopped moving. This would also prevent either of the virtual switches from being activated a second time until the gate motion stops.

So most of the following code was to track the movement of the gate:

image

@PhillySports
Copy link

Sorry if this is off topic and if it needs to be moved elsewhere or deleted, I understand, but I feel like this is as good at time and place as any others to ask the question. Unless I a misunderstanding what will happen when SmartThings shuts down Groovy & WebCore (which perhaps, hopefully, I am), but even if brbeaird is able to get this working again, isn't this just a VERY temporary fix at this point - in the sense that by year end, this -- along with all other custom DTH and Smartapps are going to stop functioning?

Perhaps this is a good time and excuse to switch over to HomeAssistant? Am I missing something? Is there a chance that EXISTING smartapps and custom handlers will continue to work? But as I understand it, this is NOT the case.

@brbeaird
Copy link
Owner

brbeaird commented Sep 1, 2021

Unless I a misunderstanding what will happen when SmartThings shuts down Groovy & WebCore (which perhaps, hopefully, I am), but even if brbeaird is able to get this working again, isn't this just a VERY temporary fix

It's worth mentioning, yes. This effort is likely the last hurrah for the SmartApp, and I'm not exactly sure how long it will even be in place. If we're lucky, maybe the groovy shutdown will get pushed out for a bit. At that point, I do plan to rewrite this whole thing to use the new SmartThings API smartapp flow. The tough part is that it will not be quite as simple as it is today where people can copy/paste the code into a web-hosted IDE. But for anyone who is willing to try, I will document the steps needed to get it running. Regardless, there is no way I'm going to back to having to manually hit my garage door every time I need it to open 😄

@drfant
Copy link

drfant commented Sep 14, 2021

@brbeaird , I am a little confused about this thread and what is actually needed, from a software and hardware perspective. It seem to me that in order to get the MYQ garage door to work correctly with Hubitat now, is to get the Smartthings App and install the MYQ app for Smartthings. Then I could integrate Smartthings to Hubitat to get the automations to work again in Hubitat. Is that correct? Is a new hub required for Smartthings to work correctly or will the MyQ App work without it? It also seemed that in order to integrate Smartthings to Hubitat that they specified a Smarthings Hub i sneeded? I understand that I would need a new tilt sensor on the door to know whether the door is open or not, correct? Is there a cost to Smartthings App and use? Could you possibly make this a bit clearer for me?

@hsingh2005
Copy link

@brbeaird I don't have access to a computer. Any workaround to get the tokens just using an Android phone or cheomebook? Thanks.

@vilayanur
Copy link

hi there I am not a coder...simply a paster of code. what is the myQ token value that needs to be assigned. I cut and pasted the latest version of the code into my smartapp and resinstalled it but I am getting a network error. Login error: null

@mikex99
Copy link

mikex99 commented Sep 15, 2021

hi there I am not a coder...simply a paster of code. what is the myQ token value that needs to be assigned. I cut and pasted the latest version of the code into my smartapp and resinstalled it but I am getting a network error. Login error: null

You have to download the token generator app. The link to it is listed in the install instructions. You can find the app here:
https://github.com/brbeaird/SmartThings_MyQ/tree/master/myQTokenGenerator

@nbtc971
Copy link

nbtc971 commented Sep 17, 2021

In the process of updating brbeaird's code (adding token), I was having issues and ended up completely removing the MyQ link to Google Assistant. Little did I know that there has been an ongoing issue with MyQ not being listed as "Works with Google" and I am unable to get voice to work. I swear, was working for a couple of years and now this..

@wtblock
Copy link

wtblock commented Sep 18, 2021

@brbeaird I went through the process by using the Community Installer as you recommended.

I generated the access token using the Windows utility and copied to clipboard.

In the IDE I clicked on the link to edit the MyQ Lite settings.

When I click on the Settings link in the MyQ app, I get the following output:

image

Any idea why I am not getting the edit boxes to enter the OAuth token?

@drfant
Copy link

drfant commented Sep 18, 2021

@brbeaird , I am a little confused about this thread and what is actually needed, from a software and hardware perspective. It seem to me that in order to get the MYQ garage door to work correctly with Hubitat now, is to get the Smartthings App and install the MYQ app for Smartthings. Then I could integrate Smartthings to Hubitat to get the automations to work again in Hubitat. Is that correct? Is a new hub required for Smartthings to work correctly or will the MyQ App work without it? It also seemed that in order to integrate Smartthings to Hubitat that they specified a Smarthings Hub i sneeded? I understand that I would need a new tilt sensor on the door to know whether the door is open or not, correct? Is there a cost to Smartthings App and use? Could you possibly make this a bit clearer for me?

@TTVert
Copy link

TTVert commented Sep 18, 2021

@brbeaird I went through the process by using the Community Installer as you recommended.

I generated the access token using the Windows utility and copied to clipboard.

In the IDE I clicked on the link to edit the MyQ Lite settings.

When I click on the Settings link in the MyQ app, I get the following output:

image

Any idea why I am not getting the edit boxes to enter the OAuth token?

Update and publish the newest smart app in IDE also.

@wtblock
Copy link

wtblock commented Sep 18, 2021

@TTVert thanks, I will give it a try.

I thought the Community Installer installed the latest code. IDE does not indicate more up-to-date code is available.

@TTVert
Copy link

TTVert commented Sep 19, 2021

I cannot comment on that as I manually update. I should probably set that up. II also didn't see the token option so I compared the code from what I had vs. what the new code was and it was different.

@wtblock
Copy link

wtblock commented Sep 19, 2021

I cannot comment on that as I manually update. I should probably set that up. II also didn't see the token option so I compared the code from what I had vs. what the new code was and it was different.

I copied the code that was generated by the Community Installer to a text file on my computer and I created another text file from the master source on GitHub and then compared the two files. They are identical.

So it appears I have everything I need, but it still does not allow me to enter the OAuth token.

@TTVert
Copy link

TTVert commented Sep 19, 2021

Double check your smart app is exactly this and them make sure to publish it.

`/**


  • ------ SMART APP ------

  • MyQ Lite
  • Copyright 2021 Jason Mok/Brian Beaird/Barry Burke/RBoy Apps
  • Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
  • in compliance with the License. You may obtain a copy of the License at:
  •  http://www.apache.org/licenses/LICENSE-2.0
    
  • Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
  • on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
  • for the specific language governing permissions and limitations under the License.

*/
include 'asynchttp_v1'

String appVersion() { return "4.0.1" }
String appModified() { return "2021-09-12"}
String appAuthor() { return "Brian Beaird" }
String gitBranch() { return "brbeaird" }
String getAppImg(imgName) { return "https://raw.githubusercontent.com/${gitBranch()}/SmartThings_MyQ/master/icons/$imgName" }

definition(
name: "MyQ Lite",
namespace: "brbeaird",
author: "Jason Mok/Brian Beaird/Barry Burke",
description: "Integrate MyQ with Smartthings",
category: "SmartThings Labs",
iconUrl: "https://raw.githubusercontent.com/brbeaird/SmartThings_MyQ/master/icons/myq.png",
iconX2Url: "https://raw.githubusercontent.com/brbeaird/SmartThings_MyQ/master/icons/myq@2x.png",
iconX3Url: "https://raw.githubusercontent.com/brbeaird/SmartThings_MyQ/master/icons/myq@3x.png"
)

appSetting "MyQToken"

preferences {
page(name: "mainPage", title: "MyQ Lite")
page(name: "loginResultPage", title: "MyQ")
page(name: "prefListDevices", title: "MyQ")
page(name: "sensorPage", title: "MyQ")
page(name: "noDoorsSelected", title: "MyQ")
page(name: "summary", title: "MyQ")
page(name: "prefUninstall", title: "MyQ")
}

def appInfoSect(sect=true) {
def str = ""
str += "${app?.name} (v${appVersion()})"
str += "\nAuthor: ${appAuthor()}"
section() { paragraph str, image: getAppImg("myq@2x.png") }
}

def mainPage() {
if (state.previousVersion == null){
state.previousVersion = 0;
}

if (!state.oauth)
	state.oauth = [:]

//Brand new install (need to grab version info)
if (!state.latestVersion){
	getVersionInfo(0, 0)
    state.currentVersion = [:]
    state.currentVersion['SmartApp'] = appVersion()
}
//Version updated
else{
    getVersionInfo(state.previousVersion, appVersion())
    state.previousVersion = appVersion()
}

 state.lastPage = "mainPage"

dynamicPage(name: "mainPage", nextPage: "", uninstall: false, install: true) {
    appInfoSect()
    def devs = refreshChildren()
    def refreshMinutesAgo = state.oauth?.lastRefresh ? (now() - state.oauth?.lastRefresh) / 1000 / 60 : 0
    def lastRefresh = state.oauth?.lastRefresh ? "Last refresh: ${Math.round(refreshMinutesAgo)} minutes ago." : "(not yet refreshed)"
    def loginMessage = "Token loaded. ${lastRefresh}"


    if (!appSettings.MyQToken || appSettings.MyQToken == "")
    	loginMessage = "Missing MyQToken in app settings. Login to the IDE and add it."

    section("MyQ Account"){
        paragraph title: "", "Auth status: ${loginMessage}"
    }
    section("Connected Devices") {
    	paragraph title: "", "${devs?.size() ? devs?.join("\n") : "No MyQ Devices Connected"}"
        href "prefListDevices", title: "", description: "Tap to modify devices"
        input "prefDoorErrorNotify", "bool", required: false, defaultValue: true, title: "Notify on door command errors"
    }
    section("App and Handler Versions"){
        state.currentVersion.each { device, version ->
        	paragraph title: "", "${device} ${version} (${versionCompare(device)})"
        }
        href(name: "Release notes", title: "Release notes",
         required: false,
         url: "https://github.com/${gitBranch()}/SmartThings_MyQ/blob/master/CHANGELOG.md")
        input "prefUpdateNotify", "bool", required: false, title: "Notify when new version is available"
    }
    section("Uninstall") {
        paragraph "Tap below to completely uninstall this SmartApp and devices (doors and lamp control devices will be force-removed from automations and SmartApps)"
        href(name: "", title: "",  description: "Tap to Uninstall", required: false, page: "prefUninstall")
    }
}

}

def versionCompare(deviceName){
if (!state.currentVersion || !state.latestVersion || state.latestVersion == [:]){
return 'latest'
}
if (state.currentVersion[deviceName] == state.latestVersion[deviceName]){
return 'latest'
}
else{
return "${state.latestVersion[deviceName]} available"
}
}

def refreshChildren(){
def useSensors = 0
def useButtons = 0
state.currentVersion = [:]
state.currentVersion['SmartApp'] = appVersion()
def devices = []
childDevices.each { child ->
def myQId = child.getMyQDeviceId() ? "ID: ${child.getMyQDeviceId()}" : 'Missing MyQ ID'
def devName = child.name
if (child.typeName == "MyQ Garage Door Opener"){
devName = devName + " (${child.currentContact}) ${myQId}"
state.currentVersion['DoorDevice'] = child.showVersion()
useSensors = 1
}
else if (child.typeName == "MyQ Garage Door Opener-NoSensor"){
devName = devName + " (No sensor) ${myQId}"
state.currentVersion['DoorDeviceNoSensor'] = child.showVersion()
}
else if (child.typeName == "MyQ Light Controller"){
devName = devName + " (${child.currentSwitch}) ${myQId}"
state.currentVersion['LightDevice'] = child.showVersion()
}
else if (child.typeName == "Virtual Switch"){
useButtons = 1
}
else{
return
}
devices.push(devName)
}
state.useSensors = useSensors
state.useButtons = useButtons
return devices
}

/* Preferences */
def prefUninstall() {
log.debug "Removing MyQ Devices..."
def msg = ""
childDevices.each {
try{
deleteChildDevice(it.deviceNetworkId, true)
msg = "Devices have been removed. Tap the three dots in the top right and then Delete to complete the process."

	}
	catch (e) {
		log.debug "Error deleting ${it.deviceNetworkId}: ${e}"
        msg = "There was a problem removing your device(s). Check the IDE logs for details."
	}
}

return dynamicPage(name: "prefUninstall",  title: "Uninstall", install:false, uninstall:true) {
    section("Uninstallation"){
		paragraph msg
	}
}

}

def getDeviceSelectionList(deviceType){
def testing
}

def prefListDevices() {
state.lastPage = "prefListDevices"
if (login()) {
getMyQDevices()

    state.useSensors = 0
    state.doorList = [:]
    state.lightList = [:]
    state.MyQDataPending.each { id, device ->
    	if (device.typeName == 'door'){
        	state.doorList[id] = device.name
        }
        else if (device.typeName == 'light'){
        	state.lightList[id] = device.name
        }
    }

	if ((state.doorList) || (state.lightList)){
    	def nextPage = "sensorPage"
        if (!state.doorList){nextPage = "summary"}  //Skip to summary if there are no doors to handle
            return dynamicPage(name: "prefListDevices",  title: "Devices", nextPage:nextPage, install:false, uninstall:false) {
                if (state.doorList) {
                    section("Select which garage door/gate to use"){
                        input(name: "doors", type: "enum", required:false, multiple:true, metadata:[values:state.doorList])
                    }
                }
                if (state.lightList) {
                    section("Select which lights to use"){
                        input(name: "lights", type: "enum", required:false, multiple:true, metadata:[values:state.lightList])
                    }
                }
                section("Advanced (optional)", hideable: true, hidden:true){
    	            paragraph "BETA: Enable the below option if you would like to force the Garage Doors to behave as Door Locks (sensor required)." +
                    			"This may be desirable if you only want doors to open up via PIN with Alexa voice commands. " +
                                "Note this is still considered highly experimental and may break many other automations/apps that need the garage door capability."
    	            input "prefUseLockType", "bool", required: false, title: "Create garage doors as door locks?"
				}
            }

    }else {
		return dynamicPage(name: "prefListDevices",  title: "Error!", install:false, uninstall:true) {
			section(""){
				paragraph "Could not find any supported device(s). Please report to author about these devices: " +  state.unsupportedList
			}
		}
	}
} else {
	return dynamicPage(name: "prefListDevices",  title: "Error!", install:false, uninstall:true) {
			section(""){
				paragraph "Login error: ${state.loginError}"
			}
		}
}

}

def sensorPage() {

//If MyQ ID changes, the old stale ID will still be listed in the settings array. Let's get a clean count of valid doors selected
state.validatedDoors = []
if (doors instanceof List && doors.size() > 1){
    doors.each {
        if (state.MyQDataPending[it] != null){
            state.validatedDoors.add(it)
        }
    }
}
else{
	state.validatedDoors = doors	//Handle single door
}

return dynamicPage(name: "sensorPage",  title: "Optional Sensors and Push Buttons", nextPage:"summary", install:false, uninstall:false) {
    def sensorCounter = 1
    state.validatedDoors.each{ door ->
        section("Setup options for " + state.MyQDataPending[door].name){
            input "door${sensorCounter}Sensor",  "capability.contactSensor", required: false, multiple: false, title: state.MyQDataPending[door].name + " Contact Sensor"
            input "prefDoor${sensorCounter}PushButtons", "bool", required: false, title: "Create separate on/off switches?"
        }
        sensorCounter++
        state.useSensors = 1
    }
    section("Sensor setup"){
    	paragraph "For each door above, you can specify an optional sensor that allows the device type to know whether the door is open or closed. This helps the device function as a switch " +
        	"you can turn on (to open) and off (to close) in other automations and SmartApps."
       	paragraph "Alternatively, you can choose the other option below to have separate additional opener and closer switch devices created. This is recommened if you have no sensors but still want a way to open/close the " +
        "garage from SmartTiles and other interfaces like Google Home that can't function with the built-in open/close capability. See wiki for more details"
    }
}

}

def summary() {
state.installMsg = ""
try{
initialize()
}

//If error thrown during initialize, try to get the line number and display on installation summary page
catch (e){
	def errorLine = "unknown"
    try{
    	log.debug e.stackTrace
        def pattern = ( e.stackTrace =~ /groovy.(\d+)./   )
        errorLine = pattern[0][1]
    }
    catch(lineError){}

	log.debug "Error at line number ${errorLine}: ${e}"
    state.installMsg = "There was a problem updating devices:\n ${e}.\nLine number: ${errorLine}\nLast successful step: ${state.lastSuccessfulStep}"
}

return dynamicPage(name: "summary",  title: "Summary", install:true, uninstall:true) {
    section("Installation Details:"){
		paragraph state.installMsg
	}
}

}

/* Initialization */
def installed() {
}

def updated() {
log.debug "MyQ Lite changes saved."
unschedule()
runEvery3Hours(updateVersionInfo) //Check for new version every 3 hours

if (state.useSensors == 1 && state.validatedDoors){
	refreshAll()
	runEvery30Minutes(refreshAll)
}
stateCleanup()

}

/* Version Checking */

//Called from scheduler every 3 hours
def updateVersionInfo(){
getVersionInfo('versionCheck', appVersion())
}

//Get latest versions for SmartApp and Device Handlers
def getVersionInfo(oldVersion, newVersion){
//Don't check for updates more 5 minutes

if (state.lastVersionCheck && oldVersion == newVersion && (now() - state.lastVersionCheck) / 1000/60 < 5 ){
	return
}
state.lastVersionCheck = now()
log.info "Checking for latest version..."
def params = [
    uri:  'http://www.brbeaird.com/getVersion',
    contentType: 'application/json',
    body: [
    	app: "myq",
        platform: "ST",
        prevVersion: oldVersion,
        currentVersion: newVersion,
    	sensor: state.useSensors == 1 ? true : false,
        door: state.validatedDoors?.size(),
        lock: prefUseLock ? true: false,
        light: state.validatedLights?.size(),
        button: state.useButtons
    ]
]
def callbackMethod = oldVersion == 'versionCheck' ? 'updateCheck' : 'handleVersionUpdateResponse'
asynchttp_v1.post(callbackMethod, params)

}

//When version response received (async), update state with the data
def handleVersionUpdateResponse(response, data) {
if (response.hasError() || !response.json?.SmartApp) {
log.error "Error getting version info: ${response.errorMessage}"
state.latestVersion = [:]
}
else {state.latestVersion = response.json}
}

//In case of periodic update check, also refresh installed versions and update the version warning message
def updateCheck(response, data) {
handleVersionUpdateResponse(response,data)
refreshChildren()
updateVersionMessage()
}

def updateVersionMessage(){
state.versionMsg = ""
state.currentVersion.each { device, version ->
if (versionCompare(device) != 'latest'){
state.versionMsg = "MyQ Lite Updates are available."
}
}

//Notify if updates are available
if (state.versionMsg != ""){
    sendNotificationEvent(state.versionMsg)

    //Send push notification if enabled
    if (prefUpdateNotify){

        //Don't notify if we've sent a notification within the last 1 day
        if (state.lastVersionNotification){
        	def timeSinceLastNotification = (now() - state.lastVersionNotification) / 1000
            if (timeSinceLastNotification < 60*60*23){
            	return
            }
        }
        sendPush(state.versionMsg)
        state.lastVersionNotification = now()
	}
}

}

def uninstall(){
log.debug "Removing MyQ Devices..."
childDevices.each {
try{
deleteChildDevice(it.deviceNetworkId, true)
}
catch (e) {
log.debug "Error deleting ${it.deviceNetworkId}: ${e}"
}
}
}

def uninstalled() {
log.debug "MyQ removal complete."
getVersionInfo(state.previousVersion, 0);
}

def initialize() {

log.debug "Initializing..."
state.data = state.MyQDataPending
state.lastSuccessfulStep = ""
unsubscribe()

//Check existing installed devices against MyQ data
verifyChildDeviceIds()

//Mark sensors onto state door data
def doorSensorCounter = 1
state.validatedDoors.each{ door ->
    if (settings["door${doorSensorCounter}Sensor"]){
        state.data[door].sensor = "door${doorSensorCounter}Sensor"
    }
    doorSensorCounter++
}
state.lastSuccessfulStep = "Sensor Indexing"

//Create door devices
def doorCounter = 1
state.validatedDoors.each{ door ->
    createChilDevices(door, settings[state.data[door].sensor], state.data[door].name, settings["prefDoor${doorCounter}PushButtons"])
    doorCounter++
}
state.lastSuccessfulStep = "Door device creation"


//Create light devices
if (lights){
    state.validatedLights = []
    if (lights instanceof List && lights.size() > 1){
        lights.each { lightId ->
            if (state.data[lightId] != null){
                state.validatedLights.add(lightId)
            }
        }
    }
    else{
        state.validatedLights = lights
    }
    state.validatedLights.each { light ->
        if (light){
            def myQAccountId = state.data[light].myQAccountId
            def myQDeviceId = state.data[light].myQDeviceId
            def DNI = [ app.id, "LightController", myQDeviceId ].join('|')
            def lightName = state.data[light].name
            def childLight = getChildDevice(state.data[light].child)

            if (!childLight) {
                log.debug "Creating child light device: " + light

                try{
                    childLight = addChildDevice("brbeaird", "MyQ Light Controller", DNI, getHubID(), ["name": lightName])
                    state.data[myQDeviceId].child = DNI
                    state.installMsg = state.installMsg + lightName + ": created light device. \r\n\r\n"
                }
                catch(physicalgraph.app.exception.UnknownDeviceTypeException e)
                {
                    log.debug "Error! " + e
                    state.installMsg = state.installMsg + lightName + ": problem creating light device. Check your IDE to make sure the brbeaird : MyQ Light Controller device handler is installed and published. \r\n\r\n"
                }
            }
            else{
                log.debug "Light device already exists: " + lightName
                state.installMsg = state.installMsg + lightName + ": light device already exists. \r\n\r\n"
            }
            log.debug "Setting ${lightName} status to ${state.data[light].status}"
            childLight.updateDeviceStatus(state.data[light].status)
            childLight.updateMyQDeviceId(myQDeviceId, myQAccountId)
        }
    }
    state.lastSuccessfulStep = "Light device creation"
}

// Remove unselected devices
getChildDevices().each{ child ->
	log.debug "Checking ${child} for deletion"
    def myQDeviceId = child.getMyQDeviceId()
    if (myQDeviceId){
    	if (!(myQDeviceId in state.validatedDoors) && !(myQDeviceId in state.validatedLights)){
        	try{
            	log.debug "Child ${child} with ID ${myQDeviceId} not found in selected list. Deleting."
                deleteChildDevice(child.deviceNetworkId, true)
            	log.debug "Removed old device: ${child}"
                state.installMsg = state.installMsg + "Removed old device: ${child} \r\n\r\n"
            }
            catch (e)
            {
                sendPush("Warning: unable to delete device: ${child}. You'll need to manually remove it.")
                log.debug "Error trying to delete device: ${child} - ${e}"
                log.debug "Device is likely in use in a Routine, or SmartApp (make sure and check Alexa, ActionTiles, etc.)."
            }
        }
    }
}
state.lastSuccessfulStep = "Old device removal"

//Set initial values
if (state.validatedDoors){
	syncDoorsWithSensors()
}
state.lastSuccessfulStep = "Setting initial values"

//Subscribe to sensor events
settings.each{ key, val->
    if (key.contains('Sensor')){
    	subscribe(val, "contact", sensorHandler)
    }
}

}

def verifyChildDeviceIds(){
//Try to match existing child devices with latest MyQ data
childDevices.each { child ->
def matchingId
if (child.typeName != 'Virtual Switch'){
//Look for a matching entry in MyQ
state.data.each { myQId, myQData ->
if (child.getMyQDeviceId() == myQId){
log.debug "Found matching ID for ${child}"
matchingId = myQId
}

            //If no matching ID, try to match on name
            else if (child.name == myQData.name || child.label == myQData.name){
                log.debug "Found matching ID (via name) for ${child}"
                child.updateMyQDeviceId(myQId, myQData.myQAccountId)	//Update child to new ID
                matchingId = myQId
            }
        }

        log.debug "final matchingid for ${child.name} ${matchingId}"
        if (matchingId){
            state.data[matchingId].child = child.deviceNetworkId
        }
        else{
            log.debug "WARNING: Existing child ${child} does not seem to have a valid MyQID"
        }
    }
}

}

def createChilDevices(door, sensor, doorName, prefPushButtons){
def sensorTypeName = "MyQ Garage Door Opener"
def noSensorTypeName = "MyQ Garage Door Opener-NoSensor"
def lockTypeName = "MyQ Lock Door"

if (door){

	def myQDeviceId = state.data[door].myQDeviceId
    def myQAccountId = state.data[door].myQAccountId
    def DNI = [ app.id, "GarageDoorOpener", myQDeviceId ].join('|')

    //Has door's child device already been created?
    def existingDev = getChildDevice(state.data[door].child)
    def existingType = existingDev?.typeName

    if (existingDev){
    	log.debug "Child already exists for " + doorName + ". Sensor name is: " + sensor
        state.installMsg = state.installMsg + doorName + ": door device already exists. \r\n\r\n"
        existingDev.updateMyQDeviceId(myQDeviceId, myQAccountId)

        if (prefUseLockType && existingType != lockTypeName){
            try{
                log.debug "Type needs updating to Lock version"
                existingDev.deviceType = lockTypeName
                state.installMsg = state.installMsg + doorName + ": changed door device to lock version." + "\r\n\r\n"
            }
            catch(physicalgraph.exception.NotFoundException e)
            {
                log.debug "Error! " + e
                state.installMsg = state.installMsg + doorName + ": problem changing door to no-sensor type. Check your IDE to make sure the brbeaird : " + lockTypeName + " device handler is installed and published. \r\n\r\n"
            }
        }
        else if ((!sensor) && existingType != noSensorTypeName){
        	try{
                log.debug "Type needs updating to no-sensor version"
                existingDev.deviceType = noSensorTypeName
                state.installMsg = state.installMsg + doorName + ": changed door device to No-sensor version." + "\r\n\r\n"
            }
            catch(physicalgraph.exception.NotFoundException e)
            {
                log.debug "Error! " + e
                state.installMsg = state.installMsg + doorName + ": problem changing door to no-sensor type. Check your IDE to make sure the brbeaird : " + noSensorTypeName + " device handler is installed and published. \r\n\r\n"
            }
        }

        else if (sensor && existingType != sensorTypeName && !prefUseLockType){
        	try{
                log.debug "Type needs updating to sensor version"
                existingDev.deviceType = sensorTypeName
                state.installMsg = state.installMsg + doorName + ": changed door device to sensor version." + "\r\n\r\n"
            }
            catch(physicalgraph.exception.NotFoundException e)
            {
                log.debug "Error! " + e
                state.installMsg = state.installMsg + doorName + ": problem changing door to sensor type. Check your IDE to make sure the brbeaird : " + sensorTypeName + " device handler is installed and published. \r\n\r\n"
            }
        }
    }
    else{
        log.debug "Creating child door device " + door
        def childDoor

        if (prefUseLockType){
            try{
                log.debug "Creating door with lock type"
                childDoor = addChildDevice("brbeaird", lockTypeName, DNI, getHubID(), ["name": doorName])
                childDoor.updateMyQDeviceId(myQDeviceId, myQAccountId)
                state.installMsg = state.installMsg + doorName + ": created lock device \r\n\r\n"
            }
            catch(physicalgraph.app.exception.UnknownDeviceTypeException e)
            {
                log.debug "Error! " + e
                state.installMsg = state.installMsg + doorName + ": problem creating door device (lock type). Check your IDE to make sure the brbeaird : " + sensorTypeName + " device handler is installed and published. \r\n\r\n"

            }
        }

        else if (sensor){
            try{
                log.debug "Creating door with sensor"
                childDoor = addChildDevice("brbeaird", sensorTypeName, DNI, getHubID(), ["name": doorName])
                childDoor.updateMyQDeviceId(myQDeviceId, myQAccountId)
                state.installMsg = state.installMsg + doorName + ": created door device (sensor version) \r\n\r\n"
            }
            catch(physicalgraph.app.exception.UnknownDeviceTypeException e)
            {
                log.debug "Error! " + e
                state.installMsg = state.installMsg + doorName + ": problem creating door device (sensor type). Check your IDE to make sure the brbeaird : " + sensorTypeName + " device handler is installed and published. \r\n\r\n"

            }
        }
        else{
            try{
                log.debug "Creating door with no sensor"
                childDoor = addChildDevice("brbeaird", noSensorTypeName, DNI, getHubID(), ["name": doorName])
                childDoor.updateMyQDeviceId(myQDeviceId, myQAccountId)
                state.installMsg = state.installMsg + doorName + ": created door device (no-sensor version) \r\n\r\n"
            }
            catch(physicalgraph.app.exception.UnknownDeviceTypeException e)
            {
                log.debug "Error! " + e
                state.installMsg = state.installMsg + doorName + ": problem creating door device (no-sensor type). Check your IDE to make sure the brbeaird : " + noSensorTypeName + " device handler is installed and published. \r\n\r\n"
            }
        }
        state.data[door].child = childDoor.deviceNetworkId
    }

    //Create push button devices
    if (prefPushButtons){
    	def existingOpenButtonDev = getChildDevice(door + " Opener")
        def existingCloseButtonDev = getChildDevice(door + " Closer")
        if (!existingOpenButtonDev){
            try{
            	def openButton = addChildDevice("brbeaird", "Virtual Switch", door + " Opener", getHubID(), [name: doorName + " Opener", label: doorName + " Opener"])
                openButton.off()
            	state.installMsg = state.installMsg + doorName + ": created push button device. \r\n\r\n"
            	subscribe(openButton, "switch.on", doorButtonOpenHandler)
            }
            catch(physicalgraph.app.exception.UnknownDeviceTypeException e)
            {
                log.debug "Error! " + e
                state.installMsg = state.installMsg + doorName + ": problem creating virtual switch device. Check your IDE to make sure the brbeaird : Virtual Switch device handler is installed and published. \r\n\r\n"
            }
        }
        else{
        	subscribe(existingOpenButtonDev, "switch.on", doorButtonOpenHandler)
            state.installMsg = state.installMsg + doorName + ": push button device already exists. Subscription recreated. \r\n\r\n"
            log.debug "subscribed to button: " + existingOpenButtonDev
        }

        if (!existingCloseButtonDev){
            try{
                def closeButton = addChildDevice("brbeaird", "Virtual Switch", door + " Closer", getHubID(), [name: doorName + " Closer", label: doorName + " Closer"])
                closeButton.off()
                subscribe(closeButton, "switch.on", doorButtonCloseHandler)
            }
            catch(physicalgraph.app.exception.UnknownDeviceTypeException e)
            {
                log.debug "Error! " + e
            }
        }
        else{
            subscribe(existingCloseButtonDev, "switch.on", doorButtonCloseHandler)
        }
    }

    //Cleanup defunct push button devices if no longer wanted
    else{
    	def pushButtonIDs = [door + " Opener", door + " Closer"]
        def devsToDelete = getChildDevices().findAll { pushButtonIDs.contains(it.deviceNetworkId)}
        log.debug "button devices to delete: " + devsToDelete
		devsToDelete.each{
        	log.debug "deleting button: " + it
            try{
            	deleteChildDevice(it.deviceNetworkId, true)
                state.installMsg = state.installMsg + "Removed ${it}. \r\n\r\n"
            } catch (e){
            	//sendPush("Warning: unable to delete virtual on/off push button - you'll need to manually remove it.")
                state.installMsg = state.installMsg + "Warning: unable to delete virtual on/off push button - you'll need to manually remove it. \r\n\r\n"
                log.debug "Error trying to delete button " + it + " - " + e
                log.debug "Button  is likely in use in a Routine, or SmartApp (make sure and check SmarTiles!)."
            }

        }
    }
}

}

def syncDoorsWithSensors(child){
state.validatedDoors.each { door ->
log.debug "Refreshing ${door} ${state.data[door].child}"
if (state.data[door].sensor){
updateDoorStatus(state.data[door].child, settings[state.data[door].sensor], '', state.data[door].name)
}
}
}

def updateDoorStatus(doorDNI, sensor, child, doorName){
try{
if (!sensor){//If we got here somehow without a sensor, bail out
log.debug "Warning: no sensor found for ${doorDNI}"
return 0}

	if (!doorDNI){
    	log.debug "Invalid doorDNI for sensor ${sensor} ${child}"
        return 0
    }

    //Get door to update and set the new value
    def doorToUpdate = getChildDevice(doorDNI)

    //Get current sensor value
    def currentSensorValue = "unknown"
    currentSensorValue = sensor.latestValue("contact")
    def currentDoorState = doorToUpdate.latestValue("door")
    doorToUpdate.updateSensorBattery(sensor.latestValue("battery"))

    if (currentDoorState != currentSensorValue){
    	log.debug "Updating ${doorName} from ${currentDoorState} to ${currentSensorValue} from sensor ${sensor}"
    }

    doorToUpdate.updateDeviceStatus(currentSensorValue)
    doorToUpdate.updateDeviceSensor("${sensor} is ${currentSensorValue}")

    //Write to child log if this was initiated from one of the doors
    if (child){child.log("Updating as ${currentSensorValue} from sensor ${sensor}")}

    //Get latest activity timestamp for the sensor (data saved for up to a week)
    def latestEvent
    def eventsSinceYesterday = sensor.eventsSince(new Date() - 7)
    def foundContactEvent = 0
    eventsSinceYesterday.each{ event ->
        if (foundContactEvent == 0 && event.name == "contact"){
            latestEvent = event.date
            foundContactEvent = 1
        }
    }

    //Update timestamp
    if (latestEvent){
        doorToUpdate.updateDeviceLastActivity(latestEvent)
    }
    else{	//If the door has been inactive for more than a week, timestamp data will be null. Keep current value in that case.
        log.debug "Door: ${doorName} Null timestamp detected from sensor ${sensor}. Keeping current value."
    }
}catch (e) {
    log.debug "Error updating door: ${doorDNI}: ${e}"
}

}

def refresh(child){
def door = child.device.deviceNetworkId
def doorName = state.data[child.getMyQDeviceId()].name
child.log("refresh called from " + doorName + ' (' + door + ')')
syncDoorsWithSensors(child)
}

def refreshAll(){
syncDoorsWithSensors()
}

def refreshAll(evt){
refreshAll()
}

def sensorHandler(evt) {
log.debug "Sensor change detected: Event name " + evt.name + " value: " + evt.value + " deviceID: " + evt.deviceId

state.validatedDoors.each{ door ->
    if (settings[state.data[door].sensor]?.id == evt.deviceId)
        updateDoorStatus(state.data[door].child, settings[state.data[door].sensor], null, state.data[door].name)
}

}

def doorButtonOpenHandler(evt) {
try{
log.debug "Door open button push detected: Event name " + evt.name + " value: " + evt.value + " deviceID: " + evt.deviceId + " DNI: " + evt.getDevice().deviceNetworkId
evt.getDevice().off()
def myQDeviceId = evt.getDevice().deviceNetworkId.replace(" Opener", "")
def doorDevice = getChildDevice(state.data[myQDeviceId].child)
doorDevice.open()
}catch(e){
def errMsg = "Warning: MyQ Open button command failed - ${e}"
log.error errMsg
}
}

def doorButtonCloseHandler(evt) {
try{
log.debug "Door close button push detected: Event name " + evt.name + " value: " + evt.value + " deviceID: " + evt.deviceId + " DNI: " + evt.getDevice().deviceNetworkId
evt.getDevice().off()
def myQDeviceId = evt.getDevice().deviceNetworkId.replace(" Closer", "")
def doorDevice = getChildDevice(state.data[myQDeviceId].child)
doorDevice.close()
}catch(e){
def errMsg = "Warning: MyQ Close button command failed - ${e}"
log.error errMsg
}
}

def getSelectedDevices( settingsName ) {
def selectedDevices = []
(!settings.get(settingsName))?:((settings.get(settingsName)?.getAt(0)?.size() > 1) ? settings.get(settingsName)?.each { selectedDevices.add(it) } : selectedDevices.add(settings.get(settingsName)))
return selectedDevices
}

/* Access Management */
private login() {
if (!appSettings.MyQToken){
log.warn "Missing refresh token in app settings."
return false
}
if (!state.oauth?.expiration || now() > state?.oauth.expiration){
log.warn "Token has expired. Logging in again."
def refreshToken = appSettings.MyQToken
if (!doLogin(refreshToken)){
return false
}
}
return true
}

private doLogin(refreshToken) {
try {

    if (!state.oauth){
    	state.oauth = [access_token: "", expiration: now() - 10000]
	}

    def tokenBody = [
		"client_id": "IOS_CGI_MYQ",
        "client_secret": "UD4DXnKyPWq25BSw",
        "grant_type": "refresh_token",
        "redirect_uri": "com.myqops://ios",
        "scope": "MyQ_Residential offline_access",
        "refresh_token": appSettings.MyQToken
    ]

    return httpPost([ uri: "https://partner-identity.myq-cloud.com", path: "/connect/token", headers: ["Content-Type": "application/x-www-form-urlencoded", "User-Agent": "null"], body: tokenBody ]) { response ->
        log.debug "Got LOGIN response: STATUS: ${response.status}"
        //log.debug "Got LOGIN POST response: STATUS: ${response.status}\n\nDATA: ${response.data}"
        if (response.status == 200) {
            state.oauth.lastRefresh = now()
            state.oauth.access_token = response.data.access_token
            appSettings.MyQToken = response.data.refresh_token
            state.oauth.expiration = now() + (response.data.expires_in * 1000)
            return true
        } else {
            log.error "Unknown LOGIN POST status: ${response.status} data: ${response.data}"
            state.loginMessage = "${response.status}-${response.data}"
            state.oauth.expiration = now() - 1000
        }
        return false
    }
} catch (e)	{
    log.warn "API POST Error: $e"
}
return false

}

//Get devices listed on your MyQ account
private getMyQDevices() {
state.MyQDataPending = [:]
state.unsupportedList = []

//Get accounts
def accounts = httpGet([ uri: "https://accounts.myq-cloud.com/api/v6.0/accounts", headers: getMyQHeaders()]) { response ->
    return response.data.accounts
}
if (!accounts){
    log.warn "No accounts found."
    return
}

accounts.each { account ->
    log.debug "Getting devices for account ${account.id}"

    def devices = httpGet([ uri: "https://devices.myq-cloud.com/api/v5.2/Accounts/${account.id}/Devices", headers: getMyQHeaders()]) { response ->
        return response.data.items
    }
    devices.each { device ->
        // 2 = garage door, 5 = gate, 7 = MyQGarage(no gateway), 9 = commercial door, 17 = Garage Door Opener WGDO
        //if (device.MyQDeviceTypeId == 2||device.MyQDeviceTypeId == 5||device.MyQDeviceTypeId == 7||device.MyQDeviceTypeId == 17||device.MyQDeviceTypeId == 9) {
        if (device.device_family == "garagedoor") {
            log.debug "Found door: ${device.name}"
            def dni = device.serial_number
            def description = device.name
            def doorState = device.state.door_state
            def updatedTime = device.last_update

            //Ignore any doors with blank descriptions
            if (description != ''){
                log.debug "Got valid door: ${description} type: ${device.device_family} status: ${doorState} type: ${device.device_type}"
                //log.debug "Storing door info: " + description + "type: " + device.device_family + " status: " + doorState +  " type: " + device.device_type
                state.MyQDataPending[dni] = [ status: doorState, lastAction: updatedTime, name: description, typeId: device.MyQDeviceTypeId, typeName: 'door', sensor: '', myQDeviceId: device.serial_number, myQAccountId: account.id]
            }
            else{
                log.debug "Door " + device.MyQDeviceId + " has blank desc field. This is unusual..."
            }
        }

        //Lights
        else if (device.device_family == "lamp") {
            def dni = device.serial_number
            def description = device.name
            def lightState = device.state.lamp_state
            def updatedTime = device.state.last_update

            //Ignore any lights with blank descriptions
            if (description && description != ''){
                log.debug "Got valid light: ${description} type: ${device.device_family} status: ${lightState} type: ${device.device_type}"
                state.MyQDataPending[dni] = [ status: lightState, lastAction: updatedTime, name: description, typeName: 'light', type: device.MyQDeviceTypeId, myQDeviceId: device.serial_number, myQAccountId: account.id ]
            }
        }

        //Unsupported devices
        else{
            state.unsupportedList.add([name: device.name, typeId: device.device_family, typeName: device.device_type])
        }
    }
}

}

def getHubID(){
def hubs = location.hubs.findAll{ it.type == physicalgraph.device.HubType.PHYSICAL }

//Try and find a valid hub on the account
def chosenHub
hubs.each {
    if (it != null){
        chosenHub = it
    }
}

if (chosenHub != null){
    log.debug "Chosen hub for child devices: ${chosenHub} (${chosenHub.id})"
    return chosenHub.id
}
else{
    log.debug "No physical hubs found. Sending NULL"
    return null
}

}

import groovy.transform.Field

@field final MAX_RETRIES = 1 // Retry count before giving up

private getMyQHeaders() {
return [
"Authorization": "Bearer ${state.oauth.access_token}"
]
}

// HTTP PUT call (Send commands)
private apiPut(apiPath, apiBody = [], actionText = "") {
if (!login()){
log.error "Unable to complete PUT, login failed"
sendNotificationEvent("Warning: MyQ command failed due to bad login.")
//if (prefDoorErrorNotify){sendPush("Warning: MyQ command failed due to bad login.")}
return false
}
try {
//log.debug "Calling out PUT ${apiPath}${getMyQHeaders()}"
return httpPut([ uri: apiPath, headers: getMyQHeaders()]) { response ->
if (response.status != 200 && response.status != 204 && response.status != 202) {
log.warn "Unexpected command response - ${response.status} ${response.data}"
}
return true;
}
} catch (e) {
if (e.response.data?.description == "Device already in desired state."){
log.debug "Device already in desired state. Command ignored."
return true
}
sendNotificationEvent("Warning: MyQ command failed - ${e.response.status}")
if (prefDoorErrorNotify){sendPush("Warning: MyQ command failed for ${actionText} - ${e}")}
return false
}
}

def sendDoorCommand(myQDeviceId, myQAccountId, command) {
if (!myQAccountId){
myQAccountId = state.session.accountId //Bandaid for people who haven't tapped through the modify menu yet to assign accountId to door device
}
state.lastCommandSent = now()
return apiPut("https://account-devices-gdo.myq-cloud.com/api/v5.2/Accounts/${myQAccountId}/door_openers/${myQDeviceId}/${command}")
return true
}

def sendLampCommand(myQDeviceId, myQAccountId, command) {
state.lastCommandSent = now()
return apiPut("https://account-devices-lamp.myq-cloud.com/api/v5.2/Accounts/${myQAccountId}/lamps/${myQDeviceId}/${command}")
}

//Transition for people who have not yet clicked through "modify devices" steps
def getDefaultAccountId(){
return state.session.accountId
}

//Remove old unused pieces of state
def stateCleanup(){
if (state.latestDoorNoSensorVersion){state.remove('latestDoorNoSensorVersion')}
if (state.latestDoorVersion){state.remove('latestDoorVersion')}
if (state.latestLightVersion){state.remove('latestLightVersion')}
if (state.latestSmartAppVersion){state.remove('latestSmartAppVersion')}
if (state.thisDoorNoSensorVersion){state.remove('thisDoorNoSensorVersion')}
if (state.thisDoorVersion){state.remove('thisDoorVersion')}
if (state.thisLightVersion){state.remove('thisLightVersion')}
if (state.thisSmartAppVersion){state.remove('thisSmartAppVersion')}
if (state.versionWarning){state.remove('versionWarning')}
if (state.polling){state.remove('polling')}
}

//Available to be called from child devices for special logging
def notify(message){
sendNotificationEvent(message)
}`

After this refresh the IDE page and look at smart app settings and you should have this:
image

@wtblock
Copy link

wtblock commented Sep 19, 2021

@TTVert I really appreciate the effort you have made to help me.

I tried to compare the code you sent with the GitHub master (which matches my code), but the Wiki seems to have modified the code to the extent they are virtually impossible to compare. I am using a Windows application called WinMerge:

image

All of the yellow indicates places where the code does not compare (not counting whitespace).

You can see from the bars on the left edge, that there is almost no comparison from top to bottom.

When I do the same comparison with the code brought in with the Community Installer and Brian's master repo code in GitHub, the comparisons are identical:

image

Again, I really appreciate your effort to help. Thank you very much.

@TTVert
Copy link

TTVert commented Sep 19, 2021 via email

@wtblock
Copy link

wtblock commented Sep 19, 2021

@TTVert thanks. Here is a link to a folder containing the code I am using:

https://1drv.ms/u/s!ArOGPLJSgdz0ga_UGkbFdhOT1HgO1SA?e=ACqHwm

The link is to a One Drive folder containing my IDE source and the GitHub source:

image

Thanks again.

@TTVert
Copy link

TTVert commented Sep 19, 2021 via email

@wtblock
Copy link

wtblock commented Sep 19, 2021

Well that matches mine exactly and as you said both of yours also match. With that said, are you 100% sure you saved and published? If you don’t publish it will not reflect the changes you’ve made. Failing that I’m out of ideas. Perhaps the page you are trying to add the token is cached? Try inprivate or a different browser yet? I do not believe I had to update my device handlers for this, just the smart app to allow insertion of the token. Dave

Here is a screen shot from the IDE:

image

The Community Installer does that automatically.

Thanks again for checking this out. At least I know I am not doing something stupid 🤔

@TTVert
Copy link

TTVert commented Sep 19, 2021

ok so when you go to edit properties | settings of the smart app you do not see this right? Humor me and try an InPrivate window to elevate a cached page.

image

@wtblock
Copy link

wtblock commented Sep 19, 2021

@TTVert I tried an InPrivate window and switched from the Edge browser to Chrome and all three do the same thing:

image

I can't explain it.

Thanks again!

@TTVert
Copy link

TTVert commented Sep 19, 2021

And again, you for sure saved/PUBLISHED right? I wonder if something is different for those who auto update than myself who manually updates the code. That is exactly what mine showed before I saved/published. I'll have to defer to Brian on this one.

@wtblock
Copy link

wtblock commented Sep 19, 2021

And again, you for sure saved/PUBLISHED right? I wonder if something is different for those who auto update than myself who manually updates the code. That is exactly what mine showed before I saved/published. I'll have to defer to Brian on this one.

Well damn, I tried doing another Save and Publish and now it works!

Thank you so much for sticking with me.

And yes I do feel a little stupid 🙄

@wtblock
Copy link

wtblock commented Sep 19, 2021

@brbeaird I just got the device working again and I really appreciate your work.

@TTVert thank you too for helping me figure out why the OAuth token settings were not working.

@michaelheinen79
Copy link

@brbeaird ; First, great job on this project! Happy to donate! 2nd, I had all of this setup before last month, and had both of my garage doors independently working. For some reason now, I can't get the MyQLite app to modify the second door. It sees both doors in the MyQ account, but when I modify the second, it simply removes the first.

I think my problem is my handlers may not be setup correctly for a second door this time. Do you have any guidance?

@TTVert
Copy link

TTVert commented Sep 19, 2021

And again, you for sure saved/PUBLISHED right? I wonder if something is different for those who auto update than myself who manually updates the code. That is exactly what mine showed before I saved/published. I'll have to defer to Brian on this one.

Well damn, I tried doing another Save and Publish and now it works!

Thank you so much for sticking with me.

And yes I do feel a little stupid 🙄

Figured it was something like that. Glad it fixed it. @brbeaird, thanks so much for this, it's great. While I'm here, does anyone have an any idea how to add a delay w/ IFTTT? I have IFTTT pro and when I say a command it opens my garage doors and also lowers the screen sin two commands in the same applet. Here is what it is below. The connected product just moves both screens down and the switch on is just turning on the momentary garage door switches. I know apillio supposedly does this and i just downloaded it and am playing but I'd love any input anyone here may have.

image

@capritzl
Copy link

I am very confused. All I want is for the Hubitat app to work. I am hoping to eliminate the reliance on SmartThings.

What are the steps required for Hubitat?

@capritzl
Copy link

@brbeaird , I am a little confused about this thread and what is actually needed, from a software and hardware perspective. It seem to me that in order to get the MYQ garage door to work correctly with Hubitat now, is to get the Smartthings App and install the MYQ app for Smartthings. Then I could integrate Smartthings to Hubitat to get the automations to work again in Hubitat. Is that correct? Is a new hub required for Smartthings to work correctly or will the MyQ App work without it? It also seemed that in order to integrate Smartthings to Hubitat that they specified a Smarthings Hub i sneeded? I understand that I would need a new tilt sensor on the door to know whether the door is open or not, correct? Is there a cost to Smartthings App and use? Could you possibly make this a bit clearer for me?

I agree. All I want are the steps for Hubitat.

@brbeaird
Copy link
Owner

All I want are the steps for Hubitat.

To be clear: these steps (and this repository as a whole) are not designed for use with Hubitat. I understand there is a modified version of this SmartApp that has been in use with Hubitat for awhile but would need to be updated. That is something I may help with eventually, but I do not actually use Hubitat and do not know when I'll get around to figuring out what would need to be added or if it even makes sense given how SmartThings has already begun a large divergence from Hubitat as far as custom device type handlers and the fact that there is no realistic way for me to support issues on Hubitat as well.

@drfant
Copy link

drfant commented Sep 20, 2021 via email

@phaldor8
Copy link

phaldor8 commented Sep 20, 2021 via email

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests