Compare commits

..

12 Commits

Author SHA1 Message Date
Vinay Rao
a17971d68c Merge pull request #1583 from SmartThingsCommunity/staging
Rolling up staging to production for deploy
2017-01-10 15:01:44 -08:00
Vinay Rao
076ffecd19 Merge pull request #1582 from SmartThingsCommunity/production
Rolling down production hotfix to staging
2017-01-10 15:00:27 -08:00
Vinay Rao
8db0556696 Merge pull request #1562 from varzac/increase-delays
DPROT-223 Increase zigbee message delays
2017-01-10 09:35:46 -08:00
Vinay Rao
490ec329cb Merge pull request #1570 from workingmonk/feature/iris_button
DEVC-520 DEVC-525 Adding Iris Button after clearing certification
2017-01-06 09:34:49 -08:00
Vinay Rao
3109049122 Merge pull request #1569 from workingmonk/feature/certification_work
DEVC-518 Iris Smart Water Sensor
2017-01-06 09:34:25 -08:00
Zach Varberg
930c4ed914 Increase zigbee message delays
This changes our smartsense DTHs that don't use the ZigBee library for
everything to have larger delays between ZigBee messages.  This is to
reduce the network load to try to work around some of the poorer
behaving ZigBee routers we support.

This resolves: https://smartthings.atlassian.net/browse/DPROT-223
2017-01-06 10:45:13 -06:00
Vinay Rao
259516f21f DEVC-520 DEVC-525 Adding Iris Button after clearing certification 2017-01-05 16:54:46 -08:00
Vinay Rao
b7a08a88e0 DEVC-518 Iris Smart Water Sensor 2017-01-05 16:41:50 -08:00
bflorian
76e139153a Merge pull request #1532 from tslagle13/fix-color-coordinator-NPE
[DVCSMP-2273] Fix for NPE for slave/master settings
2016-12-15 17:11:01 -05:00
tslagle13
a4bc248006 One further fix for NPEs 2016-12-14 11:40:02 -08:00
tslagle13
e7eb461b4e [DVCSMP-2273] Fix for NPE for slave/master settings 2016-12-13 14:37:49 -08:00
Vinay Rao
083ed7cc9a Merge pull request #1518 from SmartThingsCommunity/staging
Rolling up staging to prod for deploy
2016-12-07 12:41:33 -08:00
12 changed files with 92 additions and 615 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 500"
"st wattr 0x${device.deviceNetworkId} 1 8 0x10 0x21 {${state?.dOnOff ?: '0000'}}", "delay 2000"
]
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 500"
"st wattr 0x${device.deviceNetworkId} 1 8 0x10 0x21 {${state?.dOnOff ?: '0000'}}", "delay 2000"
]
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 500",
"st rattr 0x${device.deviceNetworkId} ${endpointId} 8 0", "delay 500",
"st rattr 0x${device.deviceNetworkId} ${endpointId} 0x0B04 0x050B", "delay 500"
"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"
]
}
@@ -313,9 +313,9 @@ def isDescriptionPower(descMap) {
def onOffConfig() {
[
"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"
"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"
]
}
@@ -323,9 +323,9 @@ def onOffConfig() {
//min level change is 01
def levelConfig() {
[
"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"
"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"
]
}
@@ -333,9 +333,10 @@ def levelConfig() {
//min change in value is 05
def powerConfig() {
[
"zdo bind 0x${device.deviceNetworkId} 1 ${endpointId} 0x0B04 {${device.zigbeeId}} {}", "delay 200",
"zdo bind 0x${device.deviceNetworkId} 1 ${endpointId} 0x0B04 {${device.zigbeeId}} {}", "delay 2000",
"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
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500"
"delay 200",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 2000"
]
}
@@ -344,7 +345,10 @@ 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}"
[
"st cmd 0x${device.deviceNetworkId} ${endpointId} 8 4 {$level $rate}",
"delay 2000"
]
}
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 500'
'delay 2000'
]
@@ -62,6 +62,6 @@ def off() {
'zcl on-off off',
'delay 200',
"send 0x${zigbee.deviceNetworkId} 0x01 0x${zigbee.endpointId}",
'delay 500'
'delay 2000'
]
}

View File

