mirror of
https://github.com/mtan93/SmartThingsPublic.git
synced 2026-03-09 13:21:53 +00:00
Compare commits
57 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2f8aac9b4c | ||
|
|
4da9730319 | ||
|
|
25db4f5235 | ||
|
|
eae2a9ca08 | ||
|
|
2dd2d7cba4 | ||
|
|
f5708bca8b | ||
|
|
a9da6d130a | ||
|
|
3a2c6f86be | ||
|
|
71d2b89a37 | ||
|
|
b131ba1507 | ||
|
|
f04a9e3f7a | ||
|
|
6a905e4380 | ||
|
|
442f16680d | ||
|
|
1c68099b52 | ||
|
|
cc9321ca9f | ||
|
|
919c9b88ee | ||
|
|
2438942071 | ||
|
|
0d9bd98cfa | ||
|
|
9e33f190c3 | ||
|
|
c959691536 | ||
|
|
2cb687dca9 | ||
|
|
70ec8a28f8 | ||
|
|
e255316474 | ||
|
|
934449c326 | ||
|
|
9e37a37991 | ||
|
|
050da829d3 | ||
|
|
f616dcbdf6 | ||
|
|
8933510436 | ||
|
|
3130e6ad27 | ||
|
|
0c87601c64 | ||
|
|
d1a5a8631f | ||
|
|
d86dcfd82f | ||
|
|
2184c2b21a | ||
|
|
16126abb2d | ||
|
|
77525c3377 | ||
|
|
c17b856e6b | ||
|
|
42b790ef10 | ||
|
|
a4ebe87f4e | ||
|
|
f84e21d83a | ||
|
|
85175eb298 | ||
|
|
c75568bcf1 | ||
|
|
a9c078c0cb | ||
|
|
edc98e4840 | ||
|
|
fb2c2cb2a7 | ||
|
|
62aeb0533d | ||
|
|
fb6cbcc35e | ||
|
|
097584944e | ||
|
|
01fae3dcd4 | ||
|
|
6c125fe80f | ||
|
|
ae705deba4 | ||
|
|
69a6fc4f9e | ||
|
|
5728f08770 | ||
|
|
f073df0a57 | ||
|
|
d82a387c68 | ||
|
|
9263107f0e | ||
|
|
20d660e236 | ||
|
|
6c5b93da87 |
23
.gitignore
vendored
Normal file
23
.gitignore
vendored
Normal file
@@ -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
|
||||
57
build.gradle
Normal file
57
build.gradle
Normal file
@@ -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<String> 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
|
||||
}
|
||||
27
circle.yml
Normal file
27
circle.yml
Normal file
@@ -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
|
||||
@@ -120,7 +120,7 @@ def setInstallSmartApp(value){
|
||||
}
|
||||
|
||||
def parse(String description) {
|
||||
|
||||
log.debug description
|
||||
def description_map = parseDescriptionAsMap(description)
|
||||
def event_name = ""
|
||||
def measurement_map = [
|
||||
@@ -129,10 +129,7 @@ def parse(String description) {
|
||||
zigbeedeviceid: device.zigbeeId,
|
||||
created: new Date().time /1000 as int
|
||||
]
|
||||
if (description_map.cluster == "0000"){
|
||||
/* version number, not used */
|
||||
|
||||
} else if (description_map.cluster == "0001"){
|
||||
if (description_map.cluster == "0001"){
|
||||
/* battery voltage in mV (device needs minimium 2.1v to run) */
|
||||
log.debug "PlantLink - id ${device.zigbeeId} battery ${description_map.value}"
|
||||
event_name = "battery_status"
|
||||
@@ -158,6 +155,10 @@ def parse(String description) {
|
||||
def parseDescriptionAsMap(description) {
|
||||
(description - "read attr - ").split(",").inject([:]) { map, param ->
|
||||
def nameAndValue = param.split(":")
|
||||
map += [(nameAndValue[0].trim()):nameAndValue[1].trim()]
|
||||
if(nameAndValue.length == 2){
|
||||
map += [(nameAndValue[0].trim()):nameAndValue[1].trim()]
|
||||
}else{
|
||||
map += []
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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"],
|
||||
|
||||
@@ -45,7 +45,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"],
|
||||
@@ -103,7 +112,7 @@ metadata {
|
||||
state "humidity", label:'${currentValue}%'
|
||||
}
|
||||
main "temperature"
|
||||
details(["temperature", "upButtonControl", "thermostatSetpoint", "currentStatus", "downButtonControl", "mode", "fanMode","resumeProgram", "humidity", "refresh"])
|
||||
details(["temperature", "upButtonControl", "thermostatSetpoint", "currentStatus", "downButtonControl", "mode", "fanMode","humidity", "resumeProgram", "refresh"])
|
||||
}
|
||||
|
||||
preferences {
|
||||
@@ -204,11 +213,11 @@ private getThermostatDescriptionText(name, value, linkText) {
|
||||
|
||||
void setHeatingSetpoint(setpoint) {
|
||||
log.debug "***heating setpoint $setpoint"
|
||||
def heatingSetpoint = setpoint.toDouble()
|
||||
def coolingSetpoint = device.currentValue("coolingSetpoint").toDouble()
|
||||
def heatingSetpoint = setpoint
|
||||
def coolingSetpoint = device.currentValue("coolingSetpoint")
|
||||
def deviceId = device.deviceNetworkId.split(/\./).last()
|
||||
def maxHeatingSetpoint = device.currentValue("maxHeatingSetpoint").toDouble()
|
||||
def minHeatingSetpoint = device.currentValue("minHeatingSetpoint").toDouble()
|
||||
def maxHeatingSetpoint = device.currentValue("maxHeatingSetpoint")
|
||||
def minHeatingSetpoint = device.currentValue("minHeatingSetpoint")
|
||||
|
||||
//enforce limits of heatingSetpoint
|
||||
if (heatingSetpoint > maxHeatingSetpoint) {
|
||||
@@ -241,11 +250,11 @@ void setHeatingSetpoint(setpoint) {
|
||||
|
||||
void setCoolingSetpoint(setpoint) {
|
||||
log.debug "***cooling setpoint $setpoint"
|
||||
def heatingSetpoint = device.currentValue("heatingSetpoint").toDouble()
|
||||
def coolingSetpoint = setpoint.toDouble()
|
||||
def heatingSetpoint = device.currentValue("heatingSetpoint")
|
||||
def coolingSetpoint = setpoint
|
||||
def deviceId = device.deviceNetworkId.split(/\./).last()
|
||||
def maxCoolingSetpoint = device.currentValue("maxCoolingSetpoint").toDouble()
|
||||
def minCoolingSetpoint = device.currentValue("minCoolingSetpoint").toDouble()
|
||||
def maxCoolingSetpoint = device.currentValue("maxCoolingSetpoint")
|
||||
def minCoolingSetpoint = device.currentValue("minCoolingSetpoint")
|
||||
|
||||
|
||||
if (coolingSetpoint > maxCoolingSetpoint) {
|
||||
@@ -386,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) {
|
||||
@@ -421,6 +430,10 @@ def heat() {
|
||||
generateStatusEvent()
|
||||
}
|
||||
|
||||
def emergencyHeat() {
|
||||
auxHeatOnly()
|
||||
}
|
||||
|
||||
def auxHeatOnly() {
|
||||
log.debug "auxHeatOnly"
|
||||
def deviceId = device.deviceNetworkId.split(/\./).last()
|
||||
@@ -480,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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -501,13 +514,10 @@ 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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
def generateSetpointEvent() {
|
||||
|
||||
log.debug "Generate SetPoint Event"
|
||||
@@ -536,6 +546,7 @@ def generateSetpointEvent() {
|
||||
coolingSetpoint = roundC(coolingSetpoint)
|
||||
}
|
||||
|
||||
|
||||
sendEvent("name":"maxHeatingSetpoint", "value":maxHeatingSetpoint, "unit":location.temperatureScale)
|
||||
sendEvent("name":"maxCoolingSetpoint", "value":maxCoolingSetpoint, "unit":location.temperatureScale)
|
||||
sendEvent("name":"minHeatingSetpoint", "value":minHeatingSetpoint, "unit":location.temperatureScale)
|
||||
|
||||
@@ -16,8 +16,8 @@ metadata {
|
||||
capability "Sensor"
|
||||
|
||||
command "setAdjustedColor"
|
||||
command "reset"
|
||||
command "refresh"
|
||||
command "reset"
|
||||
command "refresh"
|
||||
}
|
||||
|
||||
simulator {
|
||||
@@ -68,17 +68,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 +89,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 +117,25 @@ 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)
|
||||
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
|
||||
setColor(adjusted)
|
||||
}
|
||||
log.trace "setAdjustedColor: ${value}"
|
||||
def adjusted = value + [:]
|
||||
adjusted.hue = adjustOutgoingHue(value.hue)
|
||||
// Needed because color picker always sends 100
|
||||
adjusted.level = null
|
||||
setColor(adjusted)
|
||||
}
|
||||
}
|
||||
|
||||
def refresh() {
|
||||
void refresh() {
|
||||
log.debug "Executing 'refresh'"
|
||||
parent.manualRefresh()
|
||||
}
|
||||
|
||||
@@ -12,48 +12,48 @@ 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") {
|
||||
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 "turningOn", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff"
|
||||
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", range:"(0..100)"
|
||||
}
|
||||
tileAttribute ("device.level", key: "SECONDARY_CONTROL") {
|
||||
attributeState "level", label: 'Level ${currentValue}%'
|
||||
|
||||
tiles(scale: 2) {
|
||||
multiAttributeTile(name:"rich-control", type: "lighting", 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"
|
||||
attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff"
|
||||
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", range:"(0..100)"
|
||||
}
|
||||
tileAttribute ("device.level", key: "SECONDARY_CONTROL") {
|
||||
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") {
|
||||
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
|
||||
}
|
||||
}
|
||||
|
||||
main(["switch"])
|
||||
details(["rich-control", "refresh"])
|
||||
}
|
||||
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") {
|
||||
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()
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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=부재중
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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였습니다
|
||||
@@ -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였습니다
|
||||
@@ -29,6 +29,7 @@ metadata {
|
||||
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3305"
|
||||
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3325"
|
||||
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3326"
|
||||
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3326-L", deviceJoinName: "Iris Motion Sensor"
|
||||
fingerprint inClusters: "0000,0001,0003,000F,0020,0402,0500", outClusters: "0019", manufacturer: "SmartThings", model: "motionv4", deviceJoinName: "Motion Sensor"
|
||||
}
|
||||
|
||||
|
||||
@@ -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였습니다
|
||||
@@ -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"])
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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"])
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,17 +16,18 @@
|
||||
|
||||
metadata {
|
||||
definition (name: "SmartSense Open/Closed Sensor", namespace: "smartthings", author: "SmartThings") {
|
||||
capability "Battery"
|
||||
capability "Battery"
|
||||
capability "Configuration"
|
||||
capability "Contact Sensor"
|
||||
capability "Contact Sensor"
|
||||
capability "Refresh"
|
||||
capability "Temperature Measurement"
|
||||
|
||||
command "enrollResponse"
|
||||
command "enrollResponse"
|
||||
|
||||
|
||||
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3300-S"
|
||||
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3300"
|
||||
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3300"
|
||||
fingerprint inClusters: "0000,0001,0003,0020,0402,0500,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3320-L", deviceJoinName: "Iris Contact Sensor"
|
||||
}
|
||||
|
||||
simulator {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -25,6 +25,7 @@ metadata {
|
||||
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0702"
|
||||
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0702, 0B05", outClusters: "0003, 000A, 0019", manufacturer: "Jasco Products", model: "45853", deviceJoinName: "GE ZigBee Plug-In Switch"
|
||||
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0702, 0B05", outClusters: "000A, 0019", manufacturer: "Jasco Products", model: "45856", deviceJoinName: "GE ZigBee In-Wall Switch"
|
||||
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 000F, 0B04", outClusters: "0019", manufacturer: "SmartThings", model: "outletv4", deviceJoinName: "Outlet"
|
||||
}
|
||||
|
||||
tiles(scale: 2) {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
Binary file not shown.
6
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
6
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
@@ -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
|
||||
160
gradlew
vendored
Executable file
160
gradlew
vendored
Executable file
@@ -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 "$@"
|
||||
90
gradlew.bat
vendored
Normal file
90
gradlew.bat
vendored
Normal file
@@ -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
|
||||
19
settings.gradle
Normal file
19
settings.gradle
Normal file
@@ -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'
|
||||
@@ -0,0 +1,65 @@
|
||||
/**
|
||||
* Controlled Power Off
|
||||
*
|
||||
* Copyright 2016 Andrew Crow
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
definition(
|
||||
name: "Controlled Power Off",
|
||||
namespace: "acrow311",
|
||||
author: "Andrew Crow",
|
||||
description: "Application used to power off devices that should be allowed to finish their cycle before being shut down such as air conditions and tankless water heaters. Application will monitor electric usage and delay shutdown until usage has returned to inactive state.",
|
||||
category: "My Apps",
|
||||
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png",
|
||||
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png",
|
||||
iconX3Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png",
|
||||
oauth: true)
|
||||
|
||||
|
||||
preferences {
|
||||
section("Shutdown when device not active") {
|
||||
input(name: "meter", type: "capability.powerMeter", title: "When This Power Meter...", required: true, multiple: false, description: null)
|
||||
input(name: "threshold", type: "number", title: "Reports Below...", required: true, description: "In watts, enter integer value")
|
||||
}
|
||||
}
|
||||
|
||||
def installed() {
|
||||
log.debug "Installed with settings: ${settings}"
|
||||
initialize()
|
||||
}
|
||||
|
||||
def updated() {
|
||||
log.debug "Updated with settings: ${settings}"
|
||||
initialize()
|
||||
}
|
||||
|
||||
def initialize() {
|
||||
unsubscribe()
|
||||
subscribe(meter, "power", meterHandler)
|
||||
}
|
||||
|
||||
def meterHandler(evt) {
|
||||
def meterValue = evt.value as double
|
||||
def thresholdValue = threshold as int
|
||||
def switchState = meter.currentValue("switch") == "on" // Get current switch status (on = true)
|
||||
|
||||
if (switchState) { // If the switch is already off, do nothing
|
||||
if (meterValue < thresholdValue) { // If the power consumption is low enough, turn off switch
|
||||
log.info "${meter} reported energy ${meterValue} below ${threshold}. Turning off switch."
|
||||
sendNotificationEvent("${meter} not running, shutting down.")
|
||||
meter.off()
|
||||
} else { // Power consumption too high - device connected to switch in use - do not shutdown.
|
||||
sendNotificationEvent("Power consumption too high to shut off ${meter}. Attempting again in a moment.")
|
||||
log.info "${meter} reported energy ${meterValue} above ${threshold}. Leaving switch on."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
|
||||
@@ -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,38 +252,36 @@ 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)
|
||||
}
|
||||
} else {
|
||||
log.debug "http status: ${resp.status}"
|
||||
//refresh the auth token
|
||||
if (resp.data.status.code == 14) {
|
||||
log.debug "Storing the failed action to try later"
|
||||
atomicState.action = "getEcobeeThermostats"
|
||||
log.debug "Refreshing your auth_token!"
|
||||
refreshAuthToken()
|
||||
} else {
|
||||
log.error "Authentication error, invalid authentication method, lack of credentials, etc."
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch(Exception e) {
|
||||
log.debug "___exception getEcobeeThermostats(): " + e
|
||||
refreshAuthToken()
|
||||
}
|
||||
} catch (groovyx.net.http.HttpResponseException e) {
|
||||
log.trace "Exception polling children: " + e.response.data.status
|
||||
if (e.response.data.status.code == 14) {
|
||||
atomicState.action = "getEcobeeThermostats"
|
||||
log.debug "Refreshing your auth_token!"
|
||||
refreshAuthToken()
|
||||
}
|
||||
}
|
||||
atomicState.thermostats = stats
|
||||
return stats
|
||||
}
|
||||
|
||||
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
|
||||
@@ -450,23 +449,15 @@ def pollChildren(child = null) {
|
||||
}
|
||||
result = true
|
||||
log.debug "updated ${atomicState.thermostats?.size()} stats: ${atomicState.thermostats}"
|
||||
} else {
|
||||
log.error "polling children & got http status ${resp.status}"
|
||||
|
||||
//refresh the auth token
|
||||
if (resp.data.status.code == 14) {
|
||||
atomicState.action = "pollChildren"
|
||||
log.debug "Refreshing your auth_token!"
|
||||
refreshAuthToken()
|
||||
}
|
||||
else {
|
||||
log.error "Authentication error, invalid authentication method, lack of credentials, etc."
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch(Exception e) {
|
||||
log.debug "___exception polling children: " + e
|
||||
refreshAuthToken()
|
||||
} catch (groovyx.net.http.HttpResponseException e) {
|
||||
log.trace "Exception polling children: " + e.response.data.status
|
||||
if (e.response.data.status.code == 14) {
|
||||
atomicState.action = "pollChildren"
|
||||
log.debug "Refreshing your auth_token!"
|
||||
refreshAuthToken()
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
@@ -642,16 +633,14 @@ private refreshAuthToken() {
|
||||
|
||||
}
|
||||
atomicState.action = ""
|
||||
} else {
|
||||
log.debug "refresh failed ${resp.status} : ${resp.status.code}"
|
||||
}
|
||||
}
|
||||
} catch (groovyx.net.http.HttpResponseException e) {
|
||||
log.error "refreshAuthToken() >> Error: e.statusCode ${e.statusCode}"
|
||||
def reAttemptPeriod = 300 // in sec
|
||||
if (e.statusCode != 401) { //this issue might comes from exceed 20sec app execution, connectivity issue etc.
|
||||
if (e.statusCode != 401) { // this issue might comes from exceed 20sec app execution, connectivity issue etc.
|
||||
runIn(reAttemptPeriod, "refreshAuthToken")
|
||||
} else if (e.statusCode == 401) { //refresh token is expired
|
||||
} else if (e.statusCode == 401) { // unauthorized
|
||||
atomicState.reAttempt = atomicState.reAttempt + 1
|
||||
log.warn "reAttempt refreshAuthToken to try = ${atomicState.reAttempt}"
|
||||
if (atomicState.reAttempt <= 3) {
|
||||
@@ -724,29 +713,21 @@ def sendJson(child = null, String jsonBody) {
|
||||
log.debug "Error return code = ${resp.data.status.code}"
|
||||
debugEvent("Error return code = ${resp.data.status.code}")
|
||||
}
|
||||
} else {
|
||||
log.error "sent Json & got http status ${resp.status} - ${resp.status.code}"
|
||||
debugEvent ("sent Json & got http status ${resp.status} - ${resp.status.code}")
|
||||
|
||||
//refresh the auth token
|
||||
if (resp.status.code == 14) {
|
||||
log.debug "Refreshing your auth_token!"
|
||||
debugEvent ("Refreshing OAUTH Token")
|
||||
refreshAuthToken()
|
||||
return false
|
||||
} else {
|
||||
debugEvent ("Authentication error, invalid authentication method, lack of credentials, etc.")
|
||||
log.error "Authentication error, invalid authentication method, lack of credentials, etc."
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch(Exception e) {
|
||||
log.debug "Exception Sending Json: " + e
|
||||
debugEvent ("Exception Sending JSON: " + e)
|
||||
refreshAuthToken()
|
||||
return false
|
||||
}
|
||||
} catch (groovyx.net.http.HttpResponseException e) {
|
||||
log.trace "Exception Sending Json: " + e.response.data.status
|
||||
debugEvent ("sent Json & got http status ${e.statusCode} - ${e.response.data.status.code}")
|
||||
if (e.response.data.status.code == 14) {
|
||||
atomicState.action = "pollChildren"
|
||||
log.debug "Refreshing your auth_token!"
|
||||
refreshAuthToken()
|
||||
}
|
||||
else {
|
||||
debugEvent("Authentication error, invalid authentication method, lack of credentials, etc.")
|
||||
log.error "Authentication error, invalid authentication method, lack of credentials, etc."
|
||||
}
|
||||
}
|
||||
|
||||
if (returnStatus == 0)
|
||||
return true
|
||||
|
||||
@@ -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,6 +494,11 @@ def doDeviceSync(){
|
||||
discoverBridges()
|
||||
}
|
||||
|
||||
def isValidSource(macAddress) {
|
||||
def vbridges = getVerifiedHueBridges()
|
||||
return (vbridges?.find {"${it.value.mac}" == macAddress}) != null
|
||||
}
|
||||
|
||||
/////////////////////////////////////
|
||||
//CHILD DEVICE METHODS
|
||||
/////////////////////////////////////
|
||||
|
||||
@@ -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)}")
|
||||
}
|
||||
|
||||
|
||||
@@ -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 }
|
||||
}
|
||||
@@ -16,14 +16,14 @@
|
||||
* Date: 2013-09-06
|
||||
*/
|
||||
definition(
|
||||
name: "Wemo (Connect)",
|
||||
namespace: "smartthings",
|
||||
author: "SmartThings",
|
||||
description: "Allows you to integrate your WeMo Switch and Wemo Motion sensor with SmartThings.",
|
||||
category: "SmartThings Labs",
|
||||
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Partner/wemo.png",
|
||||
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Partner/wemo@2x.png",
|
||||
singleInstance: true
|
||||
name: "Wemo (Connect)",
|
||||
namespace: "smartthings",
|
||||
author: "SmartThings",
|
||||
description: "Allows you to integrate your WeMo Switch and Wemo Motion sensor with SmartThings.",
|
||||
category: "SmartThings Labs",
|
||||
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Partner/wemo.png",
|
||||
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Partner/wemo@2x.png",
|
||||
singleInstance: true
|
||||
)
|
||||
|
||||
preferences {
|
||||
@@ -39,7 +39,7 @@ private getFriendlyName(String deviceNetworkId) {
|
||||
sendHubCommand(new physicalgraph.device.HubAction("""GET /setup.xml HTTP/1.1
|
||||
HOST: ${deviceNetworkId}
|
||||
|
||||
""", physicalgraph.device.Protocol.LAN, "${deviceNetworkId}"))
|
||||
""", physicalgraph.device.Protocol.LAN, "${deviceNetworkId}", [callback: "setupHandler"]))
|
||||
}
|
||||
|
||||
private verifyDevices() {
|
||||
@@ -52,6 +52,13 @@ private verifyDevices() {
|
||||
}
|
||||
}
|
||||
|
||||
void ssdpSubscribe() {
|
||||
subscribe(location, "ssdpTerm.urn:Belkin:device:insight:1", ssdpSwitchHandler)
|
||||
subscribe(location, "ssdpTerm.urn:Belkin:device:controllee:1", ssdpSwitchHandler)
|
||||
subscribe(location, "ssdpTerm.urn:Belkin:device:sensor:1", ssdpMotionHandler)
|
||||
subscribe(location, "ssdpTerm.urn:Belkin:device:lightswitch:1", ssdpLightSwitchHandler)
|
||||
}
|
||||
|
||||
def firstPage()
|
||||
{
|
||||
if(canInstallLabs())
|
||||
@@ -62,7 +69,7 @@ def firstPage()
|
||||
|
||||
log.debug "REFRESH COUNT :: ${refreshCount}"
|
||||
|
||||
subscribe(location, null, locationHandler, [filterEvents:false])
|
||||
ssdpSubscribe()
|
||||
|
||||
//ssdp request every 25 seconds
|
||||
if((refreshCount % 5) == 0) {
|
||||
@@ -105,9 +112,7 @@ def devicesDiscovered() {
|
||||
def motions = getWemoMotions()
|
||||
def lightSwitches = getWemoLightSwitches()
|
||||
def devices = switches + motions + lightSwitches
|
||||
def list = []
|
||||
|
||||
list = devices?.collect{ [app.id, it.ssdpUSN].join('.') }
|
||||
devices?.collect{ [app.id, it.ssdpUSN].join('.') }
|
||||
}
|
||||
|
||||
def switchesDiscovered() {
|
||||
@@ -175,8 +180,9 @@ def updated() {
|
||||
|
||||
def initialize() {
|
||||
unsubscribe()
|
||||
unschedule()
|
||||
subscribe(location, null, locationHandler, [filterEvents:false])
|
||||
unschedule()
|
||||
|
||||
ssdpSubscribe()
|
||||
|
||||
if (selectedSwitches)
|
||||
addSwitches()
|
||||
@@ -189,7 +195,7 @@ def initialize() {
|
||||
|
||||
runIn(5, "subscribeToDevices") //initial subscriptions delayed by 5 seconds
|
||||
runIn(10, "refreshDevices") //refresh devices, delayed by 10 seconds
|
||||
runEvery5Minutes("refresh")
|
||||
runEvery5Minutes("refresh")
|
||||
}
|
||||
|
||||
def resubscribe() {
|
||||
@@ -199,7 +205,7 @@ def resubscribe() {
|
||||
|
||||
def refresh() {
|
||||
log.debug "refresh() called"
|
||||
doDeviceSync()
|
||||
doDeviceSync()
|
||||
refreshDevices()
|
||||
}
|
||||
|
||||
@@ -235,14 +241,14 @@ def addSwitches() {
|
||||
if (!d) {
|
||||
log.debug "Creating WeMo Switch with dni: ${selectedSwitch.value.mac}"
|
||||
d = addChildDevice("smartthings", "Wemo Switch", selectedSwitch.value.mac, selectedSwitch?.value.hub, [
|
||||
"label": selectedSwitch?.value?.name ?: "Wemo Switch",
|
||||
"data": [
|
||||
"mac": selectedSwitch.value.mac,
|
||||
"ip": selectedSwitch.value.ip,
|
||||
"port": selectedSwitch.value.port
|
||||
]
|
||||
"label": selectedSwitch?.value?.name ?: "Wemo Switch",
|
||||
"data": [
|
||||
"mac": selectedSwitch.value.mac,
|
||||
"ip": selectedSwitch.value.ip,
|
||||
"port": selectedSwitch.value.port
|
||||
]
|
||||
])
|
||||
def ipvalue = convertHexToIP(selectedSwitch.value.ip)
|
||||
def ipvalue = convertHexToIP(selectedSwitch.value.ip)
|
||||
d.sendEvent(name: "currentIP", value: ipvalue, descriptionText: "IP is ${ipvalue}")
|
||||
log.debug "Created ${d.displayName} with id: ${d.id}, dni: ${d.deviceNetworkId}"
|
||||
} else {
|
||||
@@ -273,9 +279,9 @@ def addMotions() {
|
||||
"port": selectedMotion.value.port
|
||||
]
|
||||
])
|
||||
def ipvalue = convertHexToIP(selectedMotion.value.ip)
|
||||
d.sendEvent(name: "currentIP", value: ipvalue, descriptionText: "IP is ${ipvalue}")
|
||||
log.debug "Created ${d.displayName} with id: ${d.id}, dni: ${d.deviceNetworkId}"
|
||||
def ipvalue = convertHexToIP(selectedMotion.value.ip)
|
||||
d.sendEvent(name: "currentIP", value: ipvalue, descriptionText: "IP is ${ipvalue}")
|
||||
log.debug "Created ${d.displayName} with id: ${d.id}, dni: ${d.deviceNetworkId}"
|
||||
} else {
|
||||
log.debug "found ${d.displayName} with id $dni already exists"
|
||||
}
|
||||
@@ -304,26 +310,155 @@ def addLightSwitches() {
|
||||
"port": selectedLightSwitch.value.port
|
||||
]
|
||||
])
|
||||
def ipvalue = convertHexToIP(selectedLightSwitch.value.ip)
|
||||
d.sendEvent(name: "currentIP", value: ipvalue, descriptionText: "IP is ${ipvalue}")
|
||||
def ipvalue = convertHexToIP(selectedLightSwitch.value.ip)
|
||||
d.sendEvent(name: "currentIP", value: ipvalue, descriptionText: "IP is ${ipvalue}")
|
||||
log.debug "created ${d.displayName} with id $dni"
|
||||
} else {
|
||||
log.debug "found ${d.displayName} with id $dni already exists"
|
||||
log.debug "found ${d.displayName} with id $dni already exists"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def ssdpSwitchHandler(evt) {
|
||||
def description = evt.description
|
||||
def hub = evt?.hubId
|
||||
def parsedEvent = parseDiscoveryMessage(description)
|
||||
parsedEvent << ["hub":hub]
|
||||
log.debug parsedEvent
|
||||
|
||||
def switches = getWemoSwitches()
|
||||
if (!(switches."${parsedEvent.ssdpUSN.toString()}")) {
|
||||
//if it doesn't already exist
|
||||
switches << ["${parsedEvent.ssdpUSN.toString()}":parsedEvent]
|
||||
} else {
|
||||
log.debug "Device was already found in state..."
|
||||
def d = switches."${parsedEvent.ssdpUSN.toString()}"
|
||||
boolean deviceChangedValues = false
|
||||
log.debug "$d.ip <==> $parsedEvent.ip"
|
||||
if(d.ip != parsedEvent.ip || d.port != parsedEvent.port) {
|
||||
d.ip = parsedEvent.ip
|
||||
d.port = parsedEvent.port
|
||||
deviceChangedValues = true
|
||||
log.debug "Device's port or ip changed..."
|
||||
def child = getChildDevice(parsedEvent.mac)
|
||||
if (child) {
|
||||
child.subscribe(parsedEvent.ip, parsedEvent.port)
|
||||
child.poll()
|
||||
} else {
|
||||
log.debug "Device with mac $parsedEvent.mac not found"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def ssdpMotionHandler(evt) {
|
||||
log.info("ssdpMotionHandler")
|
||||
def description = evt.description
|
||||
def hub = evt?.hubId
|
||||
def parsedEvent = parseDiscoveryMessage(description)
|
||||
parsedEvent << ["hub":hub]
|
||||
log.debug parsedEvent
|
||||
|
||||
def motions = getWemoMotions()
|
||||
if (!(motions."${parsedEvent.ssdpUSN.toString()}")) {
|
||||
//if it doesn't already exist
|
||||
motions << ["${parsedEvent.ssdpUSN.toString()}":parsedEvent]
|
||||
} else { // just update the values
|
||||
log.debug "Device was already found in state..."
|
||||
|
||||
def d = motions."${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.debug "Device's port or ip changed..."
|
||||
}
|
||||
|
||||
if (deviceChangedValues) {
|
||||
def children = getChildDevices()
|
||||
log.debug "Found children ${children}"
|
||||
children.each {
|
||||
if (it.getDeviceDataByName("mac") == parsedEvent.mac) {
|
||||
log.debug "updating ip and port, and resubscribing, for device ${it} with mac ${parsedEvent.mac}"
|
||||
it.subscribe(parsedEvent.ip, parsedEvent.port)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def ssdpLightSwitchHandler(evt) {
|
||||
log.info("ssdpLightSwitchHandler")
|
||||
def description = evt.description
|
||||
def hub = evt?.hubId
|
||||
def parsedEvent = parseDiscoveryMessage(description)
|
||||
parsedEvent << ["hub":hub]
|
||||
log.debug parsedEvent
|
||||
|
||||
def lightSwitches = getWemoLightSwitches()
|
||||
|
||||
if (!(lightSwitches."${parsedEvent.ssdpUSN.toString()}")) {
|
||||
//if it doesn't already exist
|
||||
lightSwitches << ["${parsedEvent.ssdpUSN.toString()}":parsedEvent]
|
||||
} else {
|
||||
log.debug "Device was already found in state..."
|
||||
|
||||
def d = lightSwitches."${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.debug "Device's port or ip changed..."
|
||||
def child = getChildDevice(parsedEvent.mac)
|
||||
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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void setupHandler(hubResponse) {
|
||||
String contentType = hubResponse?.headers['Content-Type']
|
||||
if (contentType != null && contentType == 'text/xml') {
|
||||
def body = hubResponse.xml
|
||||
def wemoDevices = []
|
||||
String deviceType = body?.device?.deviceType?.text() ?: ""
|
||||
if (deviceType.startsWith("urn:Belkin:device:controllee:1") || deviceType.startsWith("urn:Belkin:device:insight:1")) {
|
||||
wemoDevices = getWemoSwitches()
|
||||
} else if (deviceType.startsWith("urn:Belkin:device:sensor")) {
|
||||
wemoDevices = getWemoMotions()
|
||||
} else if (deviceType.startsWith("urn:Belkin:device:lightswitch")) {
|
||||
wemoDevices = getWemoLightSwitches()
|
||||
}
|
||||
|
||||
def wemoDevice = wemoDevices.find {it?.key?.contains(body?.device?.UDN?.text())}
|
||||
if (wemoDevice) {
|
||||
wemoDevice.value << [name:body?.device?.friendlyName?.text(), verified: true]
|
||||
} else {
|
||||
log.error "/setup.xml returned a wemo device that didn't exist"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
def locationHandler(evt) {
|
||||
def description = evt.description
|
||||
def hub = evt?.hubId
|
||||
def parsedEvent = parseDiscoveryMessage(description)
|
||||
parsedEvent << ["hub":hub]
|
||||
log.debug parsedEvent
|
||||
log.debug parsedEvent
|
||||
|
||||
if (parsedEvent?.ssdpTerm?.contains("Belkin:device:controllee") || parsedEvent?.ssdpTerm?.contains("Belkin:device:insight")) {
|
||||
def switches = getWemoSwitches()
|
||||
if (!(switches."${parsedEvent.ssdpUSN.toString()}")) {
|
||||
//if it doesn't already exist
|
||||
//if it doesn't already exist
|
||||
switches << ["${parsedEvent.ssdpUSN.toString()}":parsedEvent]
|
||||
} else {
|
||||
log.debug "Device was already found in state..."
|
||||
@@ -335,16 +470,20 @@ def locationHandler(evt) {
|
||||
d.port = parsedEvent.port
|
||||
deviceChangedValues = true
|
||||
log.debug "Device's port or ip changed..."
|
||||
def child = getChildDevice(parsedEvent.mac)
|
||||
child.subscribe(parsedEvent.ip, parsedEvent.port)
|
||||
child.poll()
|
||||
def child = getChildDevice(parsedEvent.mac)
|
||||
if (child) {
|
||||
child.subscribe(parsedEvent.ip, parsedEvent.port)
|
||||
child.poll()
|
||||
} else {
|
||||
log.debug "Device with mac $parsedEvent.mac not found"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (parsedEvent?.ssdpTerm?.contains("Belkin:device:sensor")) {
|
||||
def motions = getWemoMotions()
|
||||
if (!(motions."${parsedEvent.ssdpUSN.toString()}")) {
|
||||
//if it doesn't already exist
|
||||
//if it doesn't already exist
|
||||
motions << ["${parsedEvent.ssdpUSN.toString()}":parsedEvent]
|
||||
} else { // just update the values
|
||||
log.debug "Device was already found in state..."
|
||||
@@ -391,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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -459,6 +602,7 @@ def locationHandler(evt) {
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
private def parseXmlBody(def body) {
|
||||
def decodedBytes = body.decodeBase64()
|
||||
def bodyString
|
||||
@@ -540,4 +684,4 @@ private Boolean hasAllHubsOver(String desiredFirmware) {
|
||||
|
||||
private List getRealHubFirmwareVersions() {
|
||||
return location.hubs*.firmwareVersionString.findAll { it }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user