Compare commits

..

3 Commits

Author SHA1 Message Date
John Vanacore
a3b8dffadd MSA-1646: Gate/Garage opener 2016-12-10 07:49:27 -08:00
Vinay Rao
0a82077b24 Merge pull request #1519 from SmartThingsCommunity/staging
Rolling down staging hotfix to master
2016-12-07 14:15:16 -08:00
Vinay Rao
bbad6dfa7a Merge pull request #1501 from SmartThingsCommunity/staging
Rolling down staging hotfix to master
2016-11-28 10:35:24 -08:00
12 changed files with 615 additions and 92 deletions

View File

@@ -99,7 +99,7 @@ def parse(String description) {
def poll() {
def refreshCmds = [
"st wattr 0x${device.deviceNetworkId} 1 8 0x10 0x21 {${state?.dOnOff ?: '0000'}}", "delay 2000"
"st wattr 0x${device.deviceNetworkId} 1 8 0x10 0x21 {${state?.dOnOff ?: '0000'}}", "delay 500"
]
return refreshCmds + zigbee.onOffRefresh() + zigbee.levelRefresh()
@@ -197,7 +197,7 @@ def off() {
def refresh() {
def refreshCmds = [
"st wattr 0x${device.deviceNetworkId} 1 8 0x10 0x21 {${state?.dOnOff ?: '0000'}}", "delay 2000"
"st wattr 0x${device.deviceNetworkId} 1 8 0x10 0x21 {${state?.dOnOff ?: '0000'}}", "delay 500"
]
return refreshCmds + zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.onOffConfig()

View File

@@ -128,9 +128,9 @@ def setLevel(value) {
def refresh() {
[
"st rattr 0x${device.deviceNetworkId} ${endpointId} 6 0", "delay 2000",
"st rattr 0x${device.deviceNetworkId} ${endpointId} 8 0", "delay 2000",
"st rattr 0x${device.deviceNetworkId} ${endpointId} 0x0B04 0x050B", "delay 2000"
"st rattr 0x${device.deviceNetworkId} ${endpointId} 6 0", "delay 500",
"st rattr 0x${device.deviceNetworkId} ${endpointId} 8 0", "delay 500",
"st rattr 0x${device.deviceNetworkId} ${endpointId} 0x0B04 0x050B", "delay 500"
]
}
@@ -313,9 +313,9 @@ def isDescriptionPower(descMap) {
def onOffConfig() {
[
"zdo bind 0x${device.deviceNetworkId} 1 ${endpointId} 6 {${device.zigbeeId}} {}", "delay 2000",
"zcl global send-me-a-report 6 0 0x10 0 600 {01}", "delay 200",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 2000"
"zdo bind 0x${device.deviceNetworkId} 1 ${endpointId} 6 {${device.zigbeeId}} {}", "delay 200",
"zcl global send-me-a-report 6 0 0x10 0 600 {01}",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 1500"
]
}
@@ -323,9 +323,9 @@ def onOffConfig() {
//min level change is 01
def levelConfig() {
[
"zdo bind 0x${device.deviceNetworkId} 1 ${endpointId} 8 {${device.zigbeeId}} {}", "delay 2000",
"zcl global send-me-a-report 8 0 0x20 5 3600 {01}", "delay 200",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 2000"
"zdo bind 0x${device.deviceNetworkId} 1 ${endpointId} 8 {${device.zigbeeId}} {}", "delay 200",
"zcl global send-me-a-report 8 0 0x20 5 3600 {01}",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 1500"
]
}
@@ -333,10 +333,9 @@ def levelConfig() {
//min change in value is 05
def powerConfig() {
[
"zdo bind 0x${device.deviceNetworkId} 1 ${endpointId} 0x0B04 {${device.zigbeeId}} {}", "delay 2000",
"zdo bind 0x${device.deviceNetworkId} 1 ${endpointId} 0x0B04 {${device.zigbeeId}} {}", "delay 200",
"zcl global send-me-a-report 0x0B04 0x050B 0x29 1 600 {05 00}", //The send-me-a-report is custom to the attribute type for CentraLite
"delay 200",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 2000"
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500"
]
}
@@ -345,10 +344,7 @@ def setLevelWithRate(level, rate) {
rate = "0000"
}
level = convertToHexString(level * 255 / 100) //Converting the 0-100 range to 0-FF range in hex
[
"st cmd 0x${device.deviceNetworkId} ${endpointId} 8 4 {$level $rate}",
"delay 2000"
]
"st cmd 0x${device.deviceNetworkId} ${endpointId} 8 4 {$level $rate}"
}
String convertToHexString(value, width=2) {

View File

@@ -51,7 +51,7 @@ def on() {
'zcl on-off on',
'delay 200',
"send 0x${zigbee.deviceNetworkId} 0x01 0x${zigbee.endpointId}",
'delay 2000'
'delay 500'
]
@@ -62,6 +62,6 @@ def off() {
'zcl on-off off',
'delay 200',
"send 0x${zigbee.deviceNetworkId} 0x01 0x${zigbee.endpointId}",
'delay 2000'
'delay 500'
]
}

View File

@@ -157,10 +157,9 @@ def configure() {
//min change in value is 01
def powerConfig() {
[
"zdo bind 0x${device.deviceNetworkId} 1 ${endpointId} 0x0B04 {${device.zigbeeId}} {}", "delay 2000",
"zdo bind 0x${device.deviceNetworkId} 1 ${endpointId} 0x0B04 {${device.zigbeeId}} {}", "delay 200",
"zcl global send-me-a-report 0x0B04 0x050B 0x29 1 600 {05 00}", //The send-me-a-report is custom to the attribute type for CentraLite
"delay 200",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 2000"
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500"
]
}

View File

@@ -29,10 +29,9 @@ metadata {
command "enrollResponse"
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3315-S", deviceJoinName: "Water Leak Sensor"
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3315"
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3315-Seu", deviceJoinName: "Water Leak Sensor"
fingerprint inClusters: "0000,0001,0003,0020,0402,0500,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3315-L", deviceJoinName: "Iris Smart Water Sensor"
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3315-S", deviceJoinName: "Water Leak Sensor"
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3315"
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3315-Seu", deviceJoinName: "Water Leak Sensor"
fingerprint inClusters: "0000,0001,0003,000F,0020,0402,0500", outClusters: "0019", manufacturer: "SmartThings", model: "moisturev4", deviceJoinName: "Water Leak Sensor"
}
@@ -286,8 +285,8 @@ def ping() {
def refresh() {
log.debug "Refreshing Temperature and Battery"
def refreshCmds = [
"st rattr 0x${device.deviceNetworkId} 1 0x402 0", "delay 2000",
"st rattr 0x${device.deviceNetworkId} 1 1 0x20", "delay 2000"
"st rattr 0x${device.deviceNetworkId} 1 0x402 0", "delay 200",
"st rattr 0x${device.deviceNetworkId} 1 1 0x20", "delay 200"
]
return refreshCmds + enrollResponse()
@@ -309,10 +308,10 @@ def enrollResponse() {
[
//Resending the CIE in case the enroll request is sent before CIE is written
"zcl global write 0x500 0x10 0xf0 {${zigbeeEui}}", "delay 200",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 2000",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
//Enroll Response
"raw 0x500 {01 23 00 00 00}", "delay 200",
"send 0x${device.deviceNetworkId} 1 1", "delay 2000"
"raw 0x500 {01 23 00 00 00}",
"send 0x${device.deviceNetworkId} 1 1", "delay 200"
]
}

View File

@@ -298,8 +298,8 @@ def ping() {
def refresh() {
log.debug "refresh called"
def refreshCmds = [
"st rattr 0x${device.deviceNetworkId} 1 0x402 0", "delay 2000",
"st rattr 0x${device.deviceNetworkId} 1 1 0x20", "delay 2000"
"st rattr 0x${device.deviceNetworkId} 1 0x402 0", "delay 200",
"st rattr 0x${device.deviceNetworkId} 1 1 0x20", "delay 200"
]
return refreshCmds + enrollResponse()
@@ -321,10 +321,10 @@ def enrollResponse() {
[
//Resending the CIE in case the enroll request is sent before CIE is written
"zcl global write 0x500 0x10 0xf0 {${zigbeeEui}}", "delay 200",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 2000",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
//Enroll Response
"raw 0x500 {01 23 00 00 00}", "delay 200",
"send 0x${device.deviceNetworkId} 1 1", "delay 2000"
"raw 0x500 {01 23 00 00 00}",
"send 0x${device.deviceNetworkId} 1 1", "delay 200"
]
}

View File

@@ -428,10 +428,10 @@ def enrollResponse() {
[
//Resending the CIE in case the enroll request is sent before CIE is written
"zcl global write 0x500 0x10 0xf0 {${zigbeeEui}}", "delay 200",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 2000",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
//Enroll Response
"raw 0x500 {01 23 00 00 00}", "delay 200",
"send 0x${device.deviceNetworkId} 1 1", "delay 2000"
"send 0x${device.deviceNetworkId} 1 1", "delay 200"
]
}

View File

@@ -252,8 +252,8 @@ def ping() {
def refresh() {
log.debug "Refreshing Temperature and Battery"
def refreshCmds = [
"st rattr 0x${device.deviceNetworkId} 1 0x402 0", "delay 2000",
"st rattr 0x${device.deviceNetworkId} 1 1 0x20", "delay 2000"
"st rattr 0x${device.deviceNetworkId} 1 0x402 0", "delay 200",
"st rattr 0x${device.deviceNetworkId} 1 1 0x20", "delay 200"
]
return refreshCmds + enrollResponse()
@@ -277,10 +277,10 @@ def enrollResponse() {
[
//Resending the CIE in case the enroll request is sent before CIE is written
"zcl global write 0x500 0x10 0xf0 {${zigbeeEui}}", "delay 200",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 2000",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
//Enroll Response
"raw 0x500 {01 23 00 00 00}", "delay 200",
"send 0x${device.deviceNetworkId} 1 1", "delay 2000"
"raw 0x500 {01 23 00 00 00}",
"send 0x${device.deviceNetworkId} 1 1", "delay 200"
]
}

View File

@@ -270,9 +270,9 @@ def configure() {
log.debug "Configuring Reporting and Bindings."
def humidityConfigCmds = [
"zdo bind 0x${device.deviceNetworkId} 1 1 0xFC45 {${device.zigbeeId}} {}", "delay 2000",
"zcl global send-me-a-report 0xFC45 0 0x29 30 3600 {6400}", "delay 200",
"send 0x${device.deviceNetworkId} 1 1", "delay 2000"
"zdo bind 0x${device.deviceNetworkId} 1 1 0xFC45 {${device.zigbeeId}} {}", "delay 500",
"zcl global send-me-a-report 0xFC45 0 0x29 30 3600 {6400}",
"send 0x${device.deviceNetworkId} 1 1", "delay 500"
]
// temperature minReportTime 30 seconds, maxReportTime 5 min. Reporting interval if no activity

View File

@@ -28,8 +28,8 @@ metadata {
fingerprint inClusters: "0000, 0001, 0003, 0020, 0402, 0B05", outClusters: "0003, 0006, 0008, 0019", manufacturer: "OSRAM", model: "LIGHTIFY Dimming Switch", deviceJoinName: "OSRAM LIGHTIFY Dimming Switch"
//fingerprint inClusters: "0000, 0001, 0003, 0020, 0500", outClusters: "0003,0019", manufacturer: "CentraLite", model: "3455-L", deviceJoinName: "Iris Care Pendant"
fingerprint inClusters: "0000, 0001, 0003, 0007, 0020, 0402, 0B05", outClusters: "0003, 0006, 0019", manufacturer: "CentraLite", model: "3460-L", deviceJoinName: "Iris Smart Button"
fingerprint inClusters: "0000, 0001, 0003, 0007, 0020, 0B05", outClusters: "0003, 0006, 0019", manufacturer: "CentraLite", model:"3450-L", deviceJoinName: "Iris KeyFob"
//fingerprint inClusters: "0000, 0001, 0003, 0007, 0020, 0402, 0B05", outClusters: "0003, 0006, 0019", manufacturer: "CentraLite", model: "3460-L", deviceJoinName: "Iris Smart Button"
//fingerprint inClusters: "0000, 0001, 0003, 0007, 0020, 0B05", outClusters: "0003, 0006, 0019", manufacturer: "CentraLite", model:"3450-L", deviceJoinName: "Iris KeyFob"
}
simulator {}

View File

@@ -0,0 +1,536 @@
/**
* MyQ Lite
*
* Copyright 2015 Jason Mok/Brian Beaird/Barry Burke
*
* 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 : 06/14/2016
*
*/
definition(
name: "MyQ Lite",
namespace: "brbeaird",
author: "Jason Mok/Brian Beaird/Barry Burke",
description: "Integrate MyQ with Smartthings",
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")
page(name: "prefSensor1", title: "MyQ")
page(name: "prefSensor2", title: "MyQ")
page(name: "prefSensor3", title: "MyQ")
page(name: "prefSensor4", 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"]] )
}
}
}
def prefListDevices() {
if (forceLogin()) {
def doorList = getDoorList()
if ((doorList)) {
return dynamicPage(name: "prefListDevices", title: "Devices", nextPage:"prefSensor1", install:false, uninstall:true) {
if (doorList) {
section("Select which garage door/gate to use"){
input(name: "doors", type: "enum", required:false, multiple:true, metadata:[values:doorList])
}
}
}
} else {
def devList = getDeviceList()
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: " + devList
}
}
}
} else {
return dynamicPage(name: "prefListDevices", title: "Error!", install:false, uninstall:true) {
section(""){
paragraph "The username or password you entered is incorrect. Try again. "
}
}
}
}
def prefSensor1() {
log.debug "Doors chosen: " + doors
//Set defaults
def nextPage = ""
def showInstall = true
def titleText = ""
//Determine if we have multiple doors and need to send to another page
if (doors instanceof String){ //simulator seems to just make a single door a string. For that reason we have this weird check.
log.debug "Single door detected (string)."
titleText = "Select Sensors for Door 1 (" + state.data[doors].name + ")"
}
else if (doors.size() == 1){
log.debug "Single door detected (array)."
titleText = "Select Sensors for Door 1 (" + state.data[doors[0]].name + ")"
}
else{
log.debug "Multiple doors detected."
nextPage = "prefSensor2"
titleText = "Select Sensors for Door 1 (" + state.data[doors[0]].name + ")"
showInstall = false;
}
return dynamicPage(name: "prefSensor1", title: "Sensors", nextPage:nextPage, install:showInstall, uninstall:true) {
section(titleText){
input(name: "door1Sensor", title: "Contact Sensor", type: "capability.contactSensor", required: true, multiple: false)
input(name: "door1Acceleration", title: "Acceleration Sensor", type: "capability.accelerationSensor", required: false, multiple: false)
}
}
}
def prefSensor2() {
def nextPage = ""
def showInstall = true
def titleText = "Sensors for Door 2 (" + state.data[doors[1]].name + ")"
if (doors.size() > 2){
nextPage = "prefSensor3"
showInstall = false;
}
return dynamicPage(name: "prefSensor2", title: "Sensors", nextPage:nextPage, install:showInstall, uninstall:true) {
section(titleText){
input(name: "door2Sensor", title: "Contact Sensor", type: "capability.contactSensor", required: true, multiple: false)
input(name: "door2Acceleration", title: "Acceleration Sensor", type: "capability.accelerationSensor", required: false, multiple: false)
}
}
}
def prefSensor3() {
def nextPage = ""
def showInstall = true
def titleText = "Sensors for Door 3 (" + state.data[doors[2]].name + ")"
if (doors.size() > 3){
nextPage = "prefSensor4"
showInstall = false;
}
return dynamicPage(name: "prefSensor3", title: "Sensors", nextPage:nextPage, install:showInstall, uninstall:true) {
section(titleText){
input(name: "door3Sensor", title: "Contact Sensor", type: "capability.contactSensor", required: true, multiple: false)
input(name: "door3Acceleration", title: "Acceleration Sensor", type: "capability.accelerationSensor", required: false, multiple: false)
}
}
}
def prefSensor4() {
def titleText = "Contact Sensor for Door 4 (" + state.data[doors[3]].name + ")"
return dynamicPage(name: "prefSensor4", title: "Sensors", install:true, uninstall:true) {
section(titleText){
input(name: "door4Sensor", title: "Contact Sensor", type: "capability.contactSensor", required: "true", multiple: "false")
input(name: "door4Acceleration", title: "Acceleration Sensor", type: "capability.accelerationSensor", required: false, multiple: false)
}
}
}
/* Initialization */
def installed() { initialize() }
def updated() {
unsubscribe()
initialize()
}
def uninstalled() {}
def initialize() {
login()
state.sensorMap = [:]
// 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")
selectedDevices.each {
log.debug "Creating child device: " + it
if (!getChildDevice(it)) {
if (it.contains("GarageDoorOpener")) { addChildDevice("brbeaird", "MyQ Garage Door Opener", it, null, ["name": "MyQLite: " + doorsList[it]]) }
}
}
// Remove unselected devices
def deleteDevices = (selectedDevices) ? (getChildDevices().findAll { !selectedDevices.contains(it.deviceNetworkId) }) : getAllChildDevices()
deleteDevices.each { deleteChildDevice(it.deviceNetworkId) }
//Create subscriptions
if (door1Sensor)
subscribe(door1Sensor, "contact", sensorHandler)
if (door2Sensor)
subscribe(door2Sensor, "contact", sensorHandler)
if (door3Sensor)
subscribe(door3Sensor, "contact", sensorHandler)
if (door4Sensor)
subscribe(door4Sensor, "contact", sensorHandler)
if (door1Acceleration)
subscribe(door1Acceleration, "acceleration", sensorHandler)
if (door2Acceleration)
subscribe(door2Acceleration, "acceleration", sensorHandler)
if (door3Acceleration)
subscribe(door3Acceleration, "acceleration", sensorHandler)
if (door4Acceleration)
subscribe(door4Acceleration, "acceleration", sensorHandler)
//Set initial values
syncDoorsWithSensors()
}
def syncDoorsWithSensors(child){
def firstDoor = doors[0]
//Handle single door (sometimes it's just a dumb string thanks to the simulator)
if (doors instanceof String)
firstDoor = doors
def doorDNI = null
if (child) { // refresh only the requesting door (makes things a bit more efficient if you have more than 1 door
doorDNI = child.device.deviceNetworkId
switch (doorDNI) {
case firstDoor:
updateDoorStatus(firstDoor, door1Sensor, door1Acceleration, door1ThreeAxis, child)
break
case doors[1]:
updateDoorStatus(doors[1], door2Sensor, door2Acceleration, door2ThreeAxis, child)
break
case doors[2]:
updateDoorStatus(doors[2], door3Sensor, door3Acceleration, door3ThreeAxis, child)
break
case doors[3]:
updateDoorStatus(doors[3], door4Sensor, door4Acceleration, door4ThreeAxis, child)
}
} else { // refresh ALL the doors
if (firstDoor) updateDoorStatus(firstDoor, door1Sensor, door1Acceleration, door1ThreeAxis, null)
if (doors[1]) updateDoorStatus(doors[1], door2Sensor, door2Acceleration, door2ThreeAxis, null)
if (doors[2]) updateDoorStatus(doors[2], door3Sensor, door3Acceleration, door3ThreeAxis, null)
if (doors[3]) updateDoorStatus(doors[3], door4Sensor, door4Acceleration, door4ThreeAxis, null)
}
}
def updateDoorStatus(doorDNI, sensor, acceleration, threeAxis, child){
//Get door to update and set the new value
def doorToUpdate = getChildDevice(doorDNI)
def doorName = state.data[doorDNI].name
def value = "unknown"
def moving = "unknown"
def door = doorToUpdate.latestValue("door")
if (acceleration) moving = acceleration.latestValue("acceleration")
if (sensor) value = sensor.latestValue("contact")
if (moving == "active") {
if (value == "open") {
if (door != "opening") value = "closing" else value = "opening" // if door is "open" or "waiting" change to "closing", else it must be "opening"
} else if (value == "closed") {
if (door != "closing") value = "opening" else value = "closed"
}
} else if (moving == "inactive") {
if (door == "closing") {
if (value == "open") { // just stopped but door is still open
value = "stopped"
}
}
}
doorToUpdate.updateDeviceStatus(value)
doorToUpdate.updateDeviceSensor("${sensor} is ${sensor.currentContact}")
log.debug "Door: " + doorName + ": Updating with status - " + value + " - from sensor " + sensor
//Write to child log if this was initiated from one of the doors
if (child)
child.log("Door: " + doorName + ": Updating with status - " + value + " - from sensor " + sensor)
if (acceleration) {
doorToUpdate.updateDeviceMoving("${acceleration} is ${moving}")
log.debug "Door: " + doorName + ": Updating with status - " + moving + " - from sensor " + acceleration
if (child)
child.log("Door: " + doorName + ": Updating with status - " + moving + " - from sensor " + acceleration)
}
//Get latest activity timestamp for the sensor (data saved for up to a week)
def eventsSinceYesterday = sensor.eventsSince(new Date() - 7)
def latestEvent = eventsSinceYesterday[0]?.date
def timeStampLogText = "Door: " + doorName + ": Updating timestamp to: " + latestEvent + " - from sensor " + sensor
if (!latestEvent) //If the door has been inactive for more than a week, timestamp data will be null. Keep current value in that case.
timeStampLogText = "Door: " + doorName + ": Null timestamp detected " + " - from sensor " + sensor + " . Keeping current value."
else
doorToUpdate.updateDeviceLastActivity(latestEvent)
log.debug timeStampLogText
//Write to child log if this was initiated from one of the doors
if (child)
child.log(timeStampLogText)
}
def refresh(child){
def door = child.device.deviceNetworkId
def doorName = state.data[door].name
child.log("refresh called from " + doorName + ' (' + door + ')')
syncDoorsWithSensors(child)
}
def sensorHandler(evt) {
log.debug "Sensor change detected: Event name " + evt.name + " value: " + evt.value + " deviceID: " + evt.deviceId
switch (evt.deviceId) {
case door1Sensor.id:
case door1Acceleration?.id:
def firstDoor = doors[0]
if (doors instanceof String) firstDoor = doors
updateDoorStatus(firstDoor, door1Sensor, door1Acceleration, door1ThreeAxis, null)
break
case door2Sensor?.id:
case door2Acceleration?.id:
updateDoorStatus(doors[1], door2Sensor, door2Acceleration, door2ThreeAxis, null)
break
case door3Sensor?.id:
case door3Acceleration?.id:
updateDoorStatus(doors[2], door3Sensor, door3Acceleration, door3ThreeAxis, null)
break
case door4Sensor?.id:
case door4Acceleration?.id:
updateDoorStatus(doors[3], door4Sensor, door4Acceleration, door4ThreeAxis, null)
break
default:
syncDoorsWithSensors()
}
}
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 ->
log.debug "got login response: " + 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) {
//log.debug "response data: " + response.data.Devices
//sendAlert("response data: " + response.data.Devices)
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) {
log.debug "Found door: " + device.MyQDeviceId
def dni = [ app.id, "GarageDoorOpener", device.MyQDeviceId ].join('|')
def description = ''
def doorState = ''
def updatedTime = ''
device.Attributes.each {
if (it.AttributeDisplayName=="desc") //deviceList[dni] = it.Value
{
description = it.Value
}
if (it.AttributeDisplayName=="doorstate") {
doorState = it.Value
updatedTime = it.UpdatedTime
}
}
//Ignore any doors with blank descriptions
if (description != ''){
log.debug "adding door: " + description + "type: " + device.MyQDeviceTypeId + " status: " + doorState
deviceList[dni] = description
state.data[dni] = [ status: doorState, lastAction: updatedTime, name: description ]
}
}
}
}
}
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)) {
device.Attributes.each {
def description = ''
def doorState = ''
def updatedTime = ''
if (it.AttributeDisplayName=="desc") //deviceList[dni] = it.Value
description = it.Value
//Ignore any doors with blank descriptions
if (description != ''){
log.debug "found device: " + description
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) {
//sendAlert("API Error: $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"
}
}
// 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 ])
if ((attributeName == "desireddoorstate") && (attributeValue == 0)) { // if we are closing, check if we have an Acceleration sensor, if so, "waiting" until it moves
def firstDoor = doors[0]
if (doors instanceof String) firstDoor = doors
def doorDNI = child.device.deviceNetworkId
switch (doorDNI) {
case firstDoor:
if (door1Acceleration) child.updateDeviceStatus("waiting") else child.updateDeviceStatus("opening")
break
case doors[1]:
if (door2Acceleration) child.updateDeviceStatus("waiting") else child.updateDeviceStatus("opening")
break
case doors[2]:
if (door3Acceleration) child.updateDeviceStatus("waiting") else child.updateDeviceStatus("opening")
break
case doors[3]:
if (door4Acceleration) child.updateDeviceStatus("waiting") else child.updateDeviceStatus("opening")
break
}
}
return true
}
}

View File

@@ -1,11 +1,10 @@
/**
* Color Coordinator
* Version 1.1.1 - 11/9/16
* Version 1.1.0 - 11/9/16
* By Michael Struck
*
* 1.0.0 - Initial release
* 1.1.0 - Fixed issue where master can be part of slaves. This causes a loop that impacts SmartThings.
* 1.1.1 - Fix NPE being thrown for slave/master inputs being empty.
*
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
@@ -34,17 +33,17 @@ preferences {
def mainPage() {
dynamicPage(name: "mainPage", title: "", install: true, uninstall: false) {
def masterInList = slaves?.id?.find{it==master?.id}
def masterInList = slaves.id.find{it==master.id}
if (masterInList) {
section ("**WARNING**"){
paragraph "You have included the Master Light in the Slave Group. This will cause a loop in execution. Please remove this device from the Slave Group.", image: "https://raw.githubusercontent.com/MichaelStruck/SmartThingsPublic/master/img/caution.png"
}
}
section("Master Light") {
input "master", "capability.colorControl", title: "Colored Light", required: true
input "master", "capability.colorControl", title: "Colored Light"
}
section("Lights that follow the master settings") {
input "slaves", "capability.colorControl", title: "Colored Lights", multiple: true, required: true, submitOnChange: true
input "slaves", "capability.colorControl", title: "Colored Lights", multiple: true, required: false, submitOnChange: true
}
section([mobileOnly:true], "Options") {
input "randomYes", "bool",title: "When Master Turned On, Randomize Color", defaultValue: false
@@ -82,44 +81,40 @@ def init() {
}
//-----------------------------------
def onOffHandler(evt){
if (slaves && master) {
if (!slaves?.id.find{it==master?.id}){
if (master?.currentValue("switch") == "on"){
if (randomYes) getRandomColorMaster()
else slaves?.on()
}
else {
slaves?.off()
}
}
if (!slaves.id.find{it==master.id}){
if (master.currentValue("switch") == "on"){
if (randomYes) getRandomColorMaster()
else slaves?.on()
}
else {
slaves?.off()
}
}
}
def colorHandler(evt) {
if (slaves && master) {
if (!slaves?.id?.find{it==master?.id} && master?.currentValue("switch") == "on"){
log.debug "Changing Slave units H,S,L"
def dimLevel = master?.currentValue("level")
def hueLevel = master?.currentValue("hue")
def saturationLevel = master.currentValue("saturation")
def newValue = [hue: hueLevel, saturation: saturationLevel, level: dimLevel as Integer]
slaves?.setColor(newValue)
try {
log.debug "Changing Slave color temp"
def tempLevel = master?.currentValue("colorTemperature")
slaves?.setColorTemperature(tempLevel)
}
catch (e){
log.debug "Color temp for master --"
}
}
if (!slaves.id.find{it==master.id} && master.currentValue("switch") == "on"){
log.debug "Changing Slave units H,S,L"
def dimLevel = master.currentValue("level")
def hueLevel = master.currentValue("hue")
def saturationLevel = master.currentValue("saturation")
def newValue = [hue: hueLevel, saturation: saturationLevel, level: dimLevel as Integer]
slaves?.setColor(newValue)
try {
log.debug "Changing Slave color temp"
def tempLevel = master.currentValue("colorTemperature")
slaves?.setColorTemperature(tempLevel)
}
catch (e){
log.debug "Color temp for master --"
}
}
}
def getRandomColorMaster(){
def hueLevel = Math.floor(Math.random() *1000)
def saturationLevel = Math.floor(Math.random() * 100)
def dimLevel = master?.currentValue("level")
def dimLevel = master.currentValue("level")
def newValue = [hue: hueLevel, saturation: saturationLevel, level: dimLevel as Integer]
log.debug hueLevel
log.debug saturationLevel
@@ -128,14 +123,12 @@ def getRandomColorMaster(){
}
def tempHandler(evt){
if (slaves && master) {
if (!slaves?.id?.find{it==master?.id} && master?.currentValue("switch") == "on"){
if (evt.value != "--") {
log.debug "Changing Slave color temp based on Master change"
def tempLevel = master.currentValue("colorTemperature")
slaves?.setColorTemperature(tempLevel)
}
}
if (!slaves.id.find{it==master.id} && master.currentValue("switch") == "on"){
if (evt.value != "--") {
log.debug "Changing Slave color temp based on Master change"
def tempLevel = master.currentValue("colorTemperature")
slaves?.setColorTemperature(tempLevel)
}
}
}
@@ -146,7 +139,7 @@ private def textAppName() {
}
private def textVersion() {
def text = "Version 1.1.1 (12/13/2016)"
def text = "Version 1.1.0 (11/09/2016)"
}
private def textCopyright() {
@@ -173,4 +166,4 @@ private def textHelp() {
"This application will allow you to control the settings of multiple colored lights with one control. " +
"Simply choose a master control light, and then choose the lights that will follow the settings of the master, "+
"including on/off conditions, hue, saturation, level and color temperature. Also includes a random color feature."
}
}