@@ -157,9 +157,10 @@ def configure() {
//min change in value is 01
def powerConfig() {
[
"zdo bind 0x${device.deviceNetworkId} 1 ${endpointId} 0x0B04 {${device.zigbeeId}} {}", "delay 200",
"zdo bind 0x${device.deviceNetworkId} 1 ${endpointId} 0x0B04 {${device.zigbeeId}} {}", "delay 2000",
"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
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500"
"delay 200",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 2000"
]
}

View File

@@ -29,9 +29,10 @@ 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,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,000F,0020,0402,0500", outClusters: "0019", manufacturer: "SmartThings", model: "moisturev4", deviceJoinName: "Water Leak Sensor"
}
@@ -285,8 +286,8 @@ def ping() {
def refresh() {
log.debug "Refreshing Temperature and Battery"
def refreshCmds = [
"st rattr 0x${device.deviceNetworkId} 1 0x402 0", "delay 200",
"st rattr 0x${device.deviceNetworkId} 1 1 0x20", "delay 200"
"st rattr 0x${device.deviceNetworkId} 1 0x402 0", "delay 2000",
"st rattr 0x${device.deviceNetworkId} 1 1 0x20", "delay 2000"
]
return refreshCmds + enrollResponse()
@@ -308,10 +309,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 500",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 2000",
//Enroll Response
"raw 0x500 {01 23 00 00 00}",
"send 0x${device.deviceNetworkId} 1 1", "delay 200"
"raw 0x500 {01 23 00 00 00}", "delay 200",
"send 0x${device.deviceNetworkId} 1 1", "delay 2000"
]
}

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 200",
"st rattr 0x${device.deviceNetworkId} 1 1 0x20", "delay 200"
"st rattr 0x${device.deviceNetworkId} 1 0x402 0", "delay 2000",
"st rattr 0x${device.deviceNetworkId} 1 1 0x20", "delay 2000"
]
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 500",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 2000",
//Enroll Response
"raw 0x500 {01 23 00 00 00}",
"send 0x${device.deviceNetworkId} 1 1", "delay 200"
"raw 0x500 {01 23 00 00 00}", "delay 200",
"send 0x${device.deviceNetworkId} 1 1", "delay 2000"
]
}

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 500",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 2000",
//Enroll Response
"raw 0x500 {01 23 00 00 00}", "delay 200",
"send 0x${device.deviceNetworkId} 1 1", "delay 200"
"send 0x${device.deviceNetworkId} 1 1", "delay 2000"
]
}

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 200",
"st rattr 0x${device.deviceNetworkId} 1 1 0x20", "delay 200"
"st rattr 0x${device.deviceNetworkId} 1 0x402 0", "delay 2000",
"st rattr 0x${device.deviceNetworkId} 1 1 0x20", "delay 2000"
]
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 500",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 2000",
//Enroll Response
"raw 0x500 {01 23 00 00 00}",
"send 0x${device.deviceNetworkId} 1 1", "delay 200"
"raw 0x500 {01 23 00 00 00}", "delay 200",
"send 0x${device.deviceNetworkId} 1 1", "delay 2000"
]
}

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 500",
"zcl global send-me-a-report 0xFC45 0 0x29 30 3600 {6400}",
"send 0x${device.deviceNetworkId} 1 1", "delay 500"
"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"
]
// 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

@@ -1,536 +0,0 @@
/**
* 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,10 +1,11 @@
/**
* Color Coordinator
* Version 1.1.0 - 11/9/16
* Version 1.1.1 - 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
@@ -33,17 +34,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"
input "master", "capability.colorControl", title: "Colored Light", required: true
}
section("Lights that follow the master settings") {
input "slaves", "capability.colorControl", title: "Colored Lights", multiple: true, required: false, submitOnChange: true
input "slaves", "capability.colorControl", title: "Colored Lights", multiple: true, required: true, submitOnChange: true
}
section([mobileOnly:true], "Options") {
input "randomYes", "bool",title: "When Master Turned On, Randomize Color", defaultValue: false
@@ -81,40 +82,44 @@ def init() {
}
//-----------------------------------
def onOffHandler(evt){
if (!slaves.id.find{it==master.id}){
if (master.currentValue("switch") == "on"){
if (randomYes) getRandomColorMaster()
else slaves?.on()
}
else {
slaves?.off()
}
if (slaves && master) {
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.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 && 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
@@ -123,12 +128,14 @@ def getRandomColorMaster(){
}
def tempHandler(evt){
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 && 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)
}
}
}
}
@@ -139,7 +146,7 @@ private def textAppName() {
}
private def textVersion() {
def text = "Version 1.1.0 (11/09/2016)"
def text = "Version 1.1.1 (12/13/2016)"
}
private def textCopyright() {
@@ -166,4 +173,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."
}
}