mirror of
https://github.com/mtan93/SmartThingsPublic.git
synced 2026-03-08 05:31:56 +00:00
Compare commits
1 Commits
PROD_2017.
...
MSA-1097-1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
57c09bc402 |
370
smartapps/copy-ninja/myq-connect.src/myq-connect.groovy
Normal file
370
smartapps/copy-ninja/myq-connect.src/myq-connect.groovy
Normal file
@@ -0,0 +1,370 @@
|
||||
/**
|
||||
* MyQ (Connect)
|
||||
*
|
||||
* Copyright 2015 Jason Mok
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* Last Updated : 03/08/2016
|
||||
*
|
||||
*/
|
||||
definition(
|
||||
name: "MyQ (Connect)",
|
||||
namespace: "copy-ninja",
|
||||
author: "Jason Mok",
|
||||
description: "Connect MyQ to control your devices",
|
||||
category: "SmartThings Labs",
|
||||
iconUrl: "http://smartthings.copyninja.net/icons/MyQ@1x.png",
|
||||
iconX2Url: "http://smartthings.copyninja.net/icons/MyQ@2x.png",
|
||||
iconX3Url: "http://smartthings.copyninja.net/icons/MyQ@3x.png"
|
||||
)
|
||||
|
||||
preferences {
|
||||
page(name: "prefLogIn", title: "MyQ")
|
||||
page(name: "prefListDevices", title: "MyQ")
|
||||
}
|
||||
|
||||
/* Preferences */
|
||||
def prefLogIn() {
|
||||
def showUninstall = username != null && password != null
|
||||
return dynamicPage(name: "prefLogIn", title: "Connect to MyQ", nextPage:"prefListDevices", uninstall:showUninstall, install: false) {
|
||||
section("Login Credentials"){
|
||||
input("username", "email", title: "Username", description: "MyQ Username (email address)")
|
||||
input("password", "password", title: "Password", description: "MyQ password")
|
||||
}
|
||||
section("Gateway Brand"){
|
||||
input(name: "brand", title: "Gateway Brand", type: "enum", metadata:[values:["Liftmaster","Chamberlain","Craftsman"]] )
|
||||
}
|
||||
section("Advanced Options"){
|
||||
//input(name: "polling", title: "Server Polling (in Minutes)", type: "int", description: "in minutes", defaultValue: "5" )
|
||||
input(name: "contactSensorTrigger", title: "Contact Sensor to trigger refresh ", type: "capability.contactSensor", required: "false", multiple: "true")
|
||||
input(name: "accelerationSensorTrigger", title: "Acceleration Sensor to trigger refresh ", type: "capability.accelerationSensor", required: "false", multiple: "true")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def prefListDevices() {
|
||||
if (forceLogin()) {
|
||||
def doorList = getDoorList()
|
||||
def lightList = getLightList()
|
||||
if ((doorList) || (lightList)) {
|
||||
return dynamicPage(name: "prefListDevices", title: "Devices", install:true, uninstall:true) {
|
||||
if (doorList) {
|
||||
section("Select which garage door/gate to use"){
|
||||
input(name: "doors", type: "enum", required:false, multiple:true, metadata:[values:doorList])
|
||||
}
|
||||
}
|
||||
if (lightList) {
|
||||
section("Select which light controller to use"){
|
||||
input(name: "lights", type: "enum", required:false, multiple:true, metadata:[values:lightList])
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
def devList = getDeviceList()
|
||||
return dynamicPage(name: "prefListDevices", title: "Error!", install:true, uninstall:true) {
|
||||
section(""){
|
||||
paragraph "Could not find any supported device(s). Please report to author about these devices: " + devList
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return dynamicPage(name: "prefListDevices", title: "Error!", install:false, uninstall:true) {
|
||||
section(""){
|
||||
paragraph "The username or password you entered is incorrect. Try again. "
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Initialization */
|
||||
def installed() { initialize() }
|
||||
|
||||
def updated() {
|
||||
unsubscribe()
|
||||
initialize()
|
||||
}
|
||||
|
||||
def uninstalled() {
|
||||
unsubscribe()
|
||||
unschedule()
|
||||
getAllChildDevices().each { deleteChildDevice(it.deviceNetworkId) }
|
||||
}
|
||||
|
||||
def initialize() {
|
||||
login()
|
||||
|
||||
// Get initial device status in state.data
|
||||
state.polling = [ last: 0, rescheduler: now() ]
|
||||
state.data = [:]
|
||||
|
||||
// Create selected devices
|
||||
def doorsList = getDoorList()
|
||||
def lightsList = getLightList()
|
||||
def selectedDevices = [] + getSelectedDevices("doors") + getSelectedDevices("lights")
|
||||
|
||||
selectedDevices.each {
|
||||
if (!getChildDevice(it)) {
|
||||
if (it.contains("GarageDoorOpener")) { addChildDevice("copy-ninja", "MyQ Garage Door Opener", it, null, ["name": "MyQ: " + doorsList[it]]) }
|
||||
if (it.contains("LightController")) { addChildDevice("copy-ninja", "MyQ Light Controller", it, null, ["name": "MyQ: " + lightsList[it]]) }
|
||||
}
|
||||
}
|
||||
|
||||
// Remove unselected devices
|
||||
def deleteDevices = (selectedDevices) ? (getChildDevices().findAll { !selectedDevices.contains(it.deviceNetworkId) }) : getAllChildDevices()
|
||||
deleteDevices.each { deleteChildDevice(it.deviceNetworkId) }
|
||||
|
||||
|
||||
//Subscribes to sunrise and sunset event to trigger refreshes
|
||||
subscribe(location, "sunrise", runRefresh)
|
||||
subscribe(location, "sunset", runRefresh)
|
||||
subscribe(location, "mode", runRefresh)
|
||||
subscribe(location, "sunriseTime", runRefresh)
|
||||
subscribe(location, "sunsetTime", runRefresh)
|
||||
|
||||
//Subscribe to events from contact sensor
|
||||
if (settings.contactSensorTrigger) {
|
||||
subscribe(settings.contactSensorTrigger, "contact", runRefresh)
|
||||
}
|
||||
|
||||
//Subscribe to events from contact sensor
|
||||
if (settings.accelerationSensorTrigger) {
|
||||
subscribe(settings.accelerationSensorTrigger, "acceleration", runRefresh)
|
||||
}
|
||||
|
||||
// Run refresh after installation
|
||||
runRefresh()
|
||||
}
|
||||
|
||||
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 forceLogin() {
|
||||
//Reset token and expiry
|
||||
state.session = [ brandID: 0, brandName: settings.brand, securityToken: null, expiration: 0 ]
|
||||
state.polling = [ last: 0, rescheduler: now() ]
|
||||
state.data = [:]
|
||||
return doLogin()
|
||||
}
|
||||
|
||||
private login() { return (!(state.session.expiration > now())) ? doLogin() : true }
|
||||
|
||||
private doLogin() {
|
||||
apiGet("/api/user/validate", [username: settings.username, password: settings.password] ) { response ->
|
||||
if (response.status == 200) {
|
||||
if (response.data.SecurityToken != null) {
|
||||
state.session.brandID = response.data.BrandId
|
||||
state.session.brandName = response.data.BrandName
|
||||
state.session.securityToken = response.data.SecurityToken
|
||||
state.session.expiration = now() + 150000
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Listing all the garage doors you have in MyQ
|
||||
private getDoorList() {
|
||||
def deviceList = [:]
|
||||
apiGet("/api/v4/userdevicedetails/get", []) { response ->
|
||||
if (response.status == 200) {
|
||||
response.data.Devices.each { device ->
|
||||
// 2 = garage door, 5 = gate, 7 = MyQGarage(no gateway), 17 = Garage Door Opener WGDO
|
||||
if (device.MyQDeviceTypeId == 2||device.MyQDeviceTypeId == 5||device.MyQDeviceTypeId == 7||device.MyQDeviceTypeId == 17) {
|
||||
def dni = [ app.id, "GarageDoorOpener", device.MyQDeviceId ].join('|')
|
||||
device.Attributes.each {
|
||||
if (it.AttributeDisplayName=="desc") deviceList[dni] = it.Value
|
||||
if (it.AttributeDisplayName=="doorstate") {
|
||||
state.data[dni] = [ status: it.Value, lastAction: it.UpdatedTime ]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return deviceList
|
||||
}
|
||||
|
||||
// Listing all the light controller you have in MyQ
|
||||
private getLightList() {
|
||||
def deviceList = [:]
|
||||
apiGet("/api/v4/userdevicedetails/get", []) { response ->
|
||||
if (response.status == 200) {
|
||||
response.data.Devices.each { device ->
|
||||
if (device.MyQDeviceTypeId == 3) {
|
||||
def dni = [ app.id, "LightController", device.MyQDeviceId ].join('|')
|
||||
device.Attributes.each {
|
||||
if (it.AttributeDisplayName=="desc") { deviceList[dni] = it.Value }
|
||||
if (it.AttributeDisplayName=="lightstate") { state.data[dni] = [ status: it.Value ] }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return deviceList
|
||||
}
|
||||
|
||||
private getDeviceList() {
|
||||
def deviceList = []
|
||||
apiGet("/api/v4/userdevicedetails/get", []) { response ->
|
||||
if (response.status == 200) {
|
||||
response.data.Devices.each { device ->
|
||||
log.debug "MyQDeviceTypeId : " + device.MyQDeviceTypeId.toString()
|
||||
if (!(device.MyQDeviceTypeId == 1||device.MyQDeviceTypeId == 2||device.MyQDeviceTypeId == 3||device.MyQDeviceTypeId == 5||device.MyQDeviceTypeId == 7)) {
|
||||
deviceList.add( device.MyQDeviceTypeId.toString() + "|" + device.TypeID )
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return deviceList
|
||||
}
|
||||
|
||||
/* api connection */
|
||||
// get URL
|
||||
private getApiURL() {
|
||||
if (settings.brand == "Craftsman") {
|
||||
return "https://craftexternal.myqdevice.com"
|
||||
} else {
|
||||
return "https://myqexternal.myqdevice.com"
|
||||
}
|
||||
}
|
||||
|
||||
private getApiAppID() {
|
||||
if (settings.brand == "Craftsman") {
|
||||
return "QH5AzY8MurrilYsbcG1f6eMTffMCm3cIEyZaSdK/TD/8SvlKAWUAmodIqa5VqVAs"
|
||||
} else {
|
||||
return "JVM/G9Nwih5BwKgNCjLxiFUQxQijAebyyg8QUHr7JOrP+tuPb8iHfRHKwTmDzHOu"
|
||||
}
|
||||
}
|
||||
|
||||
// HTTP GET call
|
||||
private apiGet(apiPath, apiQuery = [], callback = {}) {
|
||||
// set up query
|
||||
apiQuery = [ appId: getApiAppID() ] + apiQuery
|
||||
if (state.session.securityToken) { apiQuery = apiQuery + [SecurityToken: state.session.securityToken ] }
|
||||
|
||||
try {
|
||||
httpGet([ uri: getApiURL(), path: apiPath, query: apiQuery ]) { response -> callback(response) }
|
||||
} catch (SocketException e) {
|
||||
log.debug "API Error: $e"
|
||||
}
|
||||
}
|
||||
|
||||
// HTTP PUT call
|
||||
private apiPut(apiPath, apiBody = [], callback = {}) {
|
||||
// set up body
|
||||
apiBody = [ ApplicationId: getApiAppID() ] + apiBody
|
||||
if (state.session.securityToken) { apiBody = apiBody + [SecurityToken: state.session.securityToken ] }
|
||||
|
||||
// set up query
|
||||
def apiQuery = [ appId: getApiAppID() ]
|
||||
if (state.session.securityToken) { apiQuery = apiQuery + [SecurityToken: state.session.securityToken ] }
|
||||
|
||||
try {
|
||||
httpPut([ uri: getApiURL(), path: apiPath, contentType: "application/json; charset=utf-8", body: apiBody, query: apiQuery ]) { response -> callback(response) }
|
||||
} catch (SocketException e) {
|
||||
log.debug "API Error: $e"
|
||||
}
|
||||
}
|
||||
|
||||
// Updates data for devices
|
||||
private updateDeviceData() {
|
||||
// automatically checks if the token has expired, if so login again
|
||||
if (login()) {
|
||||
// set polling states
|
||||
state.polling["last"] = now()
|
||||
|
||||
// Get all the door information, updated to state.data
|
||||
return (getDoorList()||getLightList())? true : false
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/* for SmartDevice to call */
|
||||
// Refresh data
|
||||
def refresh() {
|
||||
//force devices to poll to get the latest status
|
||||
if (updateDeviceData()) {
|
||||
log.info "Refreshing data..."
|
||||
// get all the children and send updates
|
||||
getAllChildDevices().each {
|
||||
//log.debug "Polling " + it.deviceNetworkId
|
||||
it.updateDeviceStatus(state.data[it.deviceNetworkId].status)
|
||||
if (it.deviceNetworkId.contains("GarageDoorOpener")) {
|
||||
it.updateDeviceLastActivity(state.data[it.deviceNetworkId].lastAction.toLong())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//schedule the rescheduler to schedule refresh ;)
|
||||
if ((state.polling["rescheduler"]?:0) + 2400000 < now()) {
|
||||
log.info "Scheduling Auto Rescheduler.."
|
||||
runEvery30Minutes(runRefresh)
|
||||
state.polling["rescheduler"] = now()
|
||||
}
|
||||
}
|
||||
|
||||
// Get Device ID
|
||||
def getChildDeviceID(child) {
|
||||
return child.device.deviceNetworkId.split("\\|")[2]
|
||||
}
|
||||
|
||||
// Get single device status
|
||||
def getDeviceStatus(child) {
|
||||
return state.data[child.device.deviceNetworkId].status
|
||||
}
|
||||
|
||||
// Get single device last activity
|
||||
def getDeviceLastActivity(child) {
|
||||
return state.data[child.device.deviceNetworkId].lastAction.toLong()
|
||||
}
|
||||
|
||||
// Send command to start or stop
|
||||
def sendCommand(child, attributeName, attributeValue) {
|
||||
if (login()) {
|
||||
//Send command
|
||||
apiPut("/api/v4/deviceattribute/putdeviceattribute", [ MyQDeviceId: getChildDeviceID(child), AttributeName: attributeName, AttributeValue: attributeValue ])
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
def runRefresh(evt) {
|
||||
if (evt) {
|
||||
log.info "Event " + evt.displayName + " triggered refresh"
|
||||
runIn(30, delayedRefresh) //schedule a refresh
|
||||
}
|
||||
log.info "Last refresh was " + ((now() - state.polling["last"])/60000) + " minutes ago"
|
||||
// Reschedule if didn't update for more than 5 minutes plus specified polling
|
||||
//if ((((state.polling["last"]?:0) + ((((!settings.polling)?:(settings.polling.toInteger() > 0 )? settings.polling.toInteger() : 5)) * 60000) + 300000) < now()) && canSchedule()) {
|
||||
if ((((state.polling["last"]?:0) + 600000) < now()) && canSchedule()) {
|
||||
log.info "Scheduling Auto Refresh.."
|
||||
//schedule("* */" + ((settings.polling.toInteger() > 0 )? settings.polling.toInteger() : 5) + " * * * ?", refresh)
|
||||
schedule("* */5 * * * ?", refresh)
|
||||
}
|
||||
|
||||
// Force Refresh NOWWW!!!!
|
||||
refresh()
|
||||
|
||||
if (!evt) state.polling["rescheduler"] = now() //Update rescheduler's last run
|
||||
}
|
||||
|
||||
def delayedRefresh() {
|
||||
log.info "Delayed refresh triggered"
|
||||
refresh()
|
||||
}
|
||||
Reference in New Issue
Block a user