Compare commits

..

25 Commits

Author SHA1 Message Date
Vinay Rao
62a965d90b Merge pull request #599 from SmartThingsCommunity/staging
Rolling up changes to production from staging 2016-03-10 Release
2016-03-10 16:26:26 -08:00
Vinay Rao
fb9f1dee47 Merge pull request #626 from munds/hue-hotfix
Hue hotfix for color temperature and overall stability
2016-03-10 15:04:01 -08:00
Amol Mundayoor
3a433d3865 Hue hotfix for color temperature and overall stability 2016-03-10 14:21:49 -08:00
Vinay Rao
8f25ff4434 Merge pull request #570 from SmartThingsCommunity/master
Rolling up changes to staging from master
2016-03-01 12:01:26 -08:00
Dylan Bijnagte
4da9730319 Merge pull request #550 from Bijnagte/device-translations
adding korean translations for DTHs
2016-03-01 13:57:05 -06:00
Donald C. Kirker
25db4f5235 Merge pull request #571 from SmartThingsCommunity/DVCSMP-1440
Remove 3axis tile definition from 1st gen multi.
2016-03-01 10:58:09 -08:00
Donald Kirker
eae2a9ca08 Remove 3axis tile definition from 1st gen multi. 2016-03-01 10:53:42 -08:00
Yaima
2dd2d7cba4 Merge pull request #569 from Yaima/master
Ecobee multiple sensors fix
2016-02-29 14:50:45 -08:00
Yaima Valdivia
f5708bca8b Copyright - Ecobee Sensor 2016-02-29 14:49:50 -08:00
Yaima Valdivia
a9da6d130a Changed copyright information for Ecobee Sensor
https://smartthings.atlassian.net/browse/DVCSMP-1511
2016-02-29 14:49:02 -08:00
Yaima Valdivia
3a2c6f86be Merge branch 'master' of github.com:SmartThingsCommunity/SmartThingsPublic
# By Lars Finander
# Via Lars Finander
* 'master' of github.com:SmartThingsCommunity/SmartThingsPublic:
  DVCSMP-1516 Hue (Connect) throws 15k groovy.lang.MissingPropertyException per day
2016-02-29 14:39:36 -08:00
Yaima Valdivia
71d2b89a37 Ecobee multiple sensors fix
https://smartthings.atlassian.net/browse/DVCSMP-1511
2016-02-29 14:15:56 -08:00
Lars Finander
b131ba1507 Merge pull request #568 from larsfinander/hueConnectFix
DVCSMP-1516 Hue (Connect) throws 15k groovy.lang.MissingPropertyExcep…
2016-02-29 13:48:07 -08:00
Lars Finander
f04a9e3f7a DVCSMP-1516 Hue (Connect) throws 15k groovy.lang.MissingPropertyException per day
-Only parse messages with JSON if they are from Hue bridge
2016-02-29 13:41:58 -08:00
bflorian
6a905e4380 Merge pull request #567 from bflorian/DVCSMP-1531-hue-command-types
DVCSMP-1531 switch hue command methods to void
2016-02-29 14:37:16 -05:00
bflorian
442f16680d DVCSMP-1531 switch hue command methods to void 2016-02-29 14:11:51 -05:00
Yaima
1c68099b52 Merge pull request #562 from Yaima/master
Fix fan mode notifications
2016-02-26 12:54:54 -08:00
Yaima
cc9321ca9f Fix fan mode notifications
https://smartthings.atlassian.net/browse/DVCSMP-1511
2016-02-26 12:39:32 -08:00
Vinay Rao
515b268374 Merge pull request #549 from SmartThingsCommunity/staging
Rolling up changes to production from staging 2016-02-25 Release
2016-02-26 10:51:42 -08:00
Vinay Rao
919c9b88ee Merge pull request #555 from workingmonk/lock_fingerprinting_form
[DEVTOOLS-506] Fixing lock fingerprint causing Fingerprint Tool issue
2016-02-26 09:25:32 -08:00
Vinay Rao
2438942071 Fixing lock fingerprint causing Fingerprint Tool issue 2016-02-25 20:07:05 -08:00
dylanbijnagte
d86dcfd82f adding korean translations for DTHs 2016-02-24 14:34:52 -06:00
Vinay Rao
9b87d39fe8 Merge pull request #546 from SmartThingsCommunity/master
Rolling up changes from master to staging
2016-02-23 10:50:20 -08:00
Vinay Rao
a103d437c2 Merge pull request #523 from SmartThingsCommunity/staging
Deploy to production 2/18
2016-02-18 09:28:02 -08:00
Vinay Rao
bdd88deb99 Merge pull request #504 from SmartThingsCommunity/staging
Merging changes from staging to production
2016-02-11 22:40:38 -08:00
14 changed files with 859 additions and 722 deletions

View File

