diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c31893b --- /dev/null +++ b/.gitignore @@ -0,0 +1,23 @@ +# Eclipse files +/.settings +/.classpath +/.project +/eclipse/ +/target-eclipse + +# IntelliJ files +*.iws +*.iml +.idea +/out +*.ipr + +# Gradle files +.gradletasknamecache +.gradle/ + +# Mac OS files +.DS_Store + +# Build files +/build diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..481d91a --- /dev/null +++ b/build.gradle @@ -0,0 +1,57 @@ +import java.nio.charset.StandardCharsets +import java.nio.file.Paths + +apply plugin: 'groovy' +apply plugin: 'smartthings-executable-deployment' +apply plugin: 'smartthings-hipchat' + +buildscript { + dependencies { + classpath "com.smartthings.deployment:executable-deployment-scripts:1.0.3" + } + repositories { + jcenter() + maven { + credentials { + username smartThingsArtifactoryUserName + password smartThingsArtifactoryPassword + } + url "http://artifactory.smartthings.com/libs-release-local" + } + } +} + +repositories { + mavenLocal() + jcenter() +} + +dependencies { +} + +hipchatShareFile { + List archives = [] + File rootDir = new File("${project.buildDir}/archives") + if (rootDir.exists()) { + // Create a list of archives which were deployed. + java.nio.file.Path rootPath = Paths.get(rootDir.absolutePath) + rootDir.eachFileRecurse { File file -> + if (file.name.endsWith('.tar.gz')) { + java.nio.file.Path archivePath = Paths.get(file.absolutePath) + archives.add(rootPath.relativize(archivePath).toString()) + } + } + } + + // Set task properties + data = archives.join('\n').getBytes(StandardCharsets.UTF_8) + fileName = 'deployment-notes.txt' + contentType = 'text/html' +} + +hipchatSendNotification { + String branch = project.hasProperty('branch') ? project.property('branch') : 'unknown' + message = "Began executable deploy of SmartThingsPublic(${branch})." + color = branch == 'master' ? 'yellow' : 'red' + notify = true +} diff --git a/circle.yml b/circle.yml new file mode 100644 index 0000000..3fd00d4 --- /dev/null +++ b/circle.yml @@ -0,0 +1,27 @@ +machine: + java: + version: + oraclejdk8 + +dependencies: + override: + - echo "Nothing to do." + +test: + override: + - echo "We don't have any tests :-(" + +deployment: + develop: + branch: master + commands: + - ./gradlew deployArchives -PsmartThingsArtifactoryUserName=$ARTIFACTORY_USERNAME -PsmartThingsArtifactoryPassword=$ARTIFACTORY_PASSWORD -Ps3BucketName=$S3_BUCKET_NAME_PREPROD_DEV + - ./gradlew hipchatSendNotification -PsmartThingsArtifactoryUserName=$ARTIFACTORY_USERNAME -PsmartThingsArtifactoryPassword=$ARTIFACTORY_PASSWORD -Pbranch=$CIRCLE_BRANCH + - ./gradlew hipchatShareFile -PsmartThingsArtifactoryUserName=$ARTIFACTORY_USERNAME -PsmartThingsArtifactoryPassword=$ARTIFACTORY_PASSWORD + + stage: + branch: staging + commands: + - ./gradlew deployArchives -PsmartThingsArtifactoryUserName=$ARTIFACTORY_USERNAME -PsmartThingsArtifactoryPassword=$ARTIFACTORY_PASSWORD -Ps3BucketName=$S3_BUCKET_NAME_PREPROD_STAGING + - ./gradlew hipchatSendNotification -PsmartThingsArtifactoryUserName=$ARTIFACTORY_USERNAME -PsmartThingsArtifactoryPassword=$ARTIFACTORY_PASSWORD -Pbranch=$CIRCLE_BRANCH + - ./gradlew hipchatShareFile -PsmartThingsArtifactoryUserName=$ARTIFACTORY_USERNAME -PsmartThingsArtifactoryPassword=$ARTIFACTORY_PASSWORD diff --git a/devicetypes/smartthings/ecobee-sensor.src/ecobee-sensor.groovy b/devicetypes/smartthings/ecobee-sensor.src/ecobee-sensor.groovy index 2753e9b..f2583bf 100644 --- a/devicetypes/smartthings/ecobee-sensor.src/ecobee-sensor.groovy +++ b/devicetypes/smartthings/ecobee-sensor.src/ecobee-sensor.groovy @@ -1,7 +1,5 @@ /** - * Ecobee Sensor - * - * Copyright 2015 Juan Risso + * Copyright 2015 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: @@ -12,6 +10,9 @@ * 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. * + * Ecobee Sensor + * + * Author: SmartThings */ metadata { definition (name: "Ecobee Sensor", namespace: "smartthings", author: "SmartThings") { @@ -26,7 +27,16 @@ metadata { valueTile("temperature", "device.temperature", width: 2, height: 2) { state("temperature", label:'${currentValue}°', unit:"F", 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: 59, color: "#90d2a7"], [value: 74, color: "#44b621"], diff --git a/devicetypes/smartthings/ecobee-thermostat.src/ecobee-thermostat.groovy b/devicetypes/smartthings/ecobee-thermostat.src/ecobee-thermostat.groovy index 802449c..eb245d6 100644 --- a/devicetypes/smartthings/ecobee-thermostat.src/ecobee-thermostat.groovy +++ b/devicetypes/smartthings/ecobee-thermostat.src/ecobee-thermostat.groovy @@ -395,7 +395,7 @@ def generateModeEvent(mode) { } 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) { @@ -493,7 +493,7 @@ def fanOn() { } else { log.debug "Error setting new mode." 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 { log.debug "Error setting new mode." def currentFanMode = device.currentState("thermostatFanMode")?.value - generateModeEvent(currentFanMode) // reset the tile back + generateFanModeEvent(currentFanMode) // reset the tile back } } diff --git a/devicetypes/smartthings/hue-bulb.src/hue-bulb.groovy b/devicetypes/smartthings/hue-bulb.src/hue-bulb.groovy index 21f6e75..f49ec95 100644 --- a/devicetypes/smartthings/hue-bulb.src/hue-bulb.groovy +++ b/devicetypes/smartthings/hue-bulb.src/hue-bulb.groovy @@ -1,4 +1,3 @@ - /** * Hue Bulb * @@ -11,13 +10,14 @@ metadata { capability "Switch Level" capability "Actuator" capability "Color Control" + capability "Color Temperature" capability "Switch" capability "Refresh" capability "Sensor" command "setAdjustedColor" - command "reset" - command "refresh" + command "reset" + command "refresh" } simulator { @@ -25,7 +25,7 @@ metadata { } 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") { 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" @@ -33,23 +33,58 @@ metadata { attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn" } 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") { 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" } - 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" } - } + 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"]) - details(["switch", "levelSliderControl", "rgbSelector", "refresh", "reset"]) + main(["switch"]) + details(["rich-control", "colorTempSliderControl", "colorTemp", "reset", "refresh"]) + } } // parse events into attributes @@ -68,17 +103,17 @@ def parse(description) { } // handle commands -def on() { +void on() { log.trace parent.on(this) sendEvent(name: "switch", value: "on") } -def off() { +void off() { log.trace parent.off(this) sendEvent(name: "switch", value: "off") } -def nextLevel() { +void nextLevel() { def level = device.latestValue("level") as Integer ?: 0 if (level <= 100) { level = Math.min(25 * (Math.round(level / 25) + 1), 100) as Integer @@ -89,25 +124,25 @@ def nextLevel() { setLevel(level) } -def setLevel(percent) { +void setLevel(percent) { log.debug "Executing 'setLevel'" parent.setLevel(this, percent) sendEvent(name: "level", value: percent) } -def setSaturation(percent) { +void setSaturation(percent) { log.debug "Executing 'setSaturation'" parent.setSaturation(this, percent) sendEvent(name: "saturation", value: percent) } -def setHue(percent) { +void setHue(percent) { log.debug "Executing 'setHue'" parent.setHue(this, percent) sendEvent(name: "hue", value: percent) } -def setColor(value) { +void setColor(value) { log.debug "setColor: ${value}, $this" parent.setColor(this, value) 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)} } -def reset() { +void reset() { log.debug "Executing 'reset'" def value = [level:100, hex:"#90C638", saturation:56, hue:23] setAdjustedColor(value) parent.poll() } -def setAdjustedColor(value) { +void setAdjustedColor(value) { if (value) { log.trace "setAdjustedColor: ${value}" def adjusted = value + [:] adjusted.hue = adjustOutgoingHue(value.hue) // Needed because color picker always sends 100 - adjusted.level = null + adjusted.level = null 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'" parent.manualRefresh() } diff --git a/devicetypes/smartthings/hue-lux-bulb.src/hue-lux-bulb.groovy b/devicetypes/smartthings/hue-lux-bulb.src/hue-lux-bulb.groovy index 07b9326..49e7f2f 100644 --- a/devicetypes/smartthings/hue-lux-bulb.src/hue-lux-bulb.groovy +++ b/devicetypes/smartthings/hue-lux-bulb.src/hue-lux-bulb.groovy @@ -12,14 +12,14 @@ metadata { capability "Switch" capability "Refresh" capability "Sensor" - - command "refresh" + + command "refresh" } simulator { // TODO: define status and reply messages here } - + tiles(scale: 2) { multiAttributeTile(name:"rich-control", type: "lighting", canChangeIcon: true){ tileAttribute ("device.switch", key: "PRIMARY_CONTROL") { @@ -35,25 +35,25 @@ metadata { attributeState "level", label: 'Level ${currentValue}%' } } - + 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("levelSliderControl", "device.level", "slider", height: 1, width: 2, inactiveLabel: false, range:"(0..100)") { 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" - } + } main(["switch"]) details(["rich-control", "refresh"]) - } + } } // parse events into attributes @@ -74,23 +74,23 @@ def parse(description) { } // handle commands -def on() { +void on() { parent.on(this) sendEvent(name: "switch", value: "on") } -def off() { +void off() { parent.off(this) sendEvent(name: "switch", value: "off") } -def setLevel(percent) { +void setLevel(percent) { log.debug "Executing 'setLevel'" parent.setLevel(this, percent) sendEvent(name: "level", value: percent) } -def refresh() { +void refresh() { log.debug "Executing 'refresh'" parent.manualRefresh() } diff --git a/devicetypes/smartthings/lifx-color-bulb.src/lifx-color-bulb.groovy b/devicetypes/smartthings/lifx-color-bulb.src/lifx-color-bulb.groovy index 5513896..b93235e 100644 --- a/devicetypes/smartthings/lifx-color-bulb.src/lifx-color-bulb.groovy +++ b/devicetypes/smartthings/lifx-color-bulb.src/lifx-color-bulb.groovy @@ -84,6 +84,7 @@ def setHue(percentage) { log.error("Bad setHue result: [${resp.status}] ${resp.data}") } } + return [] } def setSaturation(percentage) { @@ -97,6 +98,7 @@ def setSaturation(percentage) { log.error("Bad setSaturation result: [${resp.status}] ${resp.data}") } } + return [] } def setColor(Map color) { @@ -122,13 +124,15 @@ def setColor(Map color) { parent.logErrors(logObject:log) { def resp = parent.apiPUT("/lights/${selector()}/state", [color: attrs.join(" "), power: "on"]) if (resp.status < 300) { - sendEvent(name: "color", value: color.hex) + if (color.hex) + sendEvent(name: "color", value: color.hex) sendEvent(name: "switch", value: "on") events.each { sendEvent(it) } } else { log.error("Bad setColor result: [${resp.status}] ${resp.data}") } } + return [] } def setLevel(percentage) { @@ -150,6 +154,7 @@ def setLevel(percentage) { log.error("Bad setLevel result: [${resp.status}] ${resp.data}") } } + return [] } def setColorTemperature(kelvin) { @@ -165,6 +170,7 @@ def setColorTemperature(kelvin) { } } + return [] } def on() { @@ -174,6 +180,7 @@ def on() { sendEvent(name: "switch", value: "on") } } + return [] } def off() { @@ -183,6 +190,7 @@ def off() { sendEvent(name: "switch", value: "off") } } + return [] } def poll() { diff --git a/devicetypes/smartthings/lifx-white-bulb.src/lifx-white-bulb.groovy b/devicetypes/smartthings/lifx-white-bulb.src/lifx-white-bulb.groovy index 09107c0..54a78df 100644 --- a/devicetypes/smartthings/lifx-white-bulb.src/lifx-white-bulb.groovy +++ b/devicetypes/smartthings/lifx-white-bulb.src/lifx-white-bulb.groovy @@ -84,6 +84,7 @@ def setLevel(percentage) { log.error("Bad setLevel result: [${resp.status}] ${resp.data}") } } + return [] } def setColorTemperature(kelvin) { @@ -99,6 +100,7 @@ def setColorTemperature(kelvin) { log.error("Bad setColorTemperature result: [${resp.status}] ${resp.data}") } } + return [] } def on() { @@ -108,6 +110,7 @@ def on() { sendEvent(name: "switch", value: "on") } } + return [] } def off() { @@ -117,6 +120,7 @@ def off() { sendEvent(name: "switch", value: "off") } } + return [] } def poll() { diff --git a/devicetypes/smartthings/mobile-presence.src/i18n/messages.properties b/devicetypes/smartthings/mobile-presence.src/i18n/messages.properties new file mode 100644 index 0000000..bfe484e --- /dev/null +++ b/devicetypes/smartthings/mobile-presence.src/i18n/messages.properties @@ -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=부재중 diff --git a/devicetypes/smartthings/samsung-smart-tv.src/samsung-smart-tv.groovy b/devicetypes/smartthings/samsung-smart-tv.src/samsung-smart-tv.groovy deleted file mode 100644 index f53a6c3..0000000 --- a/devicetypes/smartthings/samsung-smart-tv.src/samsung-smart-tv.groovy +++ /dev/null @@ -1,235 +0,0 @@ -/** - * Copyright 2015 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. - * - * Samsung TV - * - * Author: SmartThings (juano23@gmail.com) - * Date: 2015-01-08 - */ - -metadata { - definition (name: "Samsung Smart TV", namespace: "smartthings", author: "SmartThings") { - capability "switch" - - command "mute" - command "source" - command "menu" - command "tools" - command "HDMI" - command "Sleep" - command "Up" - command "Down" - command "Left" - command "Right" - command "chup" - command "chdown" - command "prech" - command "volup" - command "voldown" - command "Enter" - command "Return" - command "Exit" - command "Info" - command "Size" - } - - standardTile("switch", "device.switch", width: 1, height: 1, canChangeIcon: true) { - state "default", label:'TV', action:"switch.off", icon:"st.Electronics.electronics15", backgroundColor:"#ffffff" - } - standardTile("power", "device.switch", width: 1, height: 1, canChangeIcon: false) { - state "default", label:'', action:"switch.off", decoration: "flat", icon:"st.thermostat.heating-cooling-off", backgroundColor:"#ffffff" - } - standardTile("mute", "device.switch", decoration: "flat", canChangeIcon: false) { - state "default", label:'Mute', action:"mute", icon:"st.custom.sonos.muted", backgroundColor:"#ffffff" - } - standardTile("source", "device.switch", decoration: "flat", canChangeIcon: false) { - state "default", label:'Source', action:"source", icon:"st.Electronics.electronics15" - } - standardTile("tools", "device.switch", decoration: "flat", canChangeIcon: false) { - state "default", label:'Tools', action:"tools", icon:"st.secondary.tools" - } - standardTile("menu", "device.switch", decoration: "flat", canChangeIcon: false) { - state "default", label:'Menu', action:"menu", icon:"st.vents.vent" - } - standardTile("HDMI", "device.switch", decoration: "flat", canChangeIcon: false) { - state "default", label:'Source', action:"HDMI", icon:"st.Electronics.electronics15" - } - standardTile("Sleep", "device.switch", decoration: "flat", canChangeIcon: false) { - state "default", label:'Sleep', action:"Sleep", icon:"st.Bedroom.bedroom10" - } - standardTile("Up", "device.switch", decoration: "flat", canChangeIcon: false) { - state "default", label:'Up', action:"Up", icon:"st.thermostat.thermostat-up" - } - standardTile("Down", "device.switch", decoration: "flat", canChangeIcon: false) { - state "default", label:'Down', action:"Down", icon:"st.thermostat.thermostat-down" - } - standardTile("Left", "device.switch", decoration: "flat", canChangeIcon: false) { - state "default", label:'Left', action:"Left", icon:"st.thermostat.thermostat-left" - } - standardTile("Right", "device.switch", decoration: "flat", canChangeIcon: false) { - state "default", label:'Right', action:"Right", icon:"st.thermostat.thermostat-right" - } - standardTile("chup", "device.switch", decoration: "flat", canChangeIcon: false) { - state "default", label:'CH Up', action:"chup", icon:"st.thermostat.thermostat-up" - } - standardTile("chdown", "device.switch", decoration: "flat", canChangeIcon: false) { - state "default", label:'CH Down', action:"chdown", icon:"st.thermostat.thermostat-down" - } - standardTile("prech", "device.switch", decoration: "flat", canChangeIcon: false) { - state "default", label:'Pre CH', action:"prech", icon:"st.secondary.refresh-icon" - } - standardTile("volup", "device.switch", decoration: "flat", canChangeIcon: false) { - state "default", label:'Vol Up', action:"volup", icon:"st.thermostat.thermostat-up" - } - standardTile("voldown", "device.switch", decoration: "flat", canChangeIcon: false) { - state "default", label:'Vol Down', action:"voldown", icon:"st.thermostat.thermostat-down" - } - standardTile("Enter", "device.switch", decoration: "flat", canChangeIcon: false) { - state "default", label:'Enter', action:"Enter", icon:"st.illuminance.illuminance.dark" - } - standardTile("Return", "device.switch", decoration: "flat", canChangeIcon: false) { - state "default", label:'Return', action:"Return", icon:"st.secondary.refresh-icon" - } - standardTile("Exit", "device.switch", decoration: "flat", canChangeIcon: false) { - state "default", label:'Exit', action:"Exit", icon:"st.locks.lock.unlocked" - } - standardTile("Info", "device.switch", decoration: "flat", canChangeIcon: false) { - state "default", label:'Info', action:"Info", icon:"st.motion.acceleration.active" - } - standardTile("Size", "device.switch", decoration: "flat", canChangeIcon: false) { - state "default", label:'Picture Size', action:"Size", icon:"st.contact.contact.open" - } - main "switch" - details (["power","HDMI","Sleep","chup","prech","volup","chdown","mute","voldown", "menu", "Up", "tools", "Left", "Enter", "Right", "Return", "Down", "Exit", "Info","Size"]) -} - -def parse(String description) { - return null -} - -def off() { - log.debug "Turning TV OFF" - parent.tvAction("POWEROFF",device.deviceNetworkId) - sendEvent(name:"Command", value: "Power Off", displayed: true) -} - -def mute() { - log.trace "MUTE pressed" - parent.tvAction("MUTE",device.deviceNetworkId) - sendEvent(name:"Command", value: "Mute", displayed: true) -} - -def source() { - log.debug "SOURCE pressed" - parent.tvAction("SOURCE",device.deviceNetworkId) - sendEvent(name:"Command", value: "Source", displayed: true) -} - -def menu() { - log.debug "MENU pressed" - parent.tvAction("MENU",device.deviceNetworkId) -} - -def tools() { - log.debug "TOOLS pressed" - parent.tvAction("TOOLS",device.deviceNetworkId) - sendEvent(name:"Command", value: "Tools", displayed: true) -} - -def HDMI() { - log.debug "HDMI pressed" - parent.tvAction("HDMI",device.deviceNetworkId) - sendEvent(name:"Command sent", value: "Source", displayed: true) -} - -def Sleep() { - log.debug "SLEEP pressed" - parent.tvAction("SLEEP",device.deviceNetworkId) - sendEvent(name:"Command", value: "Sleep", displayed: true) -} - -def Up() { - log.debug "UP pressed" - parent.tvAction("UP",device.deviceNetworkId) -} - -def Down() { - log.debug "DOWN pressed" - parent.tvAction("DOWN",device.deviceNetworkId) -} - -def Left() { - log.debug "LEFT pressed" - parent.tvAction("LEFT",device.deviceNetworkId) -} - -def Right() { - log.debug "RIGHT pressed" - parent.tvAction("RIGHT",device.deviceNetworkId) -} - -def chup() { - log.debug "CHUP pressed" - parent.tvAction("CHUP",device.deviceNetworkId) - sendEvent(name:"Command", value: "Channel Up", displayed: true) -} - -def chdown() { - log.debug "CHDOWN pressed" - parent.tvAction("CHDOWN",device.deviceNetworkId) - sendEvent(name:"Command", value: "Channel Down", displayed: true) -} - -def prech() { - log.debug "PRECH pressed" - parent.tvAction("PRECH",device.deviceNetworkId) - sendEvent(name:"Command", value: "Prev Channel", displayed: true) -} - -def Exit() { - log.debug "EXIT pressed" - parent.tvAction("EXIT",device.deviceNetworkId) -} - -def volup() { - log.debug "VOLUP pressed" - parent.tvAction("VOLUP",device.deviceNetworkId) - sendEvent(name:"Command", value: "Volume Up", displayed: true) -} - -def voldown() { - log.debug "VOLDOWN pressed" - parent.tvAction("VOLDOWN",device.deviceNetworkId) - sendEvent(name:"Command", value: "Volume Down", displayed: true) -} - -def Enter() { - log.debug "ENTER pressed" - parent.tvAction("ENTER",device.deviceNetworkId) -} - -def Return() { - log.debug "RETURN pressed" - parent.tvAction("RETURN",device.deviceNetworkId) -} - -def Info() { - log.debug "INFO pressed" - parent.tvAction("INFO",device.deviceNetworkId) - sendEvent(name:"Command", value: "Info", displayed: true) -} - -def Size() { - log.debug "PICTURE_SIZE pressed" - parent.tvAction("PICTURE_SIZE",device.deviceNetworkId) - sendEvent(name:"Command", value: "Picture Size", displayed: true) -} \ No newline at end of file diff --git a/devicetypes/smartthings/smartsense-moisture-sensor.src/i18n/messages.properties b/devicetypes/smartthings/smartsense-moisture-sensor.src/i18n/messages.properties new file mode 100644 index 0000000..307fbb8 --- /dev/null +++ b/devicetypes/smartthings/smartsense-moisture-sensor.src/i18n/messages.properties @@ -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였습니다 diff --git a/devicetypes/smartthings/smartsense-motion-sensor.src/i18n/messages.properties b/devicetypes/smartthings/smartsense-motion-sensor.src/i18n/messages.properties new file mode 100644 index 0000000..f1b7c52 --- /dev/null +++ b/devicetypes/smartthings/smartsense-motion-sensor.src/i18n/messages.properties @@ -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였습니다 diff --git a/devicetypes/smartthings/smartsense-multi-sensor.src/i18n/messages.properties b/devicetypes/smartthings/smartsense-multi-sensor.src/i18n/messages.properties new file mode 100644 index 0000000..2326d40 --- /dev/null +++ b/devicetypes/smartthings/smartsense-multi-sensor.src/i18n/messages.properties @@ -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였습니다 diff --git a/devicetypes/smartthings/smartsense-multi-sensor.src/smartsense-multi-sensor.groovy b/devicetypes/smartthings/smartsense-multi-sensor.src/smartsense-multi-sensor.groovy index de53cb3..01df653 100644 --- a/devicetypes/smartthings/smartsense-multi-sensor.src/smartsense-multi-sensor.groovy +++ b/devicetypes/smartthings/smartsense-multi-sensor.src/smartsense-multi-sensor.groovy @@ -100,9 +100,6 @@ 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) { state "battery", label:'${currentValue}% battery', unit:"" } @@ -112,7 +109,7 @@ metadata { main(["status", "acceleration", "temperature"]) - details(["status", "acceleration", "temperature", "3axis", "battery", "refresh"]) + details(["status", "acceleration", "temperature", "battery", "refresh"]) } } diff --git a/devicetypes/smartthings/smartsense-multi.src/smartsense-multi.groovy b/devicetypes/smartthings/smartsense-multi.src/smartsense-multi.groovy index a6d2d14..5024e42 100644 --- a/devicetypes/smartthings/smartsense-multi.src/smartsense-multi.groovy +++ b/devicetypes/smartthings/smartsense-multi.src/smartsense-multi.groovy @@ -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) { state "battery", label:'${currentValue}% battery', unit:"" } main(["contact", "acceleration", "temperature"]) - details(["contact", "acceleration", "temperature", "3axis", "battery"]) + details(["contact", "acceleration", "temperature", "battery"]) } } diff --git a/devicetypes/smartthings/zigbee-lock.src/zigbee-lock.groovy b/devicetypes/smartthings/zigbee-lock.src/zigbee-lock.groovy index 11d3eec..e779bde 100644 --- a/devicetypes/smartthings/zigbee-lock.src/zigbee-lock.groovy +++ b/devicetypes/smartthings/zigbee-lock.src/zigbee-lock.groovy @@ -23,22 +23,14 @@ capability "Battery" capability "Configuration" - 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" - 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", - manufacturer: "Kwikset", model: "SMARTCODE_DEADBOLT_10", deviceJoinName: "Kwikset 10-Button Deadbolt" - 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,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" + 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" + 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", manufacturer: "Kwikset", model: "SMARTCODE_DEADBOLT_10", deviceJoinName: "Kwikset 10-Button Deadbolt" + 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,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) { diff --git a/devicetypes/smartthings/zwave-motion-sensor.src/zwave-motion-sensor.groovy b/devicetypes/smartthings/zwave-motion-sensor.src/zwave-motion-sensor.groovy index b46f9c6..8618631 100644 --- a/devicetypes/smartthings/zwave-motion-sensor.src/zwave-motion-sensor.groovy +++ b/devicetypes/smartthings/zwave-motion-sensor.src/zwave-motion-sensor.groovy @@ -119,6 +119,10 @@ def zwaveEvent(physicalgraph.zwave.commands.notificationv3.NotificationReport cm def zwaveEvent(physicalgraph.zwave.commands.wakeupv1.WakeUpNotification cmd) { def result = [createEvent(descriptionText: "${device.displayName} woke up", isStateChange: false)] + + if (state.MSR == "011A-0601-0901" && device.currentState('motion') == null) { // Enerwave motion doesn't always get the associationSet that the hub sends on join + result << response(zwave.associationV1.associationSet(groupingIdentifier:1, nodeId:zwaveHubNodeId)) + } if (!state.lastbat || (new Date().time) - state.lastbat > 53*60*60*1000) { result << response(zwave.batteryV1.batteryGet()) result << response("delay 1200") @@ -179,4 +183,4 @@ def zwaveEvent(physicalgraph.zwave.commands.manufacturerspecificv2.ManufacturerS result << createEvent(descriptionText: "$device.displayName MSR: $msr", isStateChange: false) result -} +} \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..13372ae Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..a6c8b87 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Thu Feb 25 08:56:06 CST 2016 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-bin.zip diff --git a/gradlew b/gradlew new file mode 100755 index 0000000..9d82f78 --- /dev/null +++ b/gradlew @@ -0,0 +1,160 @@ +#!/usr/bin/env bash + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; +esac + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..8a0b282 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,90 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..bbdd996 --- /dev/null +++ b/settings.gradle @@ -0,0 +1,19 @@ +/* + * This settings file was auto generated by the Gradle buildInit task + * by 'jblaisdell' at '2/25/16 8:56 AM' with Gradle 2.10 + * + * The settings file is used to specify which projects to include in your build. + * In a single project build this file can be empty or even removed. + * + * Detailed information about configuring a multi-project build in Gradle can be found + * in the user guide at https://docs.gradle.org/2.10/userguide/multi_project_builds.html + */ + +/* +// To declare projects as part of a multi-project build use the 'include' method +include 'shared' +include 'api' +include 'services:webservice' +*/ + +rootProject.name = 'SmartThingsPublic' diff --git a/smartapps/smartthings/bose-soundtouch-connect.src/bose-soundtouch-connect.groovy b/smartapps/smartthings/bose-soundtouch-connect.src/bose-soundtouch-connect.groovy index bcb5d8f..2916215 100644 --- a/smartapps/smartthings/bose-soundtouch-connect.src/bose-soundtouch-connect.groovy +++ b/smartapps/smartthings/bose-soundtouch-connect.src/bose-soundtouch-connect.groovy @@ -353,7 +353,7 @@ def onLocation(evt) { } else if ( lanEvent.headers && lanEvent.body && - lanEvent.headers."content-type".contains("xml") + lanEvent.headers."content-type"?.contains("xml") ) { def parsers = getParsers() diff --git a/smartapps/smartthings/ecobee-connect.src/ecobee-connect.groovy b/smartapps/smartthings/ecobee-connect.src/ecobee-connect.groovy index 0ab4aa4..ea32f56 100644 --- a/smartapps/smartthings/ecobee-connect.src/ecobee-connect.groovy +++ b/smartapps/smartthings/ecobee-connect.src/ecobee-connect.groovy @@ -235,6 +235,7 @@ def connectionStatus(message, redirectUrl = null) { def getEcobeeThermostats() { log.debug "getting device list" + atomicState.remoteSensors = [] def requestBody = '{"selection":{"selectionType":"registered","selectionMatch":"","includeRuntime":true,"includeSensors":true}}' @@ -251,7 +252,7 @@ def getEcobeeThermostats() { if (resp.status == 200) { 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('.') stats[dni] = getThermostatDisplayName(stat) } @@ -273,11 +274,14 @@ def getEcobeeThermostats() { Map sensorsDiscovered() { def map = [:] - atomicState.remoteSensors.each { - if (it.type != "thermostat") { - def value = "${it?.name}" - def key = "ecobee_sensor-"+ it?.id + "-" + it?.code - map["${key}"] = value + log.info "list ${atomicState.remoteSensors}" + atomicState.remoteSensors.each { sensors -> + sensors.each { + if (it.type != "thermostat") { + def value = "${it?.name}" + def key = "ecobee_sensor-"+ it?.id + "-" + it?.code + map["${key}"] = value + } } } atomicState.sensors = map diff --git a/smartapps/smartthings/hue-connect.src/hue-connect.groovy b/smartapps/smartthings/hue-connect.src/hue-connect.groovy index b8d3f54..93453ac 100644 --- a/smartapps/smartthings/hue-connect.src/hue-connect.groovy +++ b/smartapps/smartthings/hue-connect.src/hue-connect.groovy @@ -15,7 +15,7 @@ * for the specific language governing permissions and limitations under the License. * */ - + definition( name: "Hue (Connect)", namespace: "smartthings", @@ -24,7 +24,7 @@ definition( category: "SmartThings Labs", iconUrl: "https://s3.amazonaws.com/smartapp-icons/Partner/hue.png", iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Partner/hue@2x.png", - singleInstance: true + //singleInstance: true ) preferences { @@ -58,7 +58,7 @@ def bridgeDiscovery(params=[:]) state.bridges = [:] state.bridgeRefreshCount = 0 app.updateSetting("selectedHue", "") - } + } subscribe(location, null, locationHandler, [filterEvents:false]) @@ -130,8 +130,8 @@ def bulbDiscovery() { def bulboptions = bulbsDiscovered() ?: [:] def numFound = bulboptions.size() ?: 0 if (numFound == 0) - app.updateSetting("selectedBulbs", "") - + app.updateSetting("selectedBulbs", "") + if((bulbRefreshCount % 5) == 0) { 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.") { 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" href "bridgeDiscovery", title: title, description: "", state: selectedHue ? "complete" : "incomplete", params: [override: true] @@ -246,13 +246,13 @@ def installed() { def updated() { log.trace "Updated with settings: ${settings}" - unsubscribe() - unschedule() + unsubscribe() + unschedule() initialize() } def initialize() { - log.debug "Initializing" + log.debug "Initializing" unsubscribe(bridge) state.inBulbDiscovery = false state.bridgeRefreshCount = 0 @@ -281,18 +281,18 @@ def uninstalled(){ def bulbListHandler(hub, data = "") { def msg = "Bulbs list not processed. Only while in settings menu." def bulbs = [:] - if (state.inBulbDiscovery) { + if (state.inBulbDiscovery) { def logg = "" log.trace "Adding bulbs to state..." state.bridgeProcessedLightList = true - def object = new groovy.json.JsonSlurper().parseText(data) + def object = new groovy.json.JsonSlurper().parseText(data) object.each { k,v -> - if (v instanceof Map) + if (v instanceof Map) bulbs[k] = [id: k, name: v.name, type: v.type, hub:hub] } - } + } def bridge = null - if (selectedHue) + if (selectedHue) bridge = getChildDevice(selectedHue) bridge.sendEvent(name: "bulbList", value: hub, data: bulbs, isStateChange: true, displayed: false) msg = "${bulbs.size()} bulbs found. ${bulbs}" @@ -318,7 +318,7 @@ def addBulbs() { } else { log.debug "$dni in not longer paired to the Hue Bridge or ID changed" } - } else { + } else { //backwards compatable newHueBulb = bulbs.find { (app.id + "/" + it.id) == dni } d = addChildDevice("smartthings", "Hue Bulb", dni, newHueBulb?.hub, ["label":newHueBulb?.name]) @@ -344,7 +344,7 @@ def addBridge() { def d = getChildDevice(selectedHue) if(!d) { // compatibility with old devices - def newbridge = true + def newbridge = true childDevices.each { if (it.getDeviceDataByName("mac")) { def newDNI = "${it.getDeviceDataByName("mac")}" @@ -354,10 +354,10 @@ def addBridge() { it.setDeviceNetworkId("${newDNI}") if (oldDNI == selectedHue) app.updateSetting("selectedHue", newDNI) - newbridge = false + newbridge = false } - } - } + } + } if (newbridge) { d = addChildDevice("smartthings", "Hue Bridge", selectedHue, vbridge.value.hub) 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.updateDataValue("networkAddress", vbridge.value.ip + ":" + vbridge.value.port) } 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)) - } + } } else { childDevice.sendEvent(name: "networkAddress", value: convertHexToIP(vbridge.value.networkAddress) + ":" + convertHexToInt(vbridge.value.deviceAddress)) childDevice.updateDataValue("networkAddress", convertHexToIP(vbridge.value.networkAddress) + ":" + convertHexToInt(vbridge.value.deviceAddress)) - } + } } } else { log.debug "found ${d.displayName} with id $selectedHue already exists" @@ -436,7 +436,7 @@ def locationHandler(evt) { dstate.name = "Philips hue ($ip)" d.sendEvent(name:"networkAddress", value: host) d.updateDataValue("networkAddress", host) - } + } } } } @@ -455,7 +455,7 @@ def locationHandler(evt) { 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)" def body = new groovy.json.JsonSlurper().parseText(parsedEvent.body) if (body.success != null) { @@ -494,16 +494,21 @@ def doDeviceSync(){ discoverBridges() } +def isValidSource(macAddress) { + def vbridges = getVerifiedHueBridges() + return (vbridges?.find {"${it.value.mac}" == macAddress}) != null +} + ///////////////////////////////////// //CHILD DEVICE METHODS ///////////////////////////////////// def parse(childDevice, description) { - def parsedEvent = parseLanMessage(description) + def parsedEvent = parseLanMessage(description) if (parsedEvent.headers && parsedEvent.body) { def headerString = parsedEvent.headers.toString() def bodyString = parsedEvent.body.toString() - if (headerString?.contains("json")) { + if (headerString?.contains("json")) { def body try { body = new groovy.json.JsonSlurper().parseText(bodyString) @@ -511,11 +516,11 @@ def parse(childDevice, description) { log.warn "Parsing Body failed - trying again..." poll() } - if (body instanceof java.util.HashMap) { + if (body instanceof java.util.HashMap) { //poll response def bulbs = getChildDevices() 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 (bulb.value.state?.reachable) { sendEvent(d.deviceNetworkId, [name: "switch", value: bulb.value?.state?.on ? "on" : "off"]) @@ -530,18 +535,18 @@ def parse(childDevice, description) { } } else { 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) { def hue = 23 def sat = 56 def hex = colorUtil.hslToHex(23, 56) sendEvent(d.deviceNetworkId, [name: "color", value: hex]) sendEvent(d.deviceNetworkId, [name: "hue", value: hue]) - sendEvent(d.deviceNetworkId, [name: "saturation", value: sat]) - } + sendEvent(d.deviceNetworkId, [name: "saturation", value: sat]) + } } } - } + } } else { //put response @@ -590,7 +595,7 @@ def parse(childDevice, description) { } } - } + } } else { log.debug "parse - got something other than headers,body..." return [] @@ -611,7 +616,7 @@ def off(childDevice) { def setLevel(childDevice, percent) { log.debug "Executing 'setLevel'" - def level + def level 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]) } @@ -628,6 +633,14 @@ def setHue(childDevice, percent) { 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) { log.debug "Executing 'setColor($huesettings)'" def hue = Math.min(Math.round(huesettings.hue * 65535 / 100), 65535) @@ -684,7 +697,7 @@ HOST: ${host} } private put(path, body) { - def host = getBridgeIP() + def host = getBridgeIP() def uri = "/api/${state.username}/$path" def bodyJSON = new groovy.json.JsonBuilder(body).toString() def length = bodyJSON.getBytes().size().toString() @@ -710,11 +723,11 @@ private getBridgeIP() { host = d.getDeviceDataByName("networkAddress") else host = d.latestState('networkAddress').stringValue - } + } if (host == null || host == "") { def serialNumber = selectedHue def bridge = getHueBridges().find { it?.value?.serialNumber?.equalsIgnoreCase(serialNumber) }?.value - if (!bridge) { + if (!bridge) { bridge = getHueBridges().find { it?.value?.mac?.equalsIgnoreCase(serialNumber) }?.value } if (bridge?.ip && bridge?.port) { @@ -724,9 +737,9 @@ private getBridgeIP() { host = "${convertHexToIP(bridge?.ip)}:${convertHexToInt(bridge?.port)}" } else if (bridge?.networkAddress && bridge?.deviceAddress) host = "${convertHexToIP(bridge?.networkAddress)}:${convertHexToInt(bridge?.deviceAddress)}" - } + } log.trace "Bridge: $selectedHue - Host: $host" - } + } return host } diff --git a/smartapps/smartthings/logitech-harmony-connect.src/logitech-harmony-connect.groovy b/smartapps/smartthings/logitech-harmony-connect.src/logitech-harmony-connect.groovy index 9fc1a5d..5cdfdff 100644 --- a/smartapps/smartthings/logitech-harmony-connect.src/logitech-harmony-connect.groovy +++ b/smartapps/smartthings/logitech-harmony-connect.src/logitech-harmony-connect.groovy @@ -89,7 +89,7 @@ mappings { } def getServerUrl() { return "https://graph.api.smartthings.com" } -def getCallbackUrl() { "https://graph.api.smartthings.com/oauth/callback" } +def getServercallbackUrl() { "https://graph.api.smartthings.com/oauth/callback" } def getBuildRedirectUrl() { "${serverUrl}/oauth/initialize?appId=${app.id}&access_token=${state.accessToken}&apiServerUrl=${apiServerUrl}" } def authPage() { @@ -166,7 +166,7 @@ def callback() { def init() { log.debug "Requesting Code" - def oauthParams = [client_id: "${appSettings.clientId}", scope: "remote", response_type: "code", redirect_uri: "${callbackUrl}" ] + def oauthParams = [client_id: "${appSettings.clientId}", scope: "remote", response_type: "code", redirect_uri: "${servercallbackUrl}" ] redirect(location: "https://home.myharmony.com/oauth2/authorize?${toQueryString(oauthParams)}") } diff --git a/smartapps/smartthings/samsung-tv-connect.src/samsung-tv-connect.groovy b/smartapps/smartthings/samsung-tv-connect.src/samsung-tv-connect.groovy deleted file mode 100644 index 79bcb68..0000000 --- a/smartapps/smartthings/samsung-tv-connect.src/samsung-tv-connect.groovy +++ /dev/null @@ -1,401 +0,0 @@ -/** - * Copyright 2015 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. - * - * Samsung TV Service Manager - * - * Author: SmartThings (Juan Risso) - */ - -definition( - name: "Samsung TV (Connect)", - namespace: "smartthings", - author: "SmartThings", - description: "Allows you to control your Samsung TV from the SmartThings app. Perform basic functions like power Off, source, volume, channels and other remote control functions.", - category: "SmartThings Labs", - iconUrl: "https://s3.amazonaws.com/smartapp-icons/Samsung/samsung-remote%402x.png", - iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Samsung/samsung-remote%403x.png", - singleInstance: true -) - -preferences { - page(name:"samsungDiscovery", title:"Samsung TV Setup", content:"samsungDiscovery", refreshTimeout:5) -} - -def getDeviceType() { - return "urn:samsung.com:device:RemoteControlReceiver:1" -} - -//PAGES -def samsungDiscovery() -{ - if(canInstallLabs()) - { - int samsungRefreshCount = !state.samsungRefreshCount ? 0 : state.samsungRefreshCount as int - state.samsungRefreshCount = samsungRefreshCount + 1 - def refreshInterval = 3 - - def options = samsungesDiscovered() ?: [] - - def numFound = options.size() ?: 0 - - if(!state.subscribe) { - log.trace "subscribe to location" - subscribe(location, null, locationHandler, [filterEvents:false]) - state.subscribe = true - } - - //samsung discovery request every 5 //25 seconds - if((samsungRefreshCount % 5) == 0) { - log.trace "Discovering..." - discoversamsunges() - } - - //setup.xml request every 3 seconds except on discoveries - if(((samsungRefreshCount % 1) == 0) && ((samsungRefreshCount % 8) != 0)) { - log.trace "Verifing..." - verifysamsungPlayer() - } - - return dynamicPage(name:"samsungDiscovery", title:"Discovery Started!", nextPage:"", refreshInterval:refreshInterval, install:true, uninstall: true) { - section("Please wait while we discover your Samsung TV. Discovery can take five minutes or more, so sit back and relax! Select your device below once discovered.") { - input "selectedsamsung", "enum", required:false, title:"Select Samsung TV (${numFound} found)", multiple:true, options:options - } - } - } - else - { - def upgradeNeeded = """To use SmartThings Labs, your Hub should be completely up to date. - -To update your Hub, access Location Settings in the Main Menu (tap the gear next to your location name), select your Hub, and choose "Update Hub".""" - - return dynamicPage(name:"samsungDiscovery", title:"Upgrade needed!", nextPage:"", install:true, uninstall: true) { - section("Upgrade") { - paragraph "$upgradeNeeded" - } - } - } -} - -def installed() { - log.trace "Installed with settings: ${settings}" - initialize() -} - -def updated() { - log.trace "Updated with settings: ${settings}" - unschedule() - initialize() -} - -def uninstalled() { - def devices = getChildDevices() - log.trace "deleting ${devices.size()} samsung" - devices.each { - deleteChildDevice(it.deviceNetworkId) - } -} - -def initialize() { - // remove location subscription afterwards - if (selectedsamsung) { - addsamsung() - } - //Check every 5 minutes for IP change - runEvery5Minutes("discoversamsunges") -} - -//CHILD DEVICE METHODS -def addsamsung() { - def players = getVerifiedsamsungPlayer() - log.trace "Adding childs" - selectedsamsung.each { dni -> - def d = getChildDevice(dni) - if(!d) { - def newPlayer = players.find { (it.value.ip + ":" + it.value.port) == dni } - log.trace "newPlayer = $newPlayer" - log.trace "dni = $dni" - d = addChildDevice("smartthings", "Samsung Smart TV", dni, newPlayer?.value.hub, [label:"${newPlayer?.value.name}"]) - log.trace "created ${d.displayName} with id $dni" - - d.setModel(newPlayer?.value.model) - log.trace "setModel to ${newPlayer?.value.model}" - } else { - log.trace "found ${d.displayName} with id $dni already exists" - } - } -} - -private tvAction(key,deviceNetworkId) { - log.debug "Executing ${tvCommand}" - - def tvs = getVerifiedsamsungPlayer() - def thetv = tvs.find { (it.value.ip + ":" + it.value.port) == deviceNetworkId } - - // Standard Connection Data - def appString = "iphone..iapp.samsung" - def appStringLength = appString.getBytes().size() - - def tvAppString = "iphone.UN60ES8000.iapp.samsung" - def tvAppStringLength = tvAppString.getBytes().size() - - def remoteName = "SmartThings".encodeAsBase64().toString() - def remoteNameLength = remoteName.getBytes().size() - - // Device Connection Data - def ipAddress = convertHexToIP(thetv?.value.ip).encodeAsBase64().toString() - def ipAddressHex = deviceNetworkId.substring(0,8) - def ipAddressLength = ipAddress.getBytes().size() - - def macAddress = thetv?.value.mac.encodeAsBase64().toString() - def macAddressLength = macAddress.getBytes().size() - - // The Authentication Message - def authenticationMessage = "${(char)0x64}${(char)0x00}${(char)ipAddressLength}${(char)0x00}${ipAddress}${(char)macAddressLength}${(char)0x00}${macAddress}${(char)remoteNameLength}${(char)0x00}${remoteName}" - def authenticationMessageLength = authenticationMessage.getBytes().size() - - def authenticationPacket = "${(char)0x00}${(char)appStringLength}${(char)0x00}${appString}${(char)authenticationMessageLength}${(char)0x00}${authenticationMessage}" - - // If our initial run, just send the authentication packet so the prompt appears on screen - if (key == "AUTHENTICATE") { - sendHubCommand(new physicalgraph.device.HubAction(authenticationPacket, physicalgraph.device.Protocol.LAN, "${ipAddressHex}:D6D8")) - } else { - // Build the command we will send to the Samsung TV - def command = "KEY_${key}".encodeAsBase64().toString() - def commandLength = command.getBytes().size() - - def actionMessage = "${(char)0x00}${(char)0x00}${(char)0x00}${(char)commandLength}${(char)0x00}${command}" - def actionMessageLength = actionMessage.getBytes().size() - - def actionPacket = "${(char)0x00}${(char)tvAppStringLength}${(char)0x00}${tvAppString}${(char)actionMessageLength}${(char)0x00}${actionMessage}" - - // Send both the authentication and action at the same time - sendHubCommand(new physicalgraph.device.HubAction(authenticationPacket + actionPacket, physicalgraph.device.Protocol.LAN, "${ipAddressHex}:D6D8")) - } -} - -private discoversamsunges() -{ - sendHubCommand(new physicalgraph.device.HubAction("lan discovery ${getDeviceType()}", physicalgraph.device.Protocol.LAN)) -} - - -private verifysamsungPlayer() { - def devices = getsamsungPlayer().findAll { it?.value?.verified != true } - - if(devices) { - log.warn "UNVERIFIED PLAYERS!: $devices" - } - - devices.each { - verifysamsung((it?.value?.ip + ":" + it?.value?.port), it?.value?.ssdpPath) - } -} - -private verifysamsung(String deviceNetworkId, String devicessdpPath) { - log.trace "dni: $deviceNetworkId, ssdpPath: $devicessdpPath" - String ip = getHostAddress(deviceNetworkId) - log.trace "ip:" + ip - sendHubCommand(new physicalgraph.device.HubAction("""GET ${devicessdpPath} HTTP/1.1\r\nHOST: $ip\r\n\r\n""", physicalgraph.device.Protocol.LAN, "${deviceNetworkId}")) -} - -Map samsungesDiscovered() { - def vsamsunges = getVerifiedsamsungPlayer() - def map = [:] - vsamsunges.each { - def value = "${it.value.name}" - def key = it.value.ip + ":" + it.value.port - map["${key}"] = value - } - log.trace "Devices discovered $map" - map -} - -def getsamsungPlayer() -{ - state.samsunges = state.samsunges ?: [:] -} - -def getVerifiedsamsungPlayer() -{ - getsamsungPlayer().findAll{ it?.value?.verified == true } -} - -def locationHandler(evt) { - def description = evt.description - def hub = evt?.hubId - def parsedEvent = parseEventMessage(description) - parsedEvent << ["hub":hub] - log.trace "${parsedEvent}" - log.trace "${getDeviceType()} - ${parsedEvent.ssdpTerm}" - if (parsedEvent?.ssdpTerm?.contains(getDeviceType())) - { //SSDP DISCOVERY EVENTS - - log.trace "TV found" - def samsunges = getsamsungPlayer() - - if (!(samsunges."${parsedEvent.ssdpUSN.toString()}")) - { //samsung does not exist - log.trace "Adding Device to state..." - samsunges << ["${parsedEvent.ssdpUSN.toString()}":parsedEvent] - } - else - { // update the values - - log.trace "Device was already found in state..." - - def d = samsunges."${parsedEvent.ssdpUSN.toString()}" - boolean deviceChangedValues = false - - if(d.ip != parsedEvent.ip || d.port != parsedEvent.port) { - d.ip = parsedEvent.ip - d.port = parsedEvent.port - deviceChangedValues = true - log.trace "Device's port or ip changed..." - } - - if (deviceChangedValues) { - def children = getChildDevices() - children.each { - if (it.getDeviceDataByName("mac") == parsedEvent.mac) { - log.trace "updating dni for device ${it} with mac ${parsedEvent.mac}" - it.setDeviceNetworkId((parsedEvent.ip + ":" + parsedEvent.port)) //could error if device with same dni already exists - } - } - } - } - } - else if (parsedEvent.headers && parsedEvent.body) - { // samsung RESPONSES - def deviceHeaders = parseLanMessage(description, false) - def type = deviceHeaders.headers."content-type" - def body - log.trace "REPONSE TYPE: $type" - if (type?.contains("xml")) - { // description.xml response (application/xml) - body = new XmlSlurper().parseText(deviceHeaders.body) - log.debug body.device.deviceType.text() - if (body?.device?.deviceType?.text().contains(getDeviceType())) - { - def samsunges = getsamsungPlayer() - def player = samsunges.find {it?.key?.contains(body?.device?.UDN?.text())} - if (player) - { - player.value << [name:body?.device?.friendlyName?.text(),model:body?.device?.modelName?.text(), serialNumber:body?.device?.serialNum?.text(), verified: true] - } - else - { - log.error "The xml file returned a device that didn't exist" - } - } - } - else if(type?.contains("json")) - { //(application/json) - body = new groovy.json.JsonSlurper().parseText(bodyString) - log.trace "GOT JSON $body" - } - - } - else { - log.trace "TV not found..." - //log.trace description - } -} - -private def parseEventMessage(String description) { - def event = [:] - def parts = description.split(',') - parts.each { part -> - part = part.trim() - if (part.startsWith('devicetype:')) { - part -= "devicetype:" - event.devicetype = part.trim() - } - else if (part.startsWith('mac:')) { - part -= "mac:" - event.mac = part.trim() - } - else if (part.startsWith('networkAddress:')) { - part -= "networkAddress:" - event.ip = part.trim() - } - else if (part.startsWith('deviceAddress:')) { - part -= "deviceAddress:" - event.port = part.trim() - } - else if (part.startsWith('ssdpPath:')) { - part -= "ssdpPath:" - event.ssdpPath = part.trim() - } - else if (part.startsWith('ssdpUSN:')) { - part -= "ssdpUSN:" - event.ssdpUSN = part.trim() - } - else if (part.startsWith('ssdpTerm:')) { - part -= "ssdpTerm:" - event.ssdpTerm = part.trim() - } - else if (part.startsWith('headers')) { - part -= "headers:" - event.headers = part.trim() - } - else if (part.startsWith('body')) { - part -= "body:" - event.body = part.trim() - } - } - event -} - -def parse(childDevice, description) { - def parsedEvent = parseEventMessage(description) - - if (parsedEvent.headers && parsedEvent.body) { - def headerString = new String(parsedEvent.headers.decodeBase64()) - def bodyString = new String(parsedEvent.body.decodeBase64()) - log.trace "parse() - ${bodyString}" - - def body = new groovy.json.JsonSlurper().parseText(bodyString) - } else { - log.trace "parse - got something other than headers,body..." - return [] - } -} - -private Integer convertHexToInt(hex) { - Integer.parseInt(hex,16) -} - -private String convertHexToIP(hex) { - [convertHexToInt(hex[0..1]),convertHexToInt(hex[2..3]),convertHexToInt(hex[4..5]),convertHexToInt(hex[6..7])].join(".") -} - -private getHostAddress(d) { - def parts = d.split(":") - def ip = convertHexToIP(parts[0]) - def port = convertHexToInt(parts[1]) - return ip + ":" + port -} - -private Boolean canInstallLabs() -{ - return hasAllHubsOver("000.011.00603") -} - -private Boolean hasAllHubsOver(String desiredFirmware) -{ - return realHubFirmwareVersions.every { fw -> fw >= desiredFirmware } -} - -private List getRealHubFirmwareVersions() -{ - return location.hubs*.firmwareVersionString.findAll { it } -} \ No newline at end of file diff --git a/smartapps/smartthings/wemo-connect.src/wemo-connect.groovy b/smartapps/smartthings/wemo-connect.src/wemo-connect.groovy index 0002c37..6213f7b 100644 --- a/smartapps/smartthings/wemo-connect.src/wemo-connect.groovy +++ b/smartapps/smartthings/wemo-connect.src/wemo-connect.groovy @@ -341,8 +341,12 @@ def ssdpSwitchHandler(evt) { deviceChangedValues = true log.debug "Device's port or ip changed..." def child = getChildDevice(parsedEvent.mac) - child.subscribe(parsedEvent.ip, parsedEvent.port) - child.poll() + if (child) { + child.subscribe(parsedEvent.ip, parsedEvent.port) + child.poll() + } else { + log.debug "Device with mac $parsedEvent.mac not found" + } } } } @@ -410,8 +414,12 @@ def ssdpLightSwitchHandler(evt) { deviceChangedValues = true log.debug "Device's port or ip changed..." def child = getChildDevice(parsedEvent.mac) - log.debug "updating ip and port, and resubscribing, for device with mac ${parsedEvent.mac}" - child.subscribe(parsedEvent.ip, parsedEvent.port) + if (child) { + log.debug "updating ip and port, and resubscribing, for device with mac ${parsedEvent.mac}" + child.subscribe(parsedEvent.ip, parsedEvent.port) + } else { + log.debug "Device with mac $parsedEvent.mac not found" + } } } } @@ -463,8 +471,12 @@ def locationHandler(evt) { deviceChangedValues = true log.debug "Device's port or ip changed..." def child = getChildDevice(parsedEvent.mac) - child.subscribe(parsedEvent.ip, parsedEvent.port) - child.poll() + if (child) { + child.subscribe(parsedEvent.ip, parsedEvent.port) + child.poll() + } else { + log.debug "Device with mac $parsedEvent.mac not found" + } } } } @@ -518,8 +530,12 @@ def locationHandler(evt) { deviceChangedValues = true log.debug "Device's port or ip changed..." def child = getChildDevice(parsedEvent.mac) - log.debug "updating ip and port, and resubscribing, for device with mac ${parsedEvent.mac}" - child.subscribe(parsedEvent.ip, parsedEvent.port) + if (child) { + log.debug "updating ip and port, and resubscribing, for device with mac ${parsedEvent.mac}" + child.subscribe(parsedEvent.ip, parsedEvent.port) + } else { + log.debug "Device with mac $parsedEvent.mac not found" + } } } }