@@ -1,294 +1,294 @@
/** /**
* Copyright 2015 SmartThings * Copyright 2015 SmartThings
* *
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * 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: * in compliance with the License. You may obtain a copy of the License at:
* *
* http://www.apache.org/licenses/LICENSE-2.0 * 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 * 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 * 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. * for the specific language governing permissions and limitations under the License.
* *
*/ */
metadata { metadata {
definition (name: "Arrival Sensor", namespace: "smartthings", author: "SmartThings") { definition (name: "Arrival Sensor", namespace: "smartthings", author: "SmartThings") {
capability "Tone" capability "Tone"
capability "Actuator" capability "Actuator"
capability "Signal Strength" capability "Signal Strength"
capability "Presence Sensor" capability "Presence Sensor"
capability "Sensor" capability "Sensor"
capability "Battery" capability "Battery"
fingerprint profileId: "FC01", deviceId: "019A" fingerprint profileId: "FC01", deviceId: "019A"
fingerprint profileId: "FC01", deviceId: "0131", inClusters: "0000,0003", outClusters: "0003" fingerprint profileId: "FC01", deviceId: "0131", inClusters: "0000,0003", outClusters: "0003"
fingerprint profileId: "FC01", deviceId: "0131", inClusters: "0000", outClusters: "0006" fingerprint profileId: "FC01", deviceId: "0131", inClusters: "0000", outClusters: "0006"
} }
simulator { simulator {
status "present": "presence: 1" status "present": "presence: 1"
status "not present": "presence: 0" status "not present": "presence: 0"
status "battery": "battery: 27, batteryDivisor: 0A, rssi: 100, lqi: 64" status "battery": "battery: 27, batteryDivisor: 0A, rssi: 100, lqi: 64"
} }
preferences { preferences {
section { section {
image(name: 'educationalcontent', multiple: true, images: [ image(name: 'educationalcontent', multiple: true, images: [
"http://cdn.device-gse.smartthings.com/Arrival/Arrival1.jpg", "http://cdn.device-gse.smartthings.com/Arrival/Arrival1.jpg",
"http://cdn.device-gse.smartthings.com/Arrival/Arrival2.jpg" "http://cdn.device-gse.smartthings.com/Arrival/Arrival2.jpg"
]) ])
} }
} }
tiles { tiles {
standardTile("presence", "device.presence", width: 2, height: 2, canChangeBackground: true) { standardTile("presence", "device.presence", width: 2, height: 2, canChangeBackground: true) {
state "present", labelIcon:"st.presence.tile.present", backgroundColor:"#53a7c0" state "present", labelIcon:"st.presence.tile.present", backgroundColor:"#53a7c0"
state "not present", labelIcon:"st.presence.tile.not-present", backgroundColor:"#ebeef2" state "not present", labelIcon:"st.presence.tile.not-present", backgroundColor:"#ebeef2"
} }
standardTile("beep", "device.beep", decoration: "flat") { standardTile("beep", "device.beep", decoration: "flat") {
state "beep", label:'', action:"tone.beep", icon:"st.secondary.beep", backgroundColor:"#ffffff" state "beep", label:'', action:"tone.beep", icon:"st.secondary.beep", backgroundColor:"#ffffff"
} }
valueTile("battery", "device.battery", decoration: "flat", inactiveLabel: false) { valueTile("battery", "device.battery", decoration: "flat", inactiveLabel: false) {
state "battery", label:'${currentValue}% battery', unit:""/*, backgroundColors:[ state "battery", label:'${currentValue}% battery', unit:""/*, backgroundColors:[
[value: 5, color: "#BC2323"], [value: 5, color: "#BC2323"],
[value: 10, color: "#D04E00"], [value: 10, color: "#D04E00"],
[value: 15, color: "#F1D801"], [value: 15, color: "#F1D801"],
[value: 16, color: "#FFFFFF"] [value: 16, color: "#FFFFFF"]
]*/ ]*/
} }
/* /*
valueTile("lqi", "device.lqi", decoration: "flat", inactiveLabel: false) { valueTile("lqi", "device.lqi", decoration: "flat", inactiveLabel: false) {
state "lqi", label:'${currentValue}% signal', unit:"" state "lqi", label:'${currentValue}% signal', unit:""
} }
*/ */
main "presence" main "presence"
details(["presence", "beep", "battery"/*, "lqi"*/]) details(["presence", "beep", "battery"/*, "lqi"*/])
} }
} }
def beep() { def beep() {
/* /*
You can make the speaker turn on for 0.5-second beeps by sending some CLI commands: You can make the speaker turn on for 0.5-second beeps by sending some CLI commands:
Command: send raw, wait 7, send raw, wait 7, send raw Command: send raw, wait 7, send raw, wait 7, send raw
Future: new packet type "st.beep" Future: new packet type "st.beep"
raw 0xFC05 {15 0A 11 00 00 15 01} raw 0xFC05 {15 0A 11 00 00 15 01}
send 0x2F7F 2 2 send 0x2F7F 2 2
where "0xABCD" is the node ID of the Smart Tag, everything else above is a constant. Except where "0xABCD" is the node ID of the Smart Tag, everything else above is a constant. Except
the "15 01" at the end of the first raw command, that sets the speaker's period (reciprocal the "15 01" at the end of the first raw command, that sets the speaker's period (reciprocal
of frequency). You can play with this value up or down to experiment with loudness as the of frequency). You can play with this value up or down to experiment with loudness as the
loudness will be strongly dependent upon frequency and the enclosure that it's in. Note that loudness will be strongly dependent upon frequency and the enclosure that it's in. Note that
"15 01" represents the hex number 0x0115 so a lower frequency is "16 01" (longer period) and "15 01" represents the hex number 0x0115 so a lower frequency is "16 01" (longer period) and
a higher frequency is "14 01" (shorter period). Note that since the tag only checks its parent a higher frequency is "14 01" (shorter period). Note that since the tag only checks its parent
for messages every 5 seconds (while at rest) or every 3 seconds (while in motion) it will take for messages every 5 seconds (while at rest) or every 3 seconds (while in motion) it will take
up to this long from the time you send the message to the time you hear a sound. up to this long from the time you send the message to the time you hear a sound.
*/ */
[ [
"raw 0xFC05 {15 0A 11 00 00 15 01}", "raw 0xFC05 {15 0A 11 00 00 15 01}",
"delay 7000", "delay 7000",
"raw 0xFC05 {15 0A 11 00 00 15 01}", "raw 0xFC05 {15 0A 11 00 00 15 01}",
"delay 7000", "delay 7000",
"raw 0xFC05 {15 0A 11 00 00 15 01}", "raw 0xFC05 {15 0A 11 00 00 15 01}",
"delay 7000", "delay 7000",
"raw 0xFC05 {15 0A 11 00 00 15 01}", "raw 0xFC05 {15 0A 11 00 00 15 01}",
"delay 7000", "delay 7000",
"raw 0xFC05 {15 0A 11 00 00 15 01}" "raw 0xFC05 {15 0A 11 00 00 15 01}"
] ]
} }
def parse(String description) { def parse(String description) {
def results def results
if (isBatteryMessage(description)) { if (isBatteryMessage(description)) {
results = parseBatteryMessage(description) results = parseBatteryMessage(description)
} }
else { else {
results = parsePresenceMessage(description) results = parsePresenceMessage(description)
} }
log.debug "Parse returned $results.descriptionText" log.debug "Parse returned $results.descriptionText"
results results
} }
private Map parsePresenceMessage(String description) { private Map parsePresenceMessage(String description) {
def name = parseName(description) def name = parseName(description)
def value = parseValue(description) def value = parseValue(description)
def linkText = getLinkText(device) def linkText = getLinkText(device)
def descriptionText = parseDescriptionText(linkText, value, description) def descriptionText = parseDescriptionText(linkText, value, description)
def handlerName = getState(value) def handlerName = getState(value)
def isStateChange = isStateChange(device, name, value) def isStateChange = isStateChange(device, name, value)
def results = [ def results = [
name: name, name: name,
value: value, value: value,
unit: null, unit: null,
linkText: linkText, linkText: linkText,
descriptionText: descriptionText, descriptionText: descriptionText,
handlerName: handlerName, handlerName: handlerName,
isStateChange: isStateChange, isStateChange: isStateChange,
displayed: displayed(description, isStateChange) displayed: displayed(description, isStateChange)
] ]
results results
} }
private String parseName(String description) { private String parseName(String description) {
if (description?.startsWith("presence: ")) { if (description?.startsWith("presence: ")) {
return "presence" return "presence"
} }
null null
} }
private String parseValue(String description) { private String parseValue(String description) {
if (description?.startsWith("presence: ")) if (description?.startsWith("presence: "))
{ {
if (description?.endsWith("1")) if (description?.endsWith("1"))
{ {
return "present" return "present"
} }
else if (description?.endsWith("0")) else if (description?.endsWith("0"))
{ {
return "not present" return "not present"
} }
} }
description description
} }
private parseDescriptionText(String linkText, String value, String description) { private parseDescriptionText(String linkText, String value, String description) {
switch(value) { switch(value) {
case "present": return "$linkText has arrived" case "present": return "$linkText has arrived"
case "not present": return "$linkText has left" case "not present": return "$linkText has left"
default: return value default: return value
} }
} }
private getState(String value) { private getState(String value) {
def state = value def state = value
if (value == "present") { if (value == "present") {
state = "arrived" state = "arrived"
} }
else if (value == "not present") { else if (value == "not present") {
state = "left" state = "left"
} }
state state
} }
private Boolean isBatteryMessage(String description) { private Boolean isBatteryMessage(String description) {
// "raw:36EF1C, dni:36EF, battery:1B, rssi:, lqi:" // "raw:36EF1C, dni:36EF, battery:1B, rssi:, lqi:"
description ==~ /.*battery:.*rssi:.*lqi:.*/ description ==~ /.*battery:.*rssi:.*lqi:.*/
} }
private List parseBatteryMessage(String description) { private List parseBatteryMessage(String description) {
def results = [] def results = []
def parts = description.split(',') def parts = description.split(',')
parts.each { part -> parts.each { part ->
part = part.trim() part = part.trim()
if (part.startsWith('battery:')) { if (part.startsWith('battery:')) {
def batteryResult = getBatteryResult(part, description) def batteryResult = getBatteryResult(part, description)
if (batteryResult) { if (batteryResult) {
results << batteryResult results << batteryResult
} }
} }
else if (part.startsWith('rssi:')) { else if (part.startsWith('rssi:')) {
def rssiResult = getRssiResult(part, description) def rssiResult = getRssiResult(part, description)
if (rssiResult) { if (rssiResult) {
results << rssiResult results << rssiResult
} }
} }
else if (part.startsWith('lqi:')) { else if (part.startsWith('lqi:')) {
def lqiResult = getLqiResult(part, description) def lqiResult = getLqiResult(part, description)
if (lqiResult) { if (lqiResult) {
results << lqiResult results << lqiResult
} }
} }
} }
results results
} }
private getBatteryResult(part, description) { private getBatteryResult(part, description) {
def batteryDivisor = description.split(",").find {it.split(":")[0].trim() == "batteryDivisor"} ? description.split(",").find {it.split(":")[0].trim() == "batteryDivisor"}.split(":")[1].trim() : null def batteryDivisor = description.split(",").find {it.split(":")[0].trim() == "batteryDivisor"} ? description.split(",").find {it.split(":")[0].trim() == "batteryDivisor"}.split(":")[1].trim() : null
def name = "battery" def name = "battery"
def value = zigbee.parseSmartThingsBatteryValue(part, batteryDivisor) def value = zigbee.parseSmartThingsBatteryValue(part, batteryDivisor)
def unit = "%" def unit = "%"
def linkText = getLinkText(device) def linkText = getLinkText(device)
def descriptionText = "$linkText battery was ${value}${unit}" def descriptionText = "$linkText battery was ${value}${unit}"
def isStateChange = isStateChange(device, name, value) def isStateChange = isStateChange(device, name, value)
[ [
name: name, name: name,
value: value, value: value,
unit: unit, unit: unit,
linkText: linkText, linkText: linkText,
descriptionText: descriptionText, descriptionText: descriptionText,
handlerName: name, handlerName: name,
isStateChange: isStateChange, isStateChange: isStateChange,
//displayed: displayed(description, isStateChange) //displayed: displayed(description, isStateChange)
displayed: false displayed: false
] ]
} }
private getRssiResult(part, description) { private getRssiResult(part, description) {
def name = "rssi" def name = "rssi"
def parts = part.split(":") def parts = part.split(":")
if (parts.size() != 2) return null if (parts.size() != 2) return null
def valueString = parts[1].trim() def valueString = parts[1].trim()
def valueInt = Integer.parseInt(valueString, 16) def valueInt = Integer.parseInt(valueString, 16)
def value = (valueInt - 128).toString() def value = (valueInt - 128).toString()
def linkText = getLinkText(device) def linkText = getLinkText(device)
def descriptionText = "$linkText was $value dBm" def descriptionText = "$linkText was $value dBm"
def isStateChange = isStateChange(device, name, value) def isStateChange = isStateChange(device, name, value)
[ [
name: name, name: name,
value: value, value: value,
unit: "dBm", unit: "dBm",
linkText: linkText, linkText: linkText,
descriptionText: descriptionText, descriptionText: descriptionText,
handlerName: null, handlerName: null,
isStateChange: isStateChange, isStateChange: isStateChange,
//displayed: displayed(description, isStateChange) //displayed: displayed(description, isStateChange)
displayed: false displayed: false
] ]
} }
/** /**
* Use LQI (Link Quality Indicator) as a measure of signal strength. The values * Use LQI (Link Quality Indicator) as a measure of signal strength. The values
* are 0 to 255 (0x00 to 0xFF) and higher values represent higher signal * are 0 to 255 (0x00 to 0xFF) and higher values represent higher signal
* strength. Return as a percentage of 255. * strength. Return as a percentage of 255.
* *
* Note: To make the signal strength indicator more accurate, we could combine * Note: To make the signal strength indicator more accurate, we could combine
* LQI with RSSI. * LQI with RSSI.
*/ */
private getLqiResult(part, description) { private getLqiResult(part, description) {
def name = "lqi" def name = "lqi"
def parts = part.split(":") def parts = part.split(":")
if (parts.size() != 2) return null if (parts.size() != 2) return null
def valueString = parts[1].trim() def valueString = parts[1].trim()
def valueInt = Integer.parseInt(valueString, 16) def valueInt = Integer.parseInt(valueString, 16)
def percentageOf = 255 def percentageOf = 255
def value = Math.round((valueInt / percentageOf * 100)).toString() def value = Math.round((valueInt / percentageOf * 100)).toString()
def unit = "%" def unit = "%"
def linkText = getLinkText(device) def linkText = getLinkText(device)
def descriptionText = "$linkText Signal (LQI) was ${value}${unit}" def descriptionText = "$linkText Signal (LQI) was ${value}${unit}"
def isStateChange = isStateChange(device, name, value) def isStateChange = isStateChange(device, name, value)
[ [
name: name, name: name,
value: value, value: value,
unit: unit, unit: unit,
linkText: linkText, linkText: linkText,
descriptionText: descriptionText, descriptionText: descriptionText,
handlerName: null, handlerName: null,
isStateChange: isStateChange, isStateChange: isStateChange,
//displayed: displayed(description, isStateChange) //displayed: displayed(description, isStateChange)
displayed: false displayed: false
] ]
} }

View File

@@ -1,7 +1,5 @@
/** /**
* Ecobee Sensor * Copyright 2015 SmartThings
*
* Copyright 2015 Juan Risso
* *
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * 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: * in compliance with the License. You may obtain a copy of the License at:
@@ -12,6 +10,9 @@
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License * 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. * for the specific language governing permissions and limitations under the License.
* *
* Ecobee Sensor
*
* Author: SmartThings
*/ */
metadata { metadata {
definition (name: "Ecobee Sensor", namespace: "smartthings", author: "SmartThings") { definition (name: "Ecobee Sensor", namespace: "smartthings", author: "SmartThings") {
@@ -26,7 +27,16 @@ metadata {
valueTile("temperature", "device.temperature", width: 2, height: 2) { valueTile("temperature", "device.temperature", width: 2, height: 2) {
state("temperature", label:'${currentValue}°', unit:"F", state("temperature", label:'${currentValue}°', unit:"F",
backgroundColors:[ backgroundColors:[
[value: 31, color: "#153591"], // Celsius
[value: 0, color: "#153591"],
[value: 7, color: "#1e9cbb"],
[value: 15, color: "#90d2a7"],
[value: 23, color: "#44b621"],
[value: 28, color: "#f1d801"],
[value: 35, color: "#d04e00"],
[value: 37, color: "#bc2323"],
// Fahrenheit
[value: 40, color: "#153591"],
[value: 44, color: "#1e9cbb"], [value: 44, color: "#1e9cbb"],
[value: 59, color: "#90d2a7"], [value: 59, color: "#90d2a7"],
[value: 74, color: "#44b621"], [value: 74, color: "#44b621"],

View File

@@ -395,7 +395,7 @@ def generateModeEvent(mode) {
} }
def generateFanModeEvent(fanMode) { def generateFanModeEvent(fanMode) {
sendEvent(name: "thermostatFanMode", value: fanMode, descriptionText: "$device.displayName fan is in ${mode} mode", displayed: true) sendEvent(name: "thermostatFanMode", value: fanMode, descriptionText: "$device.displayName fan is in ${fanMode} mode", displayed: true)
} }
def generateOperatingStateEvent(operatingState) { def generateOperatingStateEvent(operatingState) {
@@ -493,7 +493,7 @@ def fanOn() {
} else { } else {
log.debug "Error setting new mode." log.debug "Error setting new mode."
def currentFanMode = device.currentState("thermostatFanMode")?.value def currentFanMode = device.currentState("thermostatFanMode")?.value
generateModeEvent(currentFanMode) // reset the tile back generateFanModeEvent(currentFanMode) // reset the tile back
} }
} }
@@ -514,7 +514,7 @@ def fanAuto() {
} else { } else {
log.debug "Error setting new mode." log.debug "Error setting new mode."
def currentFanMode = device.currentState("thermostatFanMode")?.value def currentFanMode = device.currentState("thermostatFanMode")?.value
generateModeEvent(currentFanMode) // reset the tile back generateFanModeEvent(currentFanMode) // reset the tile back
} }
} }

View File

@@ -1,4 +1,3 @@
/** /**
* Hue Bulb * Hue Bulb
* *
@@ -11,13 +10,14 @@ metadata {
capability "Switch Level" capability "Switch Level"
capability "Actuator" capability "Actuator"
capability "Color Control" capability "Color Control"
capability "Color Temperature"
capability "Switch" capability "Switch"
capability "Refresh" capability "Refresh"
capability "Sensor" capability "Sensor"
command "setAdjustedColor" command "setAdjustedColor"
command "reset" command "reset"
command "refresh" command "refresh"
} }
simulator { simulator {
@@ -25,7 +25,7 @@ metadata {
} }
tiles (scale: 2){ tiles (scale: 2){
multiAttributeTile(name:"switch", type: "lighting", width: 6, height: 4, canChangeIcon: true){ multiAttributeTile(name:"rich-control", type: "lighting", width: 6, height: 4, canChangeIcon: true){
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") { tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
attributeState "on", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff" attributeState "on", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff"
attributeState "off", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn" attributeState "off", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
@@ -33,23 +33,58 @@ metadata {
attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn" attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
} }
tileAttribute ("device.level", key: "SLIDER_CONTROL") { tileAttribute ("device.level", key: "SLIDER_CONTROL") {
attributeState "level", action:"switch level.setLevel" attributeState "level", action:"switch level.setLevel", range:"(0..100)"
}
tileAttribute ("device.level", key: "SECONDARY_CONTROL") {
attributeState "level", label: 'Level ${currentValue}%'
} }
tileAttribute ("device.color", key: "COLOR_CONTROL") { tileAttribute ("device.color", key: "COLOR_CONTROL") {
attributeState "color", action:"setAdjustedColor" attributeState "color", action:"setAdjustedColor"
} }
} }
standardTile("reset", "device.reset", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { standardTile("switch", "device.switch", width: 2, height: 2, canChangeIcon: true) {
state "on", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff"
state "off", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
state "turningOn", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff"
state "turningOff", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
}
controlTile("colorTempSliderControl", "device.colorTemperature", "slider", width: 4, height: 2, inactiveLabel: false, range:"(2000..6500)") {
state "colorTemperature", action:"color temperature.setColorTemperature"
}
valueTile("colorTemp", "device.colorTemperature", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
state "colorTemperature", label: '${currentValue} K'
}
standardTile("reset", "device.reset", height: 2, width: 2, inactiveLabel: false, decoration: "flat") {
state "default", label:"Reset Color", action:"reset", icon:"st.lights.philips.hue-single" state "default", label:"Reset Color", action:"reset", icon:"st.lights.philips.hue-single"
} }
standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { standardTile("refresh", "device.switch", height: 2, width: 2, inactiveLabel: false, decoration: "flat") {
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh" state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
} }
} controlTile("levelSliderControl", "device.level", "slider", height: 1, width: 2, inactiveLabel: false, range:"(0..100)") {
state "level", action:"switch level.setLevel"
}
valueTile("level", "device.level", inactiveLabel: false, decoration: "flat") {
state "level", label: 'Level ${currentValue}%'
}
controlTile("saturationSliderControl", "device.saturation", "slider", height: 1, width: 2, inactiveLabel: false) {
state "saturation", action:"color control.setSaturation"
}
valueTile("saturation", "device.saturation", inactiveLabel: false, decoration: "flat") {
state "saturation", label: 'Sat ${currentValue} '
}
controlTile("hueSliderControl", "device.hue", "slider", height: 1, width: 2, inactiveLabel: false) {
state "hue", action:"color control.setHue"
}
valueTile("hue", "device.hue", inactiveLabel: false, decoration: "flat") {
state "hue", label: 'Hue ${currentValue} '
}
main(["switch"]) main(["switch"])
details(["switch", "levelSliderControl", "rgbSelector", "refresh", "reset"]) details(["rich-control", "colorTempSliderControl", "colorTemp", "reset", "refresh"])
}
} }
// parse events into attributes // parse events into attributes
@@ -68,17 +103,17 @@ def parse(description) {
} }
// handle commands // handle commands
def on() { void on() {
log.trace parent.on(this) log.trace parent.on(this)
sendEvent(name: "switch", value: "on") sendEvent(name: "switch", value: "on")
} }
def off() { void off() {
log.trace parent.off(this) log.trace parent.off(this)
sendEvent(name: "switch", value: "off") sendEvent(name: "switch", value: "off")
} }
def nextLevel() { void nextLevel() {
def level = device.latestValue("level") as Integer ?: 0 def level = device.latestValue("level") as Integer ?: 0
if (level <= 100) { if (level <= 100) {
level = Math.min(25 * (Math.round(level / 25) + 1), 100) as Integer level = Math.min(25 * (Math.round(level / 25) + 1), 100) as Integer
@@ -89,25 +124,25 @@ def nextLevel() {
setLevel(level) setLevel(level)
} }
def setLevel(percent) { void setLevel(percent) {
log.debug "Executing 'setLevel'" log.debug "Executing 'setLevel'"
parent.setLevel(this, percent) parent.setLevel(this, percent)
sendEvent(name: "level", value: percent) sendEvent(name: "level", value: percent)
} }
def setSaturation(percent) { void setSaturation(percent) {
log.debug "Executing 'setSaturation'" log.debug "Executing 'setSaturation'"
parent.setSaturation(this, percent) parent.setSaturation(this, percent)
sendEvent(name: "saturation", value: percent) sendEvent(name: "saturation", value: percent)
} }
def setHue(percent) { void setHue(percent) {
log.debug "Executing 'setHue'" log.debug "Executing 'setHue'"
parent.setHue(this, percent) parent.setHue(this, percent)
sendEvent(name: "hue", value: percent) sendEvent(name: "hue", value: percent)
} }
def setColor(value) { void setColor(value) {
log.debug "setColor: ${value}, $this" log.debug "setColor: ${value}, $this"
parent.setColor(this, value) parent.setColor(this, value)
if (value.hue) { sendEvent(name: "hue", value: value.hue)} if (value.hue) { sendEvent(name: "hue", value: value.hue)}
@@ -117,25 +152,33 @@ def setColor(value) {
if (value.switch) { sendEvent(name: "switch", value: value.switch)} if (value.switch) { sendEvent(name: "switch", value: value.switch)}
} }
def reset() { void reset() {
log.debug "Executing 'reset'" log.debug "Executing 'reset'"
def value = [level:100, hex:"#90C638", saturation:56, hue:23] def value = [level:100, hex:"#90C638", saturation:56, hue:23]
setAdjustedColor(value) setAdjustedColor(value)
parent.poll() parent.poll()
} }
def setAdjustedColor(value) { void setAdjustedColor(value) {
if (value) { if (value) {
log.trace "setAdjustedColor: ${value}" log.trace "setAdjustedColor: ${value}"
def adjusted = value + [:] def adjusted = value + [:]
adjusted.hue = adjustOutgoingHue(value.hue) adjusted.hue = adjustOutgoingHue(value.hue)
// Needed because color picker always sends 100 // Needed because color picker always sends 100
adjusted.level = null adjusted.level = null
setColor(adjusted) setColor(adjusted)
} }
} }
def refresh() { void setColorTemperature(value) {
if (value) {
log.trace "setColorTemperature: ${value}k"
parent.setColorTemperature(this, value)
sendEvent(name: "colorTemperature", value: value)
}
}
void refresh() {
log.debug "Executing 'refresh'" log.debug "Executing 'refresh'"
parent.manualRefresh() parent.manualRefresh()
} }

View File

@@ -12,14 +12,14 @@ metadata {
capability "Switch" capability "Switch"
capability "Refresh" capability "Refresh"
capability "Sensor" capability "Sensor"
command "refresh" command "refresh"
} }
simulator { simulator {
// TODO: define status and reply messages here // TODO: define status and reply messages here
} }
tiles(scale: 2) { tiles(scale: 2) {
multiAttributeTile(name:"rich-control", type: "lighting", canChangeIcon: true){ multiAttributeTile(name:"rich-control", type: "lighting", canChangeIcon: true){
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") { tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
@@ -35,25 +35,25 @@ metadata {
attributeState "level", label: 'Level ${currentValue}%' attributeState "level", label: 'Level ${currentValue}%'
} }
} }
standardTile("switch", "device.switch", width: 2, height: 2, canChangeIcon: true) { standardTile("switch", "device.switch", width: 2, height: 2, canChangeIcon: true) {
state "on", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff" state "on", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff"
state "off", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn" state "off", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
state "turningOn", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff" state "turningOn", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff"
state "turningOff", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn" state "turningOff", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
} }
controlTile("levelSliderControl", "device.level", "slider", height: 1, width: 2, inactiveLabel: false, range:"(0..100)") { controlTile("levelSliderControl", "device.level", "slider", height: 1, width: 2, inactiveLabel: false, range:"(0..100)") {
state "level", action:"switch level.setLevel" state "level", action:"switch level.setLevel"
} }
standardTile("refresh", "device.switch", inactiveLabel: false, height: 2, width: 2, decoration: "flat") { standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh" state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
} }
main(["switch"]) main(["switch"])
details(["rich-control", "refresh"]) details(["rich-control", "refresh"])
} }
} }
// parse events into attributes // parse events into attributes
@@ -74,23 +74,23 @@ def parse(description) {
} }
// handle commands // handle commands
def on() { void on() {
parent.on(this) parent.on(this)
sendEvent(name: "switch", value: "on") sendEvent(name: "switch", value: "on")
} }
def off() { void off() {
parent.off(this) parent.off(this)
sendEvent(name: "switch", value: "off") sendEvent(name: "switch", value: "off")
} }
def setLevel(percent) { void setLevel(percent) {
log.debug "Executing 'setLevel'" log.debug "Executing 'setLevel'"
parent.setLevel(this, percent) parent.setLevel(this, percent)
sendEvent(name: "level", value: percent) sendEvent(name: "level", value: percent)
} }
def refresh() { void refresh() {
log.debug "Executing 'refresh'" log.debug "Executing 'refresh'"
parent.manualRefresh() parent.manualRefresh()
} }

View File

@@ -0,0 +1,31 @@
#==============================================================================
# Copyright 2016 SmartThings
#
# 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.
#==============================================================================
# Purpose: Mobile Presence i18n Translation File
#
# Filename: mobile-presence.src/i18n/messages.properties
#
# Change History:
# 1. 20160205 TW Initial release with informal Korean translation.
#==============================================================================
# Korean (ko)
# Device Preferences
'''Give your device a name'''.ko=기기 이름 바꾸기
'''Set Device Image'''.ko=디바이스 이미지 설정
# Events / Notifications
'''{{ linkText }} has left'''.ko={{ linkText }}님이 나갔습니다
'''{{ linkText }} has arrived'''.ko={{ linkText }}님이 도착했습니다
'''present'''.ko=집안
'''not present'''.ko=부재중

View File

@@ -0,0 +1,14 @@
# Generated on Wed Feb 24 14:28:26 CST 2016 by dylan
'''Adjust temperature by this many degrees'''.ko=몇 도씩 온도를 조절하십시오
'''Degrees'''.ko=온도
'''Temperature Offset'''.ko=온도 직접 설정
'''This feature allows you to correct any temperature variations by selecting an offset. Ex: If your sensor consistently reports a temp that's 5 degrees too warm, you'd enter '-5'. If 3 degrees too cold, enter '+3'.'''.ko=기준 온도를 원하는대로 몇 도 올리거나 내려서 설정할 수 있습니다.
'''battery'''.ko=배터리
'''dry'''.ko=건조
'''wet'''.ko=누수
'''{{ device.displayName }} battery has too much power: (> 3.5) volts.'''.ko={{ device.displayName }} 배터리 전력이 너무 높습니다(3.5볼트 초과).
'''{{ device.displayName }} battery was {{ value }}'''.ko={{ device.displayName }} 배터리가 {{ value }}였습니다
'''{{ device.displayName }} is {{ value | translate }}'''.ko={{ device.displayName }}이(가) {{ value | translate }}입니다
'''{{ device.displayName }} was {{ value }}°C'''.ko={{ device.displayName }}이(가) {{ value }}°C였습니다
'''{{ device.displayName }} was {{ value }}°F'''.ko={{ device.displayName }}이(가) {{ value }}°F였습니다

View File

@@ -0,0 +1,13 @@
# Generated on Wed Feb 24 14:28:26 CST 2016 by dylan
'''Adjust temperature by this many degrees'''.ko=몇 도씩 온도를 조절하십시오
'''Degrees'''.ko=온도
'''Temperature Offset'''.ko=온도 직접 설정
'''This feature allows you to correct any temperature variations by selecting an offset. Ex: If your sensor consistently reports a temp that's 5 degrees too warm, you'd enter '-5'. If 3 degrees too cold, enter '+3'.'''.ko=기준 온도를 원하는대로 몇 도 올리거나 내려서 설정할 수 있습니다.
'''battery'''.ko=배터리
'''{{ device.displayName }} battery has too much power: (> 3.5) volts.'''.ko={{ device.displayName }} 배터리 전력이 너무 높습니다(3.5볼트 초과).
'''{{ device.displayName }} battery was {{ value }}'''.ko={{ device.displayName }} 배터리가 {{ value }}였습니다
'''{{ device.displayName }} detected motion'''.ko={{ device.displayName }}가 움직임을 감지하였습니다.
'''{{ device.displayName }} motion has stopped'''.ko={{ device.displayName }} 동작이 중단되었습니다
'''{{ device.displayName }} was {{ value }}°C'''.ko={{ device.displayName }}이(가) {{ value }}°C였습니다
'''{{ device.displayName }} was {{ value }}°F'''.ko={{ device.displayName }}이(가) {{ value }}°F였습니다

View File

@@ -0,0 +1,20 @@
# Generated on Wed Feb 24 14:28:26 CST 2016 by dylan
'''Adjust temperature by this many degrees'''.ko=몇 도씩 온도를 조절하십시오
'''Degrees'''.ko=온도
'''Do you want to use this sensor on a garage door?'''.ko=차고 문의 센서 사용 설정하기
'''No'''.ko=아니요
'''Tap to set'''.ko=눌러서 설정
'''Temperature Offset'''.ko=온도 직접 설정
'''This feature allows you to correct any temperature variations by selecting an offset. Ex: If your sensor consistently reports a temp that's 5 degrees too warm, you'd enter '-5'. If 3 degrees too cold, enter '+3'.'''.ko=기준 온도를 원하는대로 몇 도 올리거나 내려서 설정할 수 있습니다.
'''Updating device to garage sensor'''.ko=기기-차고 센서 업데이트 중
'''Updating device to open/close sensor'''.ko=기기-열림/닫힘 센서 업데이트 중
'''Yes'''.ko=
'''{{ device.displayName }} status was closed'''.ko={{ device.displayName }}은(는) 닫힌 상태입니다
'''{{ device.displayName }} status was opened'''.ko={{ device.displayName }}은(는) 열린 상태입니다
'''{{ device.displayName }} was active'''.ko={{ device.displayName }}이(가) 활성화되었습니다
'''{{ device.displayName }} was closed'''.ko={{ device.displayName }}이(가) 닫혔습니다
'''{{ device.displayName }} was inactive'''.ko={{ device.displayName }}이(가) 비활성화되었습니다
'''{{ device.displayName }} was opened'''.ko={{ device.displayName }}이(가) 열렸습니다
'''{{ device.displayName }} was {{ value }}°C'''.ko={{ device.displayName }}이(가) {{ value }}°C였습니다
'''{{ device.displayName }} was {{ value }}°F'''.ko={{ device.displayName }}이(가) {{ value }}°F였습니다

View File

@@ -72,15 +72,12 @@ metadata {
] ]
) )
} }
valueTile("3axis", "device.threeAxis", decoration: "flat", wordWrap: false, width: 2, height: 2) {
state("threeAxis", label:'${currentValue}', unit:"", backgroundColor:"#ffffff")
}
valueTile("battery", "device.battery", decoration: "flat", inactiveLabel: false, width: 2, height: 2) { valueTile("battery", "device.battery", decoration: "flat", inactiveLabel: false, width: 2, height: 2) {
state "battery", label:'${currentValue}% battery', unit:"" state "battery", label:'${currentValue}% battery', unit:""
} }
main(["contact", "acceleration", "temperature"]) main(["contact", "acceleration", "temperature"])
details(["contact", "acceleration", "temperature", "3axis", "battery"]) details(["contact", "acceleration", "temperature", "battery"])
} }
} }

View File

@@ -23,22 +23,14 @@
capability "Battery" capability "Battery"
capability "Configuration" capability "Configuration"
fingerprint profileId: "0104", inClusters: "0000,0001,0003,0004,0005,0009,0020,0101,0402,0B05,FDBD", outClusters: "000A,0019", fingerprint profileId: "0104", inClusters: "0000,0001,0003,0004,0005,0009,0020,0101,0402,0B05,FDBD", outClusters: "000A,0019", manufacturer: "Kwikset", model: "SMARTCODE_DEADBOLT_5", deviceJoinName: "Kwikset 5-Button Deadbolt"
manufacturer: "Kwikset", model: "SMARTCODE_DEADBOLT_5", deviceJoinName: "Kwikset 5-Button Deadbolt" fingerprint profileId: "0104", inClusters: "0000,0001,0003,0004,0005,0009,0020,0101,0402,0B05,FDBD", outClusters: "000A,0019", manufacturer: "Kwikset", model: "SMARTCODE_LEVER_5", deviceJoinName: "Kwikset 5-Button Lever"
fingerprint profileId: "0104", inClusters: "0000,0001,0003,0004,0005,0009,0020,0101,0402,0B05,FDBD", outClusters: "000A,0019", fingerprint profileId: "0104", inClusters: "0000,0001,0003,0004,0005,0009,0020,0101,0402,0B05,FDBD", outClusters: "000A,0019", manufacturer: "Kwikset", model: "SMARTCODE_DEADBOLT_10", deviceJoinName: "Kwikset 10-Button Deadbolt"
manufacturer: "Kwikset", model: "SMARTCODE_LEVER_5", deviceJoinName: "Kwikset 5-Button Lever" fingerprint profileId: "0104", inClusters: "0000,0001,0003,0004,0005,0009,0020,0101,0402,0B05,FDBD", outClusters: "000A,0019", manufacturer: "Kwikset", model: "SMARTCODE_DEADBOLT_10T", deviceJoinName: "Kwikset 10-Button Touch Deadbolt"
fingerprint profileId: "0104", inClusters: "0000,0001,0003,0004,0005,0009,0020,0101,0402,0B05,FDBD", outClusters: "000A,0019", fingerprint profileId: "0104", inClusters: "0000,0001,0003,0009,000A,0101,0020", outClusters: "000A,0019", manufacturer: "Yale", model: "YRL220 TS LL", deviceJoinName: "Yale Touch Screen Lever Lock"
manufacturer: "Kwikset", model: "SMARTCODE_DEADBOLT_10", deviceJoinName: "Kwikset 10-Button Deadbolt" fingerprint profileId: "0104", inClusters: "0000,0001,0003,0009,000A,0101,0020", outClusters: "000A,0019", manufacturer: "Yale", model: "YRD210 PB DB", deviceJoinName: "Yale Push Button Deadbolt Lock"
fingerprint profileId: "0104", inClusters: "0000,0001,0003,0004,0005,0009,0020,0101,0402,0B05,FDBD", outClusters: "000A,0019", fingerprint profileId: "0104", inClusters: "0000,0001,0003,0009,000A,0101,0020", outClusters: "000A,0019", manufacturer: "Yale", model: "YRD220/240 TSDB", deviceJoinName: "Yale Touch Screen Deadbolt Lock"
manufacturer: "Kwikset", model: "SMARTCODE_DEADBOLT_10T", deviceJoinName: "Kwikset 10-Button Touch Deadbolt" fingerprint profileId: "0104", inClusters: "0000,0001,0003,0009,000A,0101,0020", outClusters: "000A,0019", manufacturer: "Yale", model: "YRL210 PB LL", deviceJoinName: "Yale Push Button Lever Lock"
fingerprint profileId: "0104", inClusters: "0000,0001,0003,0009,000A,0101,0020", outClusters: "000A,0019",
manufacturer: "Yale", model: "YRL220 TS LL", deviceJoinName: "Yale Touch Screen Lever Lock"
fingerprint profileId: "0104", inClusters: "0000,0001,0003,0009,000A,0101,0020", outClusters: "000A,0019",
manufacturer: "Yale", model: "YRD210 PB DB", deviceJoinName: "Yale Push Button Deadbolt Lock"
fingerprint profileId: "0104", inClusters: "0000,0001,0003,0009,000A,0101,0020", outClusters: "000A,0019",
manufacturer: "Yale", model: "YRD220/240 TSDB", deviceJoinName: "Yale Touch Screen Deadbolt Lock"
fingerprint profileId: "0104", inClusters: "0000,0001,0003,0009,000A,0101,0020", outClusters: "000A,0019",
manufacturer: "Yale", model: "YRL210 PB LL", deviceJoinName: "Yale Push Button Lever Lock"
} }
tiles(scale: 2) { tiles(scale: 2) {

View File

@@ -1,322 +1,322 @@
/** /**
* Copyright 2015 SmartThings * Copyright 2015 SmartThings
* *
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * 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: * in compliance with the License. You may obtain a copy of the License at:
* *
* http://www.apache.org/licenses/LICENSE-2.0 * 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 * 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 * 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. * for the specific language governing permissions and limitations under the License.
* *
* Button Controller * Button Controller
* *
* Author: SmartThings * Author: SmartThings
* Date: 2014-5-21 * Date: 2014-5-21
*/ */
definition( definition(
name: "Button Controller", name: "Button Controller",
namespace: "smartthings", namespace: "smartthings",
author: "SmartThings", author: "SmartThings",
description: "Control devices with buttons like the Aeon Labs Minimote", description: "Control devices with buttons like the Aeon Labs Minimote",
category: "Convenience", category: "Convenience",
iconUrl: "https://s3.amazonaws.com/smartapp-icons/MyApps/Cat-MyApps.png", iconUrl: "https://s3.amazonaws.com/smartapp-icons/MyApps/Cat-MyApps.png",
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/MyApps/Cat-MyApps@2x.png" iconX2Url: "https://s3.amazonaws.com/smartapp-icons/MyApps/Cat-MyApps@2x.png"
) )
preferences { preferences {
page(name: "selectButton") page(name: "selectButton")
page(name: "configureButton1") page(name: "configureButton1")
page(name: "configureButton2") page(name: "configureButton2")
page(name: "configureButton3") page(name: "configureButton3")
page(name: "configureButton4") page(name: "configureButton4")
page(name: "timeIntervalInput", title: "Only during a certain time") { page(name: "timeIntervalInput", title: "Only during a certain time") {
section { section {
input "starting", "time", title: "Starting", required: false input "starting", "time", title: "Starting", required: false
input "ending", "time", title: "Ending", required: false input "ending", "time", title: "Ending", required: false
} }
} }
} }
def selectButton() { def selectButton() {
dynamicPage(name: "selectButton", title: "First, select your button device", nextPage: "configureButton1", uninstall: configured()) { dynamicPage(name: "selectButton", title: "First, select your button device", nextPage: "configureButton1", uninstall: configured()) {
section { section {
input "buttonDevice", "capability.button", title: "Button", multiple: false, required: true input "buttonDevice", "capability.button", title: "Button", multiple: false, required: true
} }
section(title: "More options", hidden: hideOptionsSection(), hideable: true) { section(title: "More options", hidden: hideOptionsSection(), hideable: true) {
def timeLabel = timeIntervalLabel() def timeLabel = timeIntervalLabel()
href "timeIntervalInput", title: "Only during a certain time", description: timeLabel ?: "Tap to set", state: timeLabel ? "complete" : null href "timeIntervalInput", title: "Only during a certain time", description: timeLabel ?: "Tap to set", state: timeLabel ? "complete" : null
input "days", "enum", title: "Only on certain days of the week", multiple: true, required: false, input "days", "enum", title: "Only on certain days of the week", multiple: true, required: false,
options: ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"] options: ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
input "modes", "mode", title: "Only when mode is", multiple: true, required: false input "modes", "mode", title: "Only when mode is", multiple: true, required: false
} }
} }
} }
def configureButton1() { def configureButton1() {
dynamicPage(name: "configureButton1", title: "Now let's decide how to use the first button", dynamicPage(name: "configureButton1", title: "Now let's decide how to use the first button",
nextPage: "configureButton2", uninstall: configured(), getButtonSections(1)) nextPage: "configureButton2", uninstall: configured(), getButtonSections(1))
} }
def configureButton2() { def configureButton2() {
dynamicPage(name: "configureButton2", title: "If you have a second button, set it up here", dynamicPage(name: "configureButton2", title: "If you have a second button, set it up here",
nextPage: "configureButton3", uninstall: configured(), getButtonSections(2)) nextPage: "configureButton3", uninstall: configured(), getButtonSections(2))
} }
def configureButton3() { def configureButton3() {
dynamicPage(name: "configureButton3", title: "If you have a third button, you can do even more here", dynamicPage(name: "configureButton3", title: "If you have a third button, you can do even more here",
nextPage: "configureButton4", uninstall: configured(), getButtonSections(3)) nextPage: "configureButton4", uninstall: configured(), getButtonSections(3))
} }
def configureButton4() { def configureButton4() {
dynamicPage(name: "configureButton4", title: "If you have a fourth button, you rule, and can set it up here", dynamicPage(name: "configureButton4", title: "If you have a fourth button, you rule, and can set it up here",
install: true, uninstall: true, getButtonSections(4)) install: true, uninstall: true, getButtonSections(4))
} }
def getButtonSections(buttonNumber) { def getButtonSections(buttonNumber) {
return { return {
section("Lights") { section("Lights") {
input "lights_${buttonNumber}_pushed", "capability.switch", title: "Pushed", multiple: true, required: false input "lights_${buttonNumber}_pushed", "capability.switch", title: "Pushed", multiple: true, required: false
input "lights_${buttonNumber}_held", "capability.switch", title: "Held", multiple: true, required: false input "lights_${buttonNumber}_held", "capability.switch", title: "Held", multiple: true, required: false
} }
section("Locks") { section("Locks") {
input "locks_${buttonNumber}_pushed", "capability.lock", title: "Pushed", multiple: true, required: false input "locks_${buttonNumber}_pushed", "capability.lock", title: "Pushed", multiple: true, required: false
input "locks_${buttonNumber}_held", "capability.lock", title: "Held", multiple: true, required: false input "locks_${buttonNumber}_held", "capability.lock", title: "Held", multiple: true, required: false
} }
section("Sonos") { section("Sonos") {
input "sonos_${buttonNumber}_pushed", "capability.musicPlayer", title: "Pushed", multiple: true, required: false input "sonos_${buttonNumber}_pushed", "capability.musicPlayer", title: "Pushed", multiple: true, required: false
input "sonos_${buttonNumber}_held", "capability.musicPlayer", title: "Held", multiple: true, required: false input "sonos_${buttonNumber}_held", "capability.musicPlayer", title: "Held", multiple: true, required: false
} }
section("Modes") { section("Modes") {
input "mode_${buttonNumber}_pushed", "mode", title: "Pushed", required: false input "mode_${buttonNumber}_pushed", "mode", title: "Pushed", required: false
input "mode_${buttonNumber}_held", "mode", title: "Held", required: false input "mode_${buttonNumber}_held", "mode", title: "Held", required: false
} }
def phrases = location.helloHome?.getPhrases()*.label def phrases = location.helloHome?.getPhrases()*.label
if (phrases) { if (phrases) {
section("Hello Home Actions") { section("Hello Home Actions") {
log.trace phrases log.trace phrases
input "phrase_${buttonNumber}_pushed", "enum", title: "Pushed", required: false, options: phrases input "phrase_${buttonNumber}_pushed", "enum", title: "Pushed", required: false, options: phrases
input "phrase_${buttonNumber}_held", "enum", title: "Held", required: false, options: phrases input "phrase_${buttonNumber}_held", "enum", title: "Held", required: false, options: phrases
} }
} }
section("Sirens") { section("Sirens") {
input "sirens_${buttonNumber}_pushed","capability.alarm" ,title: "Pushed", multiple: true, required: false input "sirens_${buttonNumber}_pushed","capability.alarm" ,title: "Pushed", multiple: true, required: false
input "sirens_${buttonNumber}_held", "capability.alarm", title: "Held", multiple: true, required: false input "sirens_${buttonNumber}_held", "capability.alarm", title: "Held", multiple: true, required: false
} }
section("Custom Message") { section("Custom Message") {
input "textMessage_${buttonNumber}", "text", title: "Message", required: false input "textMessage_${buttonNumber}", "text", title: "Message", required: false
} }
section("Push Notifications") { section("Push Notifications") {
input "notifications_${buttonNumber}_pushed","bool" ,title: "Pushed", required: false, defaultValue: false input "notifications_${buttonNumber}_pushed","bool" ,title: "Pushed", required: false, defaultValue: false
input "notifications_${buttonNumber}_held", "bool", title: "Held", required: false, defaultValue: false input "notifications_${buttonNumber}_held", "bool", title: "Held", required: false, defaultValue: false
} }
section("Sms Notifications") { section("Sms Notifications") {
input "phone_${buttonNumber}_pushed","phone" ,title: "Pushed", required: false input "phone_${buttonNumber}_pushed","phone" ,title: "Pushed", required: false
input "phone_${buttonNumber}_held", "phone", title: "Held", required: false input "phone_${buttonNumber}_held", "phone", title: "Held", required: false
} }
} }
} }
def installed() { def installed() {
initialize() initialize()
} }
def updated() { def updated() {
unsubscribe() unsubscribe()
initialize() initialize()
} }
def initialize() { def initialize() {
subscribe(buttonDevice, "button", buttonEvent) subscribe(buttonDevice, "button", buttonEvent)
} }
def configured() { def configured() {
return buttonDevice || buttonConfigured(1) || buttonConfigured(2) || buttonConfigured(3) || buttonConfigured(4) return buttonDevice || buttonConfigured(1) || buttonConfigured(2) || buttonConfigured(3) || buttonConfigured(4)
} }
def buttonConfigured(idx) { def buttonConfigured(idx) {
return settings["lights_$idx_pushed"] || return settings["lights_$idx_pushed"] ||
settings["locks_$idx_pushed"] || settings["locks_$idx_pushed"] ||
settings["sonos_$idx_pushed"] || settings["sonos_$idx_pushed"] ||
settings["mode_$idx_pushed"] || settings["mode_$idx_pushed"] ||
settings["notifications_$idx_pushed"] || settings["notifications_$idx_pushed"] ||
settings["sirens_$idx_pushed"] || settings["sirens_$idx_pushed"] ||
settings["notifications_$idx_pushed"] || settings["notifications_$idx_pushed"] ||
settings["phone_$idx_pushed"] settings["phone_$idx_pushed"]
} }
def buttonEvent(evt){ def buttonEvent(evt){
if(allOk) { if(allOk) {
def buttonNumber = evt.data // why doesn't jsonData work? always returning [:] def buttonNumber = evt.data // why doesn't jsonData work? always returning [:]
def value = evt.value def value = evt.value
log.debug "buttonEvent: $evt.name = $evt.value ($evt.data)" log.debug "buttonEvent: $evt.name = $evt.value ($evt.data)"
log.debug "button: $buttonNumber, value: $value" log.debug "button: $buttonNumber, value: $value"
def recentEvents = buttonDevice.eventsSince(new Date(now() - 3000)).findAll{it.value == evt.value && it.data == evt.data} def recentEvents = buttonDevice.eventsSince(new Date(now() - 3000)).findAll{it.value == evt.value && it.data == evt.data}
log.debug "Found ${recentEvents.size()?:0} events in past 3 seconds" log.debug "Found ${recentEvents.size()?:0} events in past 3 seconds"
if(recentEvents.size <= 1){ if(recentEvents.size <= 1){
switch(buttonNumber) { switch(buttonNumber) {
case ~/.*1.*/: case ~/.*1.*/:
executeHandlers(1, value) executeHandlers(1, value)
break break
case ~/.*2.*/: case ~/.*2.*/:
executeHandlers(2, value) executeHandlers(2, value)
break break
case ~/.*3.*/: case ~/.*3.*/:
executeHandlers(3, value) executeHandlers(3, value)
break break
case ~/.*4.*/: case ~/.*4.*/:
executeHandlers(4, value) executeHandlers(4, value)
break break
} }
} else { } else {
log.debug "Found recent button press events for $buttonNumber with value $value" log.debug "Found recent button press events for $buttonNumber with value $value"
} }
} }
} }
def executeHandlers(buttonNumber, value) { def executeHandlers(buttonNumber, value) {
log.debug "executeHandlers: $buttonNumber - $value" log.debug "executeHandlers: $buttonNumber - $value"
def lights = find('lights', buttonNumber, value) def lights = find('lights', buttonNumber, value)
if (lights != null) toggle(lights) if (lights != null) toggle(lights)
def locks = find('locks', buttonNumber, value) def locks = find('locks', buttonNumber, value)
if (locks != null) toggle(locks) if (locks != null) toggle(locks)
def sonos = find('sonos', buttonNumber, value) def sonos = find('sonos', buttonNumber, value)
if (sonos != null) toggle(sonos) if (sonos != null) toggle(sonos)
def mode = find('mode', buttonNumber, value) def mode = find('mode', buttonNumber, value)
if (mode != null) changeMode(mode) if (mode != null) changeMode(mode)
def phrase = find('phrase', buttonNumber, value) def phrase = find('phrase', buttonNumber, value)
if (phrase != null) location.helloHome.execute(phrase) if (phrase != null) location.helloHome.execute(phrase)
def textMessage = findMsg('textMessage', buttonNumber) def textMessage = findMsg('textMessage', buttonNumber)
def notifications = find('notifications', buttonNumber, value) def notifications = find('notifications', buttonNumber, value)
if (notifications?.toBoolean()) sendPush(textMessage ?: "Button $buttonNumber was pressed" ) if (notifications?.toBoolean()) sendPush(textMessage ?: "Button $buttonNumber was pressed" )
def phone = find('phone', buttonNumber, value) def phone = find('phone', buttonNumber, value)
if (phone != null) sendSms(phone, textMessage ?:"Button $buttonNumber was pressed") if (phone != null) sendSms(phone, textMessage ?:"Button $buttonNumber was pressed")
def sirens = find('sirens', buttonNumber, value) def sirens = find('sirens', buttonNumber, value)
if (sirens != null) toggle(sirens) if (sirens != null) toggle(sirens)
} }
def find(type, buttonNumber, value) { def find(type, buttonNumber, value) {
def preferenceName = type + "_" + buttonNumber + "_" + value def preferenceName = type + "_" + buttonNumber + "_" + value
def pref = settings[preferenceName] def pref = settings[preferenceName]
if(pref != null) { if(pref != null) {
log.debug "Found: $pref for $preferenceName" log.debug "Found: $pref for $preferenceName"
} }
return pref return pref
} }
def findMsg(type, buttonNumber) { def findMsg(type, buttonNumber) {
def preferenceName = type + "_" + buttonNumber def preferenceName = type + "_" + buttonNumber
def pref = settings[preferenceName] def pref = settings[preferenceName]
if(pref != null) { if(pref != null) {
log.debug "Found: $pref for $preferenceName" log.debug "Found: $pref for $preferenceName"
} }
return pref return pref
} }
def toggle(devices) { def toggle(devices) {
log.debug "toggle: $devices = ${devices*.currentValue('switch')}" log.debug "toggle: $devices = ${devices*.currentValue('switch')}"
if (devices*.currentValue('switch').contains('on')) { if (devices*.currentValue('switch').contains('on')) {
devices.off() devices.off()
} }
else if (devices*.currentValue('switch').contains('off')) { else if (devices*.currentValue('switch').contains('off')) {
devices.on() devices.on()
} }
else if (devices*.currentValue('lock').contains('locked')) { else if (devices*.currentValue('lock').contains('locked')) {
devices.unlock() devices.unlock()
} }
else if (devices*.currentValue('lock').contains('unlocked')) { else if (devices*.currentValue('lock').contains('unlocked')) {
devices.lock() devices.lock()
} }
else if (devices*.currentValue('alarm').contains('off')) { else if (devices*.currentValue('alarm').contains('off')) {
devices.siren() devices.siren()
} }
else { else {
devices.on() devices.on()
} }
} }
def changeMode(mode) { def changeMode(mode) {
log.debug "changeMode: $mode, location.mode = $location.mode, location.modes = $location.modes" log.debug "changeMode: $mode, location.mode = $location.mode, location.modes = $location.modes"
if (location.mode != mode && location.modes?.find { it.name == mode }) { if (location.mode != mode && location.modes?.find { it.name == mode }) {
setLocationMode(mode) setLocationMode(mode)
} }
} }
// execution filter methods // execution filter methods
private getAllOk() { private getAllOk() {
modeOk && daysOk && timeOk modeOk && daysOk && timeOk
} }
private getModeOk() { private getModeOk() {
def result = !modes || modes.contains(location.mode) def result = !modes || modes.contains(location.mode)
log.trace "modeOk = $result" log.trace "modeOk = $result"
result result
} }
private getDaysOk() { private getDaysOk() {
def result = true def result = true
if (days) { if (days) {
def df = new java.text.SimpleDateFormat("EEEE") def df = new java.text.SimpleDateFormat("EEEE")
if (location.timeZone) { if (location.timeZone) {
df.setTimeZone(location.timeZone) df.setTimeZone(location.timeZone)
} }
else { else {
df.setTimeZone(TimeZone.getTimeZone("America/New_York")) df.setTimeZone(TimeZone.getTimeZone("America/New_York"))
} }
def day = df.format(new Date()) def day = df.format(new Date())
result = days.contains(day) result = days.contains(day)
} }
log.trace "daysOk = $result" log.trace "daysOk = $result"
result result
} }
private getTimeOk() { private getTimeOk() {
def result = true def result = true
if (starting && ending) { if (starting && ending) {
def currTime = now() def currTime = now()
def start = timeToday(starting).time def start = timeToday(starting).time
def stop = timeToday(ending).time def stop = timeToday(ending).time
result = start < stop ? currTime >= start && currTime <= stop : currTime <= stop || currTime >= start result = start < stop ? currTime >= start && currTime <= stop : currTime <= stop || currTime >= start
} }
log.trace "timeOk = $result" log.trace "timeOk = $result"
result result
} }
private hhmm(time, fmt = "h:mm a") private hhmm(time, fmt = "h:mm a")
{ {
def t = timeToday(time, location.timeZone) def t = timeToday(time, location.timeZone)
def f = new java.text.SimpleDateFormat(fmt) def f = new java.text.SimpleDateFormat(fmt)
f.setTimeZone(location.timeZone ?: timeZone(time)) f.setTimeZone(location.timeZone ?: timeZone(time))
f.format(t) f.format(t)
} }
private hideOptionsSection() { private hideOptionsSection() {
(starting || ending || days || modes) ? false : true (starting || ending || days || modes) ? false : true
} }
private timeIntervalLabel() { private timeIntervalLabel() {
(starting && ending) ? hhmm(starting) + "-" + hhmm(ending, "h:mm a z") : "" (starting && ending) ? hhmm(starting) + "-" + hhmm(ending, "h:mm a z") : ""
} }

View File

@@ -235,6 +235,7 @@ def connectionStatus(message, redirectUrl = null) {
def getEcobeeThermostats() { def getEcobeeThermostats() {
log.debug "getting device list" log.debug "getting device list"
atomicState.remoteSensors = []
def requestBody = '{"selection":{"selectionType":"registered","selectionMatch":"","includeRuntime":true,"includeSensors":true}}' def requestBody = '{"selection":{"selectionType":"registered","selectionMatch":"","includeRuntime":true,"includeSensors":true}}'
@@ -251,7 +252,7 @@ def getEcobeeThermostats() {
if (resp.status == 200) { if (resp.status == 200) {
resp.data.thermostatList.each { stat -> resp.data.thermostatList.each { stat ->
atomicState.remoteSensors = stat.remoteSensors atomicState.remoteSensors = atomicState.remoteSensors == null ? stat.remoteSensors : atomicState.remoteSensors << stat.remoteSensors
def dni = [app.id, stat.identifier].join('.') def dni = [app.id, stat.identifier].join('.')
stats[dni] = getThermostatDisplayName(stat) stats[dni] = getThermostatDisplayName(stat)
} }
@@ -273,11 +274,14 @@ def getEcobeeThermostats() {
Map sensorsDiscovered() { Map sensorsDiscovered() {
def map = [:] def map = [:]
atomicState.remoteSensors.each { log.info "list ${atomicState.remoteSensors}"
if (it.type != "thermostat") { atomicState.remoteSensors.each { sensors ->
def value = "${it?.name}" sensors.each {
def key = "ecobee_sensor-"+ it?.id + "-" + it?.code if (it.type != "thermostat") {
map["${key}"] = value def value = "${it?.name}"
def key = "ecobee_sensor-"+ it?.id + "-" + it?.code
map["${key}"] = value
}
} }
} }
atomicState.sensors = map atomicState.sensors = map

View File

@@ -15,7 +15,7 @@
* for the specific language governing permissions and limitations under the License. * for the specific language governing permissions and limitations under the License.
* *
*/ */
definition( definition(
name: "Hue (Connect)", name: "Hue (Connect)",
namespace: "smartthings", namespace: "smartthings",
@@ -24,7 +24,7 @@ definition(
category: "SmartThings Labs", category: "SmartThings Labs",
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Partner/hue.png", iconUrl: "https://s3.amazonaws.com/smartapp-icons/Partner/hue.png",
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Partner/hue@2x.png", iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Partner/hue@2x.png",
singleInstance: true //singleInstance: true
) )
preferences { preferences {
@@ -58,7 +58,7 @@ def bridgeDiscovery(params=[:])
state.bridges = [:] state.bridges = [:]
state.bridgeRefreshCount = 0 state.bridgeRefreshCount = 0
app.updateSetting("selectedHue", "") app.updateSetting("selectedHue", "")
} }
subscribe(location, null, locationHandler, [filterEvents:false]) subscribe(location, null, locationHandler, [filterEvents:false])
@@ -130,8 +130,8 @@ def bulbDiscovery() {
def bulboptions = bulbsDiscovered() ?: [:] def bulboptions = bulbsDiscovered() ?: [:]
def numFound = bulboptions.size() ?: 0 def numFound = bulboptions.size() ?: 0
if (numFound == 0) if (numFound == 0)
app.updateSetting("selectedBulbs", "") app.updateSetting("selectedBulbs", "")
if((bulbRefreshCount % 5) == 0) { if((bulbRefreshCount % 5) == 0) {
discoverHueBulbs() discoverHueBulbs()
} }
@@ -140,7 +140,7 @@ def bulbDiscovery() {
section("Please wait while we discover your Hue Bulbs. Discovery can take five minutes or more, so sit back and relax! Select your device below once discovered.") { section("Please wait while we discover your Hue Bulbs. Discovery can take five minutes or more, so sit back and relax! Select your device below once discovered.") {
input "selectedBulbs", "enum", required:false, title:"Select Hue Bulbs (${numFound} found)", multiple:true, options:bulboptions input "selectedBulbs", "enum", required:false, title:"Select Hue Bulbs (${numFound} found)", multiple:true, options:bulboptions
} }
section { section {
def title = getBridgeIP() ? "Hue bridge (${getBridgeIP()})" : "Find bridges" def title = getBridgeIP() ? "Hue bridge (${getBridgeIP()})" : "Find bridges"
href "bridgeDiscovery", title: title, description: "", state: selectedHue ? "complete" : "incomplete", params: [override: true] href "bridgeDiscovery", title: title, description: "", state: selectedHue ? "complete" : "incomplete", params: [override: true]
@@ -246,13 +246,13 @@ def installed() {
def updated() { def updated() {
log.trace "Updated with settings: ${settings}" log.trace "Updated with settings: ${settings}"
unsubscribe() unsubscribe()
unschedule() unschedule()
initialize() initialize()
} }
def initialize() { def initialize() {
log.debug "Initializing" log.debug "Initializing"
unsubscribe(bridge) unsubscribe(bridge)
state.inBulbDiscovery = false state.inBulbDiscovery = false
state.bridgeRefreshCount = 0 state.bridgeRefreshCount = 0
@@ -281,18 +281,18 @@ def uninstalled(){
def bulbListHandler(hub, data = "") { def bulbListHandler(hub, data = "") {
def msg = "Bulbs list not processed. Only while in settings menu." def msg = "Bulbs list not processed. Only while in settings menu."
def bulbs = [:] def bulbs = [:]
if (state.inBulbDiscovery) { if (state.inBulbDiscovery) {
def logg = "" def logg = ""
log.trace "Adding bulbs to state..." log.trace "Adding bulbs to state..."
state.bridgeProcessedLightList = true state.bridgeProcessedLightList = true
def object = new groovy.json.JsonSlurper().parseText(data) def object = new groovy.json.JsonSlurper().parseText(data)
object.each { k,v -> object.each { k,v ->
if (v instanceof Map) if (v instanceof Map)
bulbs[k] = [id: k, name: v.name, type: v.type, hub:hub] bulbs[k] = [id: k, name: v.name, type: v.type, hub:hub]
} }
} }
def bridge = null def bridge = null
if (selectedHue) if (selectedHue)
bridge = getChildDevice(selectedHue) bridge = getChildDevice(selectedHue)
bridge.sendEvent(name: "bulbList", value: hub, data: bulbs, isStateChange: true, displayed: false) bridge.sendEvent(name: "bulbList", value: hub, data: bulbs, isStateChange: true, displayed: false)
msg = "${bulbs.size()} bulbs found. ${bulbs}" msg = "${bulbs.size()} bulbs found. ${bulbs}"
@@ -318,7 +318,7 @@ def addBulbs() {
} else { } else {
log.debug "$dni in not longer paired to the Hue Bridge or ID changed" log.debug "$dni in not longer paired to the Hue Bridge or ID changed"
} }
} else { } else {
//backwards compatable //backwards compatable
newHueBulb = bulbs.find { (app.id + "/" + it.id) == dni } newHueBulb = bulbs.find { (app.id + "/" + it.id) == dni }
d = addChildDevice("smartthings", "Hue Bulb", dni, newHueBulb?.hub, ["label":newHueBulb?.name]) d = addChildDevice("smartthings", "Hue Bulb", dni, newHueBulb?.hub, ["label":newHueBulb?.name])
@@ -344,7 +344,7 @@ def addBridge() {
def d = getChildDevice(selectedHue) def d = getChildDevice(selectedHue)
if(!d) { if(!d) {
// compatibility with old devices // compatibility with old devices
def newbridge = true def newbridge = true
childDevices.each { childDevices.each {
if (it.getDeviceDataByName("mac")) { if (it.getDeviceDataByName("mac")) {
def newDNI = "${it.getDeviceDataByName("mac")}" def newDNI = "${it.getDeviceDataByName("mac")}"
@@ -354,10 +354,10 @@ def addBridge() {
it.setDeviceNetworkId("${newDNI}") it.setDeviceNetworkId("${newDNI}")
if (oldDNI == selectedHue) if (oldDNI == selectedHue)
app.updateSetting("selectedHue", newDNI) app.updateSetting("selectedHue", newDNI)
newbridge = false newbridge = false
} }
} }
} }
if (newbridge) { if (newbridge) {
d = addChildDevice("smartthings", "Hue Bridge", selectedHue, vbridge.value.hub) d = addChildDevice("smartthings", "Hue Bridge", selectedHue, vbridge.value.hub)
log.debug "created ${d.displayName} with id ${d.deviceNetworkId}" log.debug "created ${d.displayName} with id ${d.deviceNetworkId}"
@@ -368,13 +368,13 @@ def addBridge() {
childDevice.sendEvent(name: "networkAddress", value: vbridge.value.ip + ":" + vbridge.value.port) childDevice.sendEvent(name: "networkAddress", value: vbridge.value.ip + ":" + vbridge.value.port)
childDevice.updateDataValue("networkAddress", vbridge.value.ip + ":" + vbridge.value.port) childDevice.updateDataValue("networkAddress", vbridge.value.ip + ":" + vbridge.value.port)
} else { } else {
childDevice.sendEvent(name: "networkAddress", value: convertHexToIP(vbridge.value.ip) + ":" + convertHexToInt(vbridge.value.port)) childDevice.sendEvent(name: "networkAddress", value: convertHexToIP(vbridge.value.ip) + ":" + convertHexToInt(vbridge.value.port))
childDevice.updateDataValue("networkAddress", convertHexToIP(vbridge.value.ip) + ":" + convertHexToInt(vbridge.value.port)) childDevice.updateDataValue("networkAddress", convertHexToIP(vbridge.value.ip) + ":" + convertHexToInt(vbridge.value.port))
} }
} else { } else {
childDevice.sendEvent(name: "networkAddress", value: convertHexToIP(vbridge.value.networkAddress) + ":" + convertHexToInt(vbridge.value.deviceAddress)) childDevice.sendEvent(name: "networkAddress", value: convertHexToIP(vbridge.value.networkAddress) + ":" + convertHexToInt(vbridge.value.deviceAddress))
childDevice.updateDataValue("networkAddress", convertHexToIP(vbridge.value.networkAddress) + ":" + convertHexToInt(vbridge.value.deviceAddress)) childDevice.updateDataValue("networkAddress", convertHexToIP(vbridge.value.networkAddress) + ":" + convertHexToInt(vbridge.value.deviceAddress))
} }
} }
} else { } else {
log.debug "found ${d.displayName} with id $selectedHue already exists" log.debug "found ${d.displayName} with id $selectedHue already exists"
@@ -436,7 +436,7 @@ def locationHandler(evt) {
dstate.name = "Philips hue ($ip)" dstate.name = "Philips hue ($ip)"
d.sendEvent(name:"networkAddress", value: host) d.sendEvent(name:"networkAddress", value: host)
d.updateDataValue("networkAddress", host) d.updateDataValue("networkAddress", host)
} }
} }
} }
} }
@@ -455,7 +455,7 @@ def locationHandler(evt) {
log.error "/description.xml returned a bridge that didn't exist" log.error "/description.xml returned a bridge that didn't exist"
} }
} }
} else if(headerString?.contains("json")) { } else if(headerString?.contains("json") && isValidSource(parsedEvent.mac)) {
log.trace "description.xml response (application/json)" log.trace "description.xml response (application/json)"
def body = new groovy.json.JsonSlurper().parseText(parsedEvent.body) def body = new groovy.json.JsonSlurper().parseText(parsedEvent.body)
if (body.success != null) { if (body.success != null) {
@@ -494,16 +494,21 @@ def doDeviceSync(){
discoverBridges() discoverBridges()
} }
def isValidSource(macAddress) {
def vbridges = getVerifiedHueBridges()
return (vbridges?.find {"${it.value.mac}" == macAddress}) != null
}
///////////////////////////////////// /////////////////////////////////////
//CHILD DEVICE METHODS //CHILD DEVICE METHODS
///////////////////////////////////// /////////////////////////////////////
def parse(childDevice, description) { def parse(childDevice, description) {
def parsedEvent = parseLanMessage(description) def parsedEvent = parseLanMessage(description)
if (parsedEvent.headers && parsedEvent.body) { if (parsedEvent.headers && parsedEvent.body) {
def headerString = parsedEvent.headers.toString() def headerString = parsedEvent.headers.toString()
def bodyString = parsedEvent.body.toString() def bodyString = parsedEvent.body.toString()
if (headerString?.contains("json")) { if (headerString?.contains("json")) {
def body def body
try { try {
body = new groovy.json.JsonSlurper().parseText(bodyString) body = new groovy.json.JsonSlurper().parseText(bodyString)
@@ -511,11 +516,11 @@ def parse(childDevice, description) {
log.warn "Parsing Body failed - trying again..." log.warn "Parsing Body failed - trying again..."
poll() poll()
} }
if (body instanceof java.util.HashMap) { if (body instanceof java.util.HashMap) {
//poll response //poll response
def bulbs = getChildDevices() def bulbs = getChildDevices()
for (bulb in body) { for (bulb in body) {
def d = bulbs.find{it.deviceNetworkId == "${app.id}/${bulb.key}"} def d = bulbs.find{it.deviceNetworkId == "${app.id}/${bulb.key}"}
if (d) { if (d) {
if (bulb.value.state?.reachable) { if (bulb.value.state?.reachable) {
sendEvent(d.deviceNetworkId, [name: "switch", value: bulb.value?.state?.on ? "on" : "off"]) sendEvent(d.deviceNetworkId, [name: "switch", value: bulb.value?.state?.on ? "on" : "off"])
@@ -530,18 +535,18 @@ def parse(childDevice, description) {
} }
} else { } else {
sendEvent(d.deviceNetworkId, [name: "switch", value: "off"]) sendEvent(d.deviceNetworkId, [name: "switch", value: "off"])
sendEvent(d.deviceNetworkId, [name: "level", value: 100]) sendEvent(d.deviceNetworkId, [name: "level", value: 100])
if (bulb.value.state.sat) { if (bulb.value.state.sat) {
def hue = 23 def hue = 23
def sat = 56 def sat = 56
def hex = colorUtil.hslToHex(23, 56) def hex = colorUtil.hslToHex(23, 56)
sendEvent(d.deviceNetworkId, [name: "color", value: hex]) sendEvent(d.deviceNetworkId, [name: "color", value: hex])
sendEvent(d.deviceNetworkId, [name: "hue", value: hue]) sendEvent(d.deviceNetworkId, [name: "hue", value: hue])
sendEvent(d.deviceNetworkId, [name: "saturation", value: sat]) sendEvent(d.deviceNetworkId, [name: "saturation", value: sat])
} }
} }
} }
} }
} }
else else
{ //put response { //put response
@@ -590,7 +595,7 @@ def parse(childDevice, description) {
} }
} }
} }
} else { } else {
log.debug "parse - got something other than headers,body..." log.debug "parse - got something other than headers,body..."
return [] return []
@@ -611,7 +616,7 @@ def off(childDevice) {
def setLevel(childDevice, percent) { def setLevel(childDevice, percent) {
log.debug "Executing 'setLevel'" log.debug "Executing 'setLevel'"
def level def level
if (percent == 1) level = 1 else level = Math.min(Math.round(percent * 255 / 100), 255) if (percent == 1) level = 1 else level = Math.min(Math.round(percent * 255 / 100), 255)
put("lights/${getId(childDevice)}/state", [bri: level, on: percent > 0]) put("lights/${getId(childDevice)}/state", [bri: level, on: percent > 0])
} }
@@ -628,6 +633,14 @@ def setHue(childDevice, percent) {
put("lights/${getId(childDevice)}/state", [hue: level]) put("lights/${getId(childDevice)}/state", [hue: level])
} }
def setColorTemperature(childDevice, huesettings) {
log.debug "Executing 'setColorTemperature($huesettings)'"
def ct = Math.round(Math.abs((huesettings / 12.96829971181556) - 654))
def value = [ct: ct, on: true]
log.trace "sending command $value"
put("lights/${getId(childDevice)}/state", value)
}
def setColor(childDevice, huesettings) { def setColor(childDevice, huesettings) {
log.debug "Executing 'setColor($huesettings)'" log.debug "Executing 'setColor($huesettings)'"
def hue = Math.min(Math.round(huesettings.hue * 65535 / 100), 65535) def hue = Math.min(Math.round(huesettings.hue * 65535 / 100), 65535)
@@ -684,7 +697,7 @@ HOST: ${host}
} }
private put(path, body) { private put(path, body) {
def host = getBridgeIP() def host = getBridgeIP()
def uri = "/api/${state.username}/$path" def uri = "/api/${state.username}/$path"
def bodyJSON = new groovy.json.JsonBuilder(body).toString() def bodyJSON = new groovy.json.JsonBuilder(body).toString()
def length = bodyJSON.getBytes().size().toString() def length = bodyJSON.getBytes().size().toString()
@@ -710,11 +723,11 @@ private getBridgeIP() {
host = d.getDeviceDataByName("networkAddress") host = d.getDeviceDataByName("networkAddress")
else else
host = d.latestState('networkAddress').stringValue host = d.latestState('networkAddress').stringValue
} }
if (host == null || host == "") { if (host == null || host == "") {
def serialNumber = selectedHue def serialNumber = selectedHue
def bridge = getHueBridges().find { it?.value?.serialNumber?.equalsIgnoreCase(serialNumber) }?.value def bridge = getHueBridges().find { it?.value?.serialNumber?.equalsIgnoreCase(serialNumber) }?.value
if (!bridge) { if (!bridge) {
bridge = getHueBridges().find { it?.value?.mac?.equalsIgnoreCase(serialNumber) }?.value bridge = getHueBridges().find { it?.value?.mac?.equalsIgnoreCase(serialNumber) }?.value
} }
if (bridge?.ip && bridge?.port) { if (bridge?.ip && bridge?.port) {
@@ -724,9 +737,9 @@ private getBridgeIP() {
host = "${convertHexToIP(bridge?.ip)}:${convertHexToInt(bridge?.port)}" host = "${convertHexToIP(bridge?.ip)}:${convertHexToInt(bridge?.port)}"
} else if (bridge?.networkAddress && bridge?.deviceAddress) } else if (bridge?.networkAddress && bridge?.deviceAddress)
host = "${convertHexToIP(bridge?.networkAddress)}:${convertHexToInt(bridge?.deviceAddress)}" host = "${convertHexToIP(bridge?.networkAddress)}:${convertHexToInt(bridge?.deviceAddress)}"
} }
log.trace "Bridge: $selectedHue - Host: $host" log.trace "Bridge: $selectedHue - Host: $host"
} }
return host return host
} }