mirror of
https://github.com/mtan93/SmartThingsPublic.git
synced 2026-03-17 21:03:30 +00:00
Compare commits
2 Commits
DVCSMP-399
...
MSA-862-1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bdb1390ad0 | ||
|
|
65c1228931 |
23
.gitignore
vendored
23
.gitignore
vendored
@@ -1,23 +0,0 @@
|
|||||||
# 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
|
|
||||||
60
build.gradle
60
build.gradle
@@ -1,60 +0,0 @@
|
|||||||
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})."
|
|
||||||
if (branch == 'master') {
|
|
||||||
message += ' (dev shards)'
|
|
||||||
}
|
|
||||||
color = branch == 'master' ? 'yellow' : 'red'
|
|
||||||
notify = true
|
|
||||||
}
|
|
||||||
27
circle.yml
27
circle.yml
@@ -1,27 +0,0 @@
|
|||||||
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
|
|
||||||
@@ -1,107 +0,0 @@
|
|||||||
/**
|
|
||||||
* BeaconThing
|
|
||||||
*
|
|
||||||
* Copyright 2015 obycode
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
import groovy.json.JsonSlurper
|
|
||||||
|
|
||||||
metadata {
|
|
||||||
definition (name: "BeaconThing", namespace: "com.obycode", author: "obycode") {
|
|
||||||
capability "Beacon"
|
|
||||||
capability "Presence Sensor"
|
|
||||||
capability "Sensor"
|
|
||||||
|
|
||||||
attribute "inRange", "json_object"
|
|
||||||
attribute "inRangeFriendly", "string"
|
|
||||||
|
|
||||||
command "setPresence", ["string"]
|
|
||||||
command "arrived", ["string"]
|
|
||||||
command "left", ["string"]
|
|
||||||
}
|
|
||||||
|
|
||||||
simulator {
|
|
||||||
status "present": "presence: 1"
|
|
||||||
status "not present": "presence: 0"
|
|
||||||
}
|
|
||||||
|
|
||||||
tiles {
|
|
||||||
standardTile("presence", "device.presence", width: 2, height: 2, canChangeBackground: true) {
|
|
||||||
state("present", labelIcon:"st.presence.tile.present", backgroundColor:"#53a7c0")
|
|
||||||
state("not present", labelIcon:"st.presence.tile.not-present", backgroundColor:"#ffffff")
|
|
||||||
}
|
|
||||||
valueTile("inRange", "device.inRangeFriendly", inactiveLabel: true, height:1, width:3, decoration: "flat") {
|
|
||||||
state "default", label:'${currentValue}', backgroundColor:"#ffffff"
|
|
||||||
}
|
|
||||||
main "presence"
|
|
||||||
details (["presence","inRange"])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// parse events into attributes
|
|
||||||
def parse(String description) {
|
|
||||||
log.debug "Parsing '${description}'"
|
|
||||||
}
|
|
||||||
|
|
||||||
def installed() {
|
|
||||||
sendEvent(name: "presence", value: "not present")
|
|
||||||
def emptyList = []
|
|
||||||
def json = new groovy.json.JsonBuilder(emptyList)
|
|
||||||
sendEvent(name:"inRange", value:json.toString())
|
|
||||||
}
|
|
||||||
|
|
||||||
def setPresence(status) {
|
|
||||||
log.debug "Status is $status"
|
|
||||||
sendEvent(name:"presence", value:status)
|
|
||||||
}
|
|
||||||
|
|
||||||
def arrived(id) {
|
|
||||||
log.debug "$id has arrived"
|
|
||||||
def theList = device.latestValue("inRange")
|
|
||||||
def inRangeList = new JsonSlurper().parseText(theList)
|
|
||||||
if (inRangeList.contains(id)) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
inRangeList += id
|
|
||||||
def json = new groovy.json.JsonBuilder(inRangeList)
|
|
||||||
log.debug "Now in range: ${json.toString()}"
|
|
||||||
sendEvent(name:"inRange", value:json.toString())
|
|
||||||
|
|
||||||
// Generate human friendly string for tile
|
|
||||||
def friendlyList = "Nearby: " + inRangeList.join(", ")
|
|
||||||
sendEvent(name:"inRangeFriendly", value:friendlyList)
|
|
||||||
|
|
||||||
if (inRangeList.size() == 1) {
|
|
||||||
setPresence("present")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def left(id) {
|
|
||||||
log.debug "$id has left"
|
|
||||||
def theList = device.latestValue("inRange")
|
|
||||||
def inRangeList = new JsonSlurper().parseText(theList)
|
|
||||||
inRangeList -= id
|
|
||||||
def json = new groovy.json.JsonBuilder(inRangeList)
|
|
||||||
log.debug "Now in range: ${json.toString()}"
|
|
||||||
sendEvent(name:"inRange", value:json.toString())
|
|
||||||
|
|
||||||
// Generate human friendly string for tile
|
|
||||||
def friendlyList = "Nearby: " + inRangeList.join(", ")
|
|
||||||
|
|
||||||
if (inRangeList.empty) {
|
|
||||||
setPresence("not present")
|
|
||||||
friendlyList = "No one is nearby"
|
|
||||||
}
|
|
||||||
|
|
||||||
sendEvent(name:"inRangeFriendly", value:friendlyList)
|
|
||||||
}
|
|
||||||
@@ -120,7 +120,7 @@ def setInstallSmartApp(value){
|
|||||||
}
|
}
|
||||||
|
|
||||||
def parse(String description) {
|
def parse(String description) {
|
||||||
log.debug description
|
|
||||||
def description_map = parseDescriptionAsMap(description)
|
def description_map = parseDescriptionAsMap(description)
|
||||||
def event_name = ""
|
def event_name = ""
|
||||||
def measurement_map = [
|
def measurement_map = [
|
||||||
@@ -129,7 +129,10 @@ def parse(String description) {
|
|||||||
zigbeedeviceid: device.zigbeeId,
|
zigbeedeviceid: device.zigbeeId,
|
||||||
created: new Date().time /1000 as int
|
created: new Date().time /1000 as int
|
||||||
]
|
]
|
||||||
if (description_map.cluster == "0001"){
|
if (description_map.cluster == "0000"){
|
||||||
|
/* version number, not used */
|
||||||
|
|
||||||
|
} else if (description_map.cluster == "0001"){
|
||||||
/* battery voltage in mV (device needs minimium 2.1v to run) */
|
/* battery voltage in mV (device needs minimium 2.1v to run) */
|
||||||
log.debug "PlantLink - id ${device.zigbeeId} battery ${description_map.value}"
|
log.debug "PlantLink - id ${device.zigbeeId} battery ${description_map.value}"
|
||||||
event_name = "battery_status"
|
event_name = "battery_status"
|
||||||
@@ -155,10 +158,6 @@ def parse(String description) {
|
|||||||
def parseDescriptionAsMap(description) {
|
def parseDescriptionAsMap(description) {
|
||||||
(description - "read attr - ").split(",").inject([:]) { map, param ->
|
(description - "read attr - ").split(",").inject([:]) { map, param ->
|
||||||
def nameAndValue = param.split(":")
|
def nameAndValue = param.split(":")
|
||||||
if(nameAndValue.length == 2){
|
map += [(nameAndValue[0].trim()):nameAndValue[1].trim()]
|
||||||
map += [(nameAndValue[0].trim()):nameAndValue[1].trim()]
|
|
||||||
}else{
|
|
||||||
map += []
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,627 +0,0 @@
|
|||||||
/**
|
|
||||||
* Spruce Controller - Pre Release V2 10/11/2015
|
|
||||||
*
|
|
||||||
* Copyright 2015 Plaid Systems
|
|
||||||
*
|
|
||||||
* Author: NC
|
|
||||||
* Date: 2015-11
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*
|
|
||||||
-----------V3 updates-11-2015------------
|
|
||||||
-Start program button updated to signal schedule check in Scheduler
|
|
||||||
11/17 alarm "0" -> 0 (ln 305)
|
|
||||||
*/
|
|
||||||
|
|
||||||
metadata {
|
|
||||||
definition (name: "Spruce Controller", namespace: "plaidsystems", author: "NCauffman") {
|
|
||||||
capability "Switch"
|
|
||||||
capability "Configuration"
|
|
||||||
capability "Refresh"
|
|
||||||
capability "Actuator"
|
|
||||||
capability "Valve"
|
|
||||||
|
|
||||||
attribute "switch", "string"
|
|
||||||
attribute "switch1", "string"
|
|
||||||
attribute "switch2", "string"
|
|
||||||
attribute "switch8", "string"
|
|
||||||
attribute "switch5", "string"
|
|
||||||
attribute "switch3", "string"
|
|
||||||
attribute "switch4", "string"
|
|
||||||
attribute "switch6", "string"
|
|
||||||
attribute "switch7", "string"
|
|
||||||
attribute "switch9", "string"
|
|
||||||
attribute "switch10", "string"
|
|
||||||
attribute "switch11", "string"
|
|
||||||
attribute "switch12", "string"
|
|
||||||
attribute "switch13", "string"
|
|
||||||
attribute "switch14", "string"
|
|
||||||
attribute "switch15", "string"
|
|
||||||
attribute "switch16", "string"
|
|
||||||
attribute "status", "string"
|
|
||||||
|
|
||||||
command "programOn"
|
|
||||||
command "programOff"
|
|
||||||
command "on"
|
|
||||||
command "off"
|
|
||||||
command "z1on"
|
|
||||||
command "z1off"
|
|
||||||
command "z2on"
|
|
||||||
command "z2off"
|
|
||||||
command "z3on"
|
|
||||||
command "z3off"
|
|
||||||
command "z4on"
|
|
||||||
command "z4off"
|
|
||||||
command "z5on"
|
|
||||||
command "z5off"
|
|
||||||
command "z6on"
|
|
||||||
command "z6off"
|
|
||||||
command "z7on"
|
|
||||||
command "z7off"
|
|
||||||
command "z8on"
|
|
||||||
command "z8off"
|
|
||||||
command "z9on"
|
|
||||||
command "z9off"
|
|
||||||
command "z10on"
|
|
||||||
command "z10off"
|
|
||||||
command "z11on"
|
|
||||||
command "z11off"
|
|
||||||
command "z12on"
|
|
||||||
command "z12off"
|
|
||||||
command "z13on"
|
|
||||||
command "z13off"
|
|
||||||
command "z14on"
|
|
||||||
command "z14off"
|
|
||||||
command "z15on"
|
|
||||||
command "z15off"
|
|
||||||
command "z16on"
|
|
||||||
command "z16off"
|
|
||||||
command "offtime"
|
|
||||||
|
|
||||||
command "refresh"
|
|
||||||
command "rain"
|
|
||||||
command "manual"
|
|
||||||
command "setDisplay"
|
|
||||||
|
|
||||||
command "settingsMap"
|
|
||||||
command "writeTime"
|
|
||||||
command "writeType"
|
|
||||||
command "notify"
|
|
||||||
command "updated"
|
|
||||||
|
|
||||||
fingerprint endpointId: "1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18", profileId: "0104", deviceId: "0002", deviceVersion: "00", inClusters: "0000,0003,0004,0005,0006,000F", outClusters: "0003, 0019", manufacturer: "PLAID SYSTEMS", model: "PS-SPRZ16-01"
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// simulator metadata
|
|
||||||
simulator {
|
|
||||||
// status messages
|
|
||||||
|
|
||||||
// reply messages
|
|
||||||
|
|
||||||
}
|
|
||||||
preferences {
|
|
||||||
input description: "Press Configure button after making changes to these preferences", displayDuringSetup: true, type: "paragraph", element: "paragraph", title: ""
|
|
||||||
input "RainEnable", "bool", title: "Rain Sensor Attached?", required: false, displayDuringSetup: true
|
|
||||||
input "ManualTime", "number", title: "Automatic shutoff time when a zone is turned on manually?", required: false, displayDuringSetup: true
|
|
||||||
}
|
|
||||||
|
|
||||||
// UI tile definitions
|
|
||||||
tiles {
|
|
||||||
|
|
||||||
standardTile("status", "device.status") {
|
|
||||||
state "schedule", label: 'Schedule Set', icon: "http://www.plaidsystems.com/smartthings/st_spruce_leaf_225_t.png"
|
|
||||||
state "finished", label: 'Spruce Finished', icon: "st.Outdoor.outdoor5", backgroundColor: "#46c2e8"
|
|
||||||
state "raintoday", label: 'Rain Today', icon: "st.custom.wuk.nt_chancerain"
|
|
||||||
state "rainy", label: 'Previous Rain', icon: "st.custom.wuk.nt_chancerain"
|
|
||||||
state "raintom", label: 'Rain Tomorrow', icon: "st.custom.wuk.nt_chancerain"
|
|
||||||
state "donewweek", label: 'Spruce Finished', icon: "st.Outdoor.outdoor5", backgroundColor: "#52c435"
|
|
||||||
state "skipping", label: 'Skip Today', icon: "st.Outdoor.outdoor20", backgroundColor: "#36cfe3"
|
|
||||||
state "moisture", label: '', icon: "st.Weather.weather2", backgroundColor: "#36cfe3"
|
|
||||||
state "pause", label: 'PAUSE', icon: "st.contact.contact.open", backgroundColor: "#f2a51f"
|
|
||||||
state "active", label: 'Active', icon: "st.Outdoor.outdoor12", backgroundColor: "#3DC72E"
|
|
||||||
state "season", label: 'Seasonal Adjustment', icon: "st.Outdoor.outdoor17", backgroundColor: "#ffb900"
|
|
||||||
state "disable", label: 'Disabled', icon: "st.secondary.off", backgroundColor: "#888888"
|
|
||||||
state "warning", label: '', icon: "st.categories.damageAndDanger", backgroundColor: "#ffff7f"
|
|
||||||
state "alarm", label: 'Alarm', icon: "st.categories.damageAndDanger", backgroundColor: "#f9240c"
|
|
||||||
}
|
|
||||||
standardTile("switch", "device.switch") {
|
|
||||||
//state "programOff", label: 'Start Program', action: "programOn", icon: "st.sonos.play-icon", backgroundColor: "#a9a9a9"
|
|
||||||
state "off", label: 'Start Program', action: "programOn", icon: "st.sonos.play-icon", backgroundColor: "#a9a9a9"
|
|
||||||
state "programOn", label: 'Initialize Program', action: "programOff", icon: "st.contact.contact.open", backgroundColor: "#f6e10e"
|
|
||||||
state "on", label: 'Program Running', action: "off", icon: "st.Outdoor.outdoor12", backgroundColor: "#3DC72E"
|
|
||||||
}
|
|
||||||
standardTile("rainsensor", "device.rainsensor") {
|
|
||||||
state "rainSensrooff", label: 'Rain Sensor Clear', icon: "st.Weather.weather14", backgroundColor: "#a9a9a9"
|
|
||||||
state "rainSensoron", label: 'Rain Detected', icon: "st.Weather.weather10", backgroundColor: "#f6e10e"
|
|
||||||
}
|
|
||||||
standardTile("switch1", "device.switch1") {
|
|
||||||
state "z1off", label: '1', action: "z1on", icon: "st.valves.water.closed", backgroundColor: "#ffffff"
|
|
||||||
state "z1on", label: '1', action: "z1off", icon: "st.valves.water.open", backgroundColor: "#46c2e8"
|
|
||||||
}
|
|
||||||
standardTile("switch2", "device.switch2") {
|
|
||||||
state "z2off", label: '2', action: "z2on", icon: "st.valves.water.closed", backgroundColor: "#ffffff"
|
|
||||||
state "z2on", label: '2', action: "z2off", icon: "st.valves.water.open", backgroundColor: "#46c2e8"
|
|
||||||
}
|
|
||||||
standardTile("switch3", "device.switch3", inactiveLabel: false) {
|
|
||||||
state "z3off", label: '3', action: "z3on", icon: "st.valves.water.closed", backgroundColor: "#ffffff"
|
|
||||||
state "z3on", label: '3', action: "z3off", icon: "st.valves.water.open", backgroundColor: "#46c2e8"
|
|
||||||
}
|
|
||||||
standardTile("switch4", "device.switch4", inactiveLabel: false) {
|
|
||||||
state "z4off", label: '4', action: "z4on", icon: "st.valves.water.closed", backgroundColor: "#ffffff"
|
|
||||||
state "z4on", label: '4', action: "z4off", icon: "st.valves.water.open", backgroundColor: "#46c2e8"
|
|
||||||
}
|
|
||||||
standardTile("switch5", "device.switch5", inactiveLabel: false) {
|
|
||||||
state "z5off", label: '5', action: "z5on", icon: "st.valves.water.closed", backgroundColor: "#ffffff"
|
|
||||||
state "z5on", label: '5', action: "z5off", icon: "st.valves.water.open", backgroundColor: "#46c2e8"
|
|
||||||
}
|
|
||||||
standardTile("switch6", "device.switch6", inactiveLabel: false) {
|
|
||||||
state "z6off", label: '6', action: "z6on", icon: "st.valves.water.closed", backgroundColor: "#ffffff"
|
|
||||||
state "z6on", label: '6', action: "z6off", icon: "st.valves.water.open", backgroundColor: "#46c2e8"
|
|
||||||
}
|
|
||||||
standardTile("switch7", "device.switch7", inactiveLabel: false) {
|
|
||||||
state "z7off", label: '7', action: "z7on", icon: "st.valves.water.closed", backgroundColor: "#ffffff"
|
|
||||||
state "z7on", label: '7', action: "z7off", icon: "st.valves.water.open", backgroundColor: "#46c2e8"
|
|
||||||
}
|
|
||||||
standardTile("switch8", "device.switch8", inactiveLabel: false) {
|
|
||||||
state "z8off", label: '8', action: "z8on", icon: "st.valves.water.closed", backgroundColor: "#ffffff"
|
|
||||||
state "z8on", label: '8', action: "z8off", icon: "st.valves.water.open", backgroundColor: "#46c2e8"
|
|
||||||
}
|
|
||||||
standardTile("switch9", "device.switch9", inactiveLabel: false) {
|
|
||||||
state "z9off", label: '9', action: "z9on", icon: "st.valves.water.closed", backgroundColor: "#ffffff"
|
|
||||||
state "z9on", label: '9', action: "z9off", icon: "st.valves.water.open", backgroundColor: "#46c2e8"
|
|
||||||
}
|
|
||||||
standardTile("switch10", "device.switch10", inactiveLabel: false) {
|
|
||||||
state "z10off", label: '10', action: "z10on", icon: "st.valves.water.closed", backgroundColor: "#ffffff"
|
|
||||||
state "z10on", label: '10', action: "z10off", icon: "st.valves.water.open", backgroundColor: "#46c2e8"
|
|
||||||
}
|
|
||||||
standardTile("switch11", "device.switch11", inactiveLabel: false) {
|
|
||||||
state "z11off", label: '11', action: "z11on", icon: "st.valves.water.closed", backgroundColor: "#ffffff"
|
|
||||||
state "z11on", label: '11', action: "z11off", icon: "st.valves.water.open", backgroundColor: "#46c2e8"
|
|
||||||
}
|
|
||||||
standardTile("switch12", "device.switch12", inactiveLabel: false) {
|
|
||||||
state "z12off", label: '12', action: "z12on", icon: "st.valves.water.closed", backgroundColor: "#ffffff"
|
|
||||||
state "z12on", label: '12', action: "z12off", icon: "st.valves.water.open", backgroundColor: "#46c2e8"
|
|
||||||
}
|
|
||||||
standardTile("switch13", "device.switch13", inactiveLabel: false) {
|
|
||||||
state "z13off", label: '13', action: "z13on", icon: "st.valves.water.closed", backgroundColor: "#ffffff"
|
|
||||||
state "z13on", label: '13', action: "z13off", icon: "st.valves.water.open", backgroundColor: "#46c2e8"
|
|
||||||
}
|
|
||||||
standardTile("switch14", "device.switch14", inactiveLabel: false) {
|
|
||||||
state "z14off", label: '14', action: "z14on", icon: "st.valves.water.closed", backgroundColor: "#ffffff"
|
|
||||||
state "z14on", label: '14', action: "z14off", icon: "st.valves.water.open", backgroundColor: "#46c2e8"
|
|
||||||
}
|
|
||||||
standardTile("switch15", "device.switch15", inactiveLabel: false) {
|
|
||||||
state "z15off", label: '15', action: "z15on", icon: "st.valves.water.closed", backgroundColor: "#ffffff"
|
|
||||||
state "z15on", label: '15', action: "z15off", icon: "st.valves.water.open", backgroundColor: "#46c2e8"
|
|
||||||
}
|
|
||||||
standardTile("switch16", "device.switch16", inactiveLabel: false) {
|
|
||||||
state "z16off", label: '16', action: "z16on", icon: "st.valves.water.closed", backgroundColor: "#ffffff"
|
|
||||||
state "z16on", label: '16', action: "z16off", icon: "st.valves.water.open", backgroundColor: "#46c2e8"
|
|
||||||
}
|
|
||||||
standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat") {
|
|
||||||
state "default", action: "refresh", icon:"st.secondary.refresh"
|
|
||||||
}
|
|
||||||
standardTile("configure", "device.configure", inactiveLabel: false, decoration: "flat") {
|
|
||||||
state "configure", label:'', action:"configuration.configure", icon:"st.secondary.configure"
|
|
||||||
}
|
|
||||||
|
|
||||||
main (["status"])
|
|
||||||
details(["status","rainsensor","switch","switch1","switch2","switch3","switch4","switch5","switch6","switch7","switch8","switch9","switch10","switch11","switch12","switch13","switch14","switch15","switch16","refresh","configure"])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def programOn(){
|
|
||||||
sendEvent(name: "switch", value: "programOn", descriptionText: "Program turned on")
|
|
||||||
}
|
|
||||||
|
|
||||||
def programOff(){
|
|
||||||
sendEvent(name: "switch", value: "off", descriptionText: "Program turned off")
|
|
||||||
off()
|
|
||||||
}
|
|
||||||
|
|
||||||
def updated(){
|
|
||||||
log.debug "updated"
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse incoming device messages to generate events
|
|
||||||
def parse(String description) {
|
|
||||||
//log.debug "Parse description $description"
|
|
||||||
def result = null
|
|
||||||
def map = [:]
|
|
||||||
if (description?.startsWith("read attr -")) {
|
|
||||||
def descMap = parseDescriptionAsMap(description)
|
|
||||||
//log.debug "Desc Map: $descMap"
|
|
||||||
//using 000F cluster instead of 0006 (switch) because ST does not differentiate between EPs and processes all as switch
|
|
||||||
if (descMap.cluster == "000F" && descMap.attrId == "0055") {
|
|
||||||
log.debug "Zone"
|
|
||||||
map = getZone(descMap)
|
|
||||||
}
|
|
||||||
else if (descMap.cluster == "0009" && descMap.attrId == "0000") {
|
|
||||||
log.debug "Alarm"
|
|
||||||
map = getAlarm(descMap)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (map) {
|
|
||||||
result = createEvent(map)
|
|
||||||
}
|
|
||||||
log.debug "Parse returned $map $result"
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
def parseDescriptionAsMap(description) {
|
|
||||||
(description - "read attr - ").split(",").inject([:]) { map, param ->
|
|
||||||
def nameAndValue = param.split(":")
|
|
||||||
map += [(nameAndValue[0].trim()):nameAndValue[1].trim()]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def getZone(descMap){
|
|
||||||
def map = [:]
|
|
||||||
|
|
||||||
def EP = Integer.parseInt(descMap.endpoint.trim(), 16)
|
|
||||||
|
|
||||||
String onoff
|
|
||||||
if(descMap.value == "00"){
|
|
||||||
onoff = "off"
|
|
||||||
}
|
|
||||||
else onoff = "on"
|
|
||||||
|
|
||||||
if (EP == 1){
|
|
||||||
map.name = "switch"
|
|
||||||
map.value = onoff
|
|
||||||
map.descriptionText = "${device.displayName} turned sprinkler program $onoff"
|
|
||||||
}
|
|
||||||
|
|
||||||
else if (EP == 18) {
|
|
||||||
map.name = "rainsensor"
|
|
||||||
map.value = "rainSensor" + onoff
|
|
||||||
map.descriptionText = "${device.displayName} rain sensor is $onoff"
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
EP -= 1
|
|
||||||
map.name = "switch" + EP
|
|
||||||
map.value = "z" + EP + onoff
|
|
||||||
map.descriptionText = "${device.displayName} turned Zone $EP $onoff"
|
|
||||||
}
|
|
||||||
|
|
||||||
map.isStateChange = true
|
|
||||||
map.displayed = true
|
|
||||||
return map
|
|
||||||
}
|
|
||||||
|
|
||||||
def getAlarm(descMap){
|
|
||||||
def map = [:]
|
|
||||||
map.name = "status"
|
|
||||||
def alarmID = Integer.parseInt(descMap.value.trim(), 16)
|
|
||||||
log.debug "${alarmID}"
|
|
||||||
if(alarmID <= 0) map.descriptionText = "${device.displayName} has rebooted, no other alarms"
|
|
||||||
else map.descriptionText = "${device.displayName} rebooted, reported error on zone ${alarmID - 1}, please check zone is working correctly"
|
|
||||||
map.value = "alarm"
|
|
||||||
map.isStateChange = true
|
|
||||||
map.displayed = true
|
|
||||||
return map
|
|
||||||
}
|
|
||||||
|
|
||||||
//status notify and change status
|
|
||||||
def notify(value, text){
|
|
||||||
sendEvent(name:"status", value:"$value", descriptionText:"$text", isStateChange: true, display: false)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
//prefrences - rain sensor, manual time
|
|
||||||
def rain() {
|
|
||||||
log.debug "Rain $RainEnable"
|
|
||||||
if (RainEnable) "st wattr 0x${device.deviceNetworkId} 18 0x0F 0x51 0x10 {01}"
|
|
||||||
else "st wattr 0x${device.deviceNetworkId} 18 0x0F 0x51 0x10 {00}"
|
|
||||||
}
|
|
||||||
def manual(){
|
|
||||||
log.debug "Time $ManualTime"
|
|
||||||
def mTime = 10
|
|
||||||
if (ManualTime) mTime = ManualTime
|
|
||||||
def manualTime = hex(mTime)
|
|
||||||
"st wattr 0x${device.deviceNetworkId} 1 6 0x4002 0x21 {00${manualTime}}"
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
//write switch time settings map
|
|
||||||
def settingsMap(WriteTimes, attrType){
|
|
||||||
log.debug WriteTimes
|
|
||||||
|
|
||||||
def i = 1
|
|
||||||
def runTime
|
|
||||||
def sendCmds = []
|
|
||||||
while(i <= 17){
|
|
||||||
|
|
||||||
if (WriteTimes."${i}"){
|
|
||||||
runTime = hex(Integer.parseInt(WriteTimes."${i}"))
|
|
||||||
log.debug "${i} : $runTime"
|
|
||||||
|
|
||||||
if (attrType == 4001) sendCmds.push("st wattr 0x${device.deviceNetworkId} ${i} 0x06 0x4001 0x21 {00${runTime}}")
|
|
||||||
else sendCmds.push("st wattr 0x${device.deviceNetworkId} ${i} 0x06 0x4002 0x21 {00${runTime}}")
|
|
||||||
sendCmds.push("delay 500")
|
|
||||||
}
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
return sendCmds
|
|
||||||
}
|
|
||||||
|
|
||||||
//send switch time
|
|
||||||
def writeType(wEP, cycle){
|
|
||||||
log.debug "wt ${wEP} ${cycle}"
|
|
||||||
"st wattr 0x${device.deviceNetworkId} ${wEP} 0x06 0x4001 0x21 {00" + hex(cycle) + "}"
|
|
||||||
}
|
|
||||||
//send switch off time
|
|
||||||
def writeTime(wEP, runTime){
|
|
||||||
"st wattr 0x${device.deviceNetworkId} ${wEP} 0x06 0x4002 0x21 {00" + hex(runTime) + "}"
|
|
||||||
}
|
|
||||||
|
|
||||||
//set reporting and binding
|
|
||||||
def configure() {
|
|
||||||
|
|
||||||
String zigbeeId = swapEndianHex(device.hub.zigbeeId)
|
|
||||||
log.debug "Confuguring Reporting and Bindings ${device.deviceNetworkId} ${device.zigbeeId}"
|
|
||||||
sendEvent(name: 'configuration',value: 100, descriptionText: "Configuration initialized")
|
|
||||||
|
|
||||||
def configCmds = [
|
|
||||||
//program on/off
|
|
||||||
"zdo bind 0x${device.deviceNetworkId} 1 1 6 {${device.zigbeeId}} {}", "delay 1000",
|
|
||||||
"zdo bind 0x${device.deviceNetworkId} 1 1 0x09 {${device.zigbeeId}} {}", "delay 1000",
|
|
||||||
"zdo bind 0x${device.deviceNetworkId} 1 1 0x0F {${device.zigbeeId}} {}", "delay 1000",
|
|
||||||
//zones 1-8
|
|
||||||
"zdo bind 0x${device.deviceNetworkId} 2 1 0x0F {${device.zigbeeId}} {}", "delay 1000",
|
|
||||||
"zdo bind 0x${device.deviceNetworkId} 3 1 0x0F {${device.zigbeeId}} {}", "delay 1000",
|
|
||||||
"zdo bind 0x${device.deviceNetworkId} 4 1 0x0F {${device.zigbeeId}} {}", "delay 1000",
|
|
||||||
"zdo bind 0x${device.deviceNetworkId} 5 1 0x0F {${device.zigbeeId}} {}", "delay 1000",
|
|
||||||
"zdo bind 0x${device.deviceNetworkId} 6 1 0x0F {${device.zigbeeId}} {}", "delay 1000",
|
|
||||||
"zdo bind 0x${device.deviceNetworkId} 7 1 0x0F {${device.zigbeeId}} {}", "delay 1000",
|
|
||||||
"zdo bind 0x${device.deviceNetworkId} 8 1 0x0F {${device.zigbeeId}} {}", "delay 1000",
|
|
||||||
"zdo bind 0x${device.deviceNetworkId} 9 1 0x0F {${device.zigbeeId}} {}", "delay 1000",
|
|
||||||
//zones 9-16
|
|
||||||
"zdo bind 0x${device.deviceNetworkId} 10 1 0x0F {${device.zigbeeId}} {}", "delay 1000",
|
|
||||||
"zdo bind 0x${device.deviceNetworkId} 11 1 0x0F {${device.zigbeeId}} {}", "delay 1000",
|
|
||||||
"zdo bind 0x${device.deviceNetworkId} 12 1 0x0F {${device.zigbeeId}} {}", "delay 1000",
|
|
||||||
"zdo bind 0x${device.deviceNetworkId} 13 1 0x0F {${device.zigbeeId}} {}", "delay 1000",
|
|
||||||
"zdo bind 0x${device.deviceNetworkId} 14 1 0x0F {${device.zigbeeId}} {}", "delay 1000",
|
|
||||||
"zdo bind 0x${device.deviceNetworkId} 15 1 0x0F {${device.zigbeeId}} {}", "delay 1000",
|
|
||||||
"zdo bind 0x${device.deviceNetworkId} 16 1 0x0F {${device.zigbeeId}} {}", "delay 1000",
|
|
||||||
"zdo bind 0x${device.deviceNetworkId} 17 1 0x0F {${device.zigbeeId}} {}", "delay 1000",
|
|
||||||
//rain sensor
|
|
||||||
"zdo bind 0x${device.deviceNetworkId} 18 1 0x0F {${device.zigbeeId}} {}",
|
|
||||||
|
|
||||||
"zcl global send-me-a-report 6 0 0x10 1 0 {01}", "delay 500",
|
|
||||||
"send 0x${device.deviceNetworkId} 1 1", "delay 500",
|
|
||||||
|
|
||||||
"zcl global send-me-a-report 0x0F 0x55 0x10 1 0 {01}", "delay 500",
|
|
||||||
"send 0x${device.deviceNetworkId} 1 1", "delay 500",
|
|
||||||
|
|
||||||
"zcl global send-me-a-report 0x0F 0x55 0x10 1 0 {01}", "delay 500",
|
|
||||||
"send 0x${device.deviceNetworkId} 1 2", "delay 500",
|
|
||||||
|
|
||||||
"zcl global send-me-a-report 0x0F 0x55 0x10 1 0 {01}", "delay 500",
|
|
||||||
"send 0x${device.deviceNetworkId} 1 3", "delay 500",
|
|
||||||
|
|
||||||
"zcl global send-me-a-report 0x0F 0x55 0x10 1 0 {01}", "delay 500",
|
|
||||||
"send 0x${device.deviceNetworkId} 1 4", "delay 500",
|
|
||||||
|
|
||||||
"zcl global send-me-a-report 0x0F 0x55 0x10 1 0 {01}", "delay 500",
|
|
||||||
"send 0x${device.deviceNetworkId} 1 5", "delay 500",
|
|
||||||
|
|
||||||
"zcl global send-me-a-report 0x0F 0x55 0x10 1 0 {01}", "delay 500",
|
|
||||||
"send 0x${device.deviceNetworkId} 1 6", "delay 500",
|
|
||||||
|
|
||||||
"zcl global send-me-a-report 0x0F 0x55 0x10 1 0 {01}", "delay 500",
|
|
||||||
"send 0x${device.deviceNetworkId} 1 7", "delay 500",
|
|
||||||
|
|
||||||
"zcl global send-me-a-report 0x0F 0x55 0x10 1 0 {01}", "delay 500",
|
|
||||||
"send 0x${device.deviceNetworkId} 1 8", "delay 500",
|
|
||||||
|
|
||||||
|
|
||||||
"zcl global send-me-a-report 0x0F 0x55 0x10 1 0 {01}", "delay 500",
|
|
||||||
"send 0x${device.deviceNetworkId} 1 9", "delay 500",
|
|
||||||
|
|
||||||
"zcl global send-me-a-report 0x0F 0x55 0x10 1 0 {01}", "delay 500",
|
|
||||||
"send 0x${device.deviceNetworkId} 1 10", "delay 500",
|
|
||||||
|
|
||||||
"zcl global send-me-a-report 0x0F 0x55 0x10 1 0 {01}", "delay 500",
|
|
||||||
"send 0x${device.deviceNetworkId} 1 11", "delay 500",
|
|
||||||
|
|
||||||
"zcl global send-me-a-report 0x0F 0x55 0x10 1 0 {01}", "delay 500",
|
|
||||||
"send 0x${device.deviceNetworkId} 1 12", "delay 500",
|
|
||||||
|
|
||||||
"zcl global send-me-a-report 0x0F 0x55 0x10 1 0 {01}", "delay 500",
|
|
||||||
"send 0x${device.deviceNetworkId} 1 13", "delay 500",
|
|
||||||
|
|
||||||
"zcl global send-me-a-report 0x0F 0x55 0x10 1 0 {01}", "delay 500",
|
|
||||||
"send 0x${device.deviceNetworkId} 1 14", "delay 500",
|
|
||||||
|
|
||||||
"zcl global send-me-a-report 0x0F 0x55 0x10 1 0 {01}", "delay 500",
|
|
||||||
"send 0x${device.deviceNetworkId} 1 15", "delay 500",
|
|
||||||
|
|
||||||
"zcl global send-me-a-report 0x0F 0x55 0x10 1 0 {01}", "delay 500",
|
|
||||||
"send 0x${device.deviceNetworkId} 1 16", "delay 500",
|
|
||||||
|
|
||||||
"zcl global send-me-a-report 0x0F 0x55 0x10 1 0 {01}", "delay 500",
|
|
||||||
"send 0x${device.deviceNetworkId} 1 17", "delay 500",
|
|
||||||
|
|
||||||
"zcl global send-me-a-report 0x0F 0x55 0x10 1 0 {01}", "delay 500",
|
|
||||||
"send 0x${device.deviceNetworkId} 1 18", "delay 500",
|
|
||||||
|
|
||||||
"zcl global send-me-a-report 0x09 0x00 0x21 1 0 {00}", "delay 500",
|
|
||||||
"send 0x${device.deviceNetworkId} 1 1", "delay 500"
|
|
||||||
]
|
|
||||||
return configCmds + rain() + manual()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private hex(value) {
|
|
||||||
new BigInteger(Math.round(value).toString()).toString(16)
|
|
||||||
}
|
|
||||||
|
|
||||||
private String swapEndianHex(String hex) {
|
|
||||||
reverseArray(hex.decodeHex()).encodeHex()
|
|
||||||
}
|
|
||||||
|
|
||||||
private byte[] reverseArray(byte[] array) {
|
|
||||||
int i = 0;
|
|
||||||
int j = array.length - 1;
|
|
||||||
byte tmp;
|
|
||||||
while (j > i) {
|
|
||||||
tmp = array[j];
|
|
||||||
array[j] = array[i];
|
|
||||||
array[i] = tmp;
|
|
||||||
j--;
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
return array
|
|
||||||
}
|
|
||||||
|
|
||||||
def refresh() {
|
|
||||||
|
|
||||||
log.debug "refresh"
|
|
||||||
def refreshCmds = [
|
|
||||||
|
|
||||||
"st rattr 0x${device.deviceNetworkId} 1 0x0F 0x55", "delay 500",
|
|
||||||
|
|
||||||
"st rattr 0x${device.deviceNetworkId} 2 0x0F 0x55", "delay 500",
|
|
||||||
"st rattr 0x${device.deviceNetworkId} 3 0x0F 0x55", "delay 500",
|
|
||||||
"st rattr 0x${device.deviceNetworkId} 4 0x0F 0x55", "delay 500",
|
|
||||||
"st rattr 0x${device.deviceNetworkId} 5 0x0F 0x55", "delay 500",
|
|
||||||
"st rattr 0x${device.deviceNetworkId} 6 0x0F 0x55", "delay 500",
|
|
||||||
"st rattr 0x${device.deviceNetworkId} 7 0x0F 0x55", "delay 500",
|
|
||||||
"st rattr 0x${device.deviceNetworkId} 8 0x0F 0x55", "delay 500",
|
|
||||||
"st rattr 0x${device.deviceNetworkId} 9 0x0F 0x55", "delay 500",
|
|
||||||
|
|
||||||
"st rattr 0x${device.deviceNetworkId} 10 0x0F 0x55", "delay 500",
|
|
||||||
"st rattr 0x${device.deviceNetworkId} 11 0x0F 0x55", "delay 500",
|
|
||||||
"st rattr 0x${device.deviceNetworkId} 12 0x0F 0x55", "delay 500",
|
|
||||||
"st rattr 0x${device.deviceNetworkId} 13 0x0F 0x55", "delay 500",
|
|
||||||
"st rattr 0x${device.deviceNetworkId} 14 0x0F 0x55", "delay 500",
|
|
||||||
"st rattr 0x${device.deviceNetworkId} 15 0x0F 0x55", "delay 500",
|
|
||||||
"st rattr 0x${device.deviceNetworkId} 16 0x0F 0x55", "delay 500",
|
|
||||||
"st rattr 0x${device.deviceNetworkId} 17 0x0F 0x55", "delay 500",
|
|
||||||
|
|
||||||
"st rattr 0x${device.deviceNetworkId} 18 0x0F 0x51","delay 500",
|
|
||||||
|
|
||||||
]
|
|
||||||
return refreshCmds + rain() + manual()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Commands to device
|
|
||||||
//zones on - 8
|
|
||||||
def on() {
|
|
||||||
//sendEvent(name:"status", value:"active", descriptionText:"Program Running", isStateChange: true, display: false)
|
|
||||||
log.debug "on"
|
|
||||||
"st cmd 0x${device.deviceNetworkId} 1 6 1 {}"
|
|
||||||
}
|
|
||||||
def off() {
|
|
||||||
log.debug "off"
|
|
||||||
"st cmd 0x${device.deviceNetworkId} 1 6 0 {}"
|
|
||||||
}
|
|
||||||
def z1on() {
|
|
||||||
"st cmd 0x${device.deviceNetworkId} 2 6 1 {}"
|
|
||||||
}
|
|
||||||
def z1off() {
|
|
||||||
"st cmd 0x${device.deviceNetworkId} 2 6 0 {}"
|
|
||||||
}
|
|
||||||
def z2on() {
|
|
||||||
"st cmd 0x${device.deviceNetworkId} 3 6 1 {}"
|
|
||||||
}
|
|
||||||
def z2off() {
|
|
||||||
"st cmd 0x${device.deviceNetworkId} 3 6 0 {}"
|
|
||||||
}
|
|
||||||
def z3on() {
|
|
||||||
"st cmd 0x${device.deviceNetworkId} 4 6 1 {}"
|
|
||||||
}
|
|
||||||
def z3off() {
|
|
||||||
"st cmd 0x${device.deviceNetworkId} 4 6 0 {}"
|
|
||||||
}
|
|
||||||
def z4on() {
|
|
||||||
"st cmd 0x${device.deviceNetworkId} 5 6 1 {}"
|
|
||||||
}
|
|
||||||
def z4off() {
|
|
||||||
"st cmd 0x${device.deviceNetworkId} 5 6 0 {}"
|
|
||||||
}
|
|
||||||
def z5on() {
|
|
||||||
"st cmd 0x${device.deviceNetworkId} 6 6 1 {}"
|
|
||||||
}
|
|
||||||
def z5off() {
|
|
||||||
"st cmd 0x${device.deviceNetworkId} 6 6 0 {}"
|
|
||||||
}
|
|
||||||
def z6on() {
|
|
||||||
"st cmd 0x${device.deviceNetworkId} 7 6 1 {}"
|
|
||||||
}
|
|
||||||
def z6off() {
|
|
||||||
"st cmd 0x${device.deviceNetworkId} 7 6 0 {}"
|
|
||||||
}
|
|
||||||
def z7on() {
|
|
||||||
"st cmd 0x${device.deviceNetworkId} 8 6 1 {}"
|
|
||||||
}
|
|
||||||
def z7off() {
|
|
||||||
"st cmd 0x${device.deviceNetworkId} 8 6 0 {}"
|
|
||||||
}
|
|
||||||
def z8on() {
|
|
||||||
"st cmd 0x${device.deviceNetworkId} 9 6 1 {}"
|
|
||||||
}
|
|
||||||
def z8off() {
|
|
||||||
"st cmd 0x${device.deviceNetworkId} 9 6 0 {}"
|
|
||||||
}
|
|
||||||
|
|
||||||
//zones 9 - 16
|
|
||||||
def z9on() {
|
|
||||||
"st cmd 0x${device.deviceNetworkId} 10 6 1 {}"
|
|
||||||
}
|
|
||||||
def z9off() {
|
|
||||||
"st cmd 0x${device.deviceNetworkId} 10 6 0 {}"
|
|
||||||
}
|
|
||||||
def z10on() {
|
|
||||||
"st cmd 0x${device.deviceNetworkId} 11 6 1 {}"
|
|
||||||
}
|
|
||||||
def z10off() {
|
|
||||||
"st cmd 0x${device.deviceNetworkId} 11 6 0 {}"
|
|
||||||
}
|
|
||||||
def z11on() {
|
|
||||||
"st cmd 0x${device.deviceNetworkId} 12 6 1 {}"
|
|
||||||
}
|
|
||||||
def z11off() {
|
|
||||||
"st cmd 0x${device.deviceNetworkId} 12 6 0 {}"
|
|
||||||
}
|
|
||||||
def z12on() {
|
|
||||||
"st cmd 0x${device.deviceNetworkId} 13 6 1 {}"
|
|
||||||
}
|
|
||||||
def z12off() {
|
|
||||||
"st cmd 0x${device.deviceNetworkId} 13 6 0 {}"
|
|
||||||
}
|
|
||||||
def z13on() {
|
|
||||||
"st cmd 0x${device.deviceNetworkId} 14 6 1 {}"
|
|
||||||
}
|
|
||||||
def z13off() {
|
|
||||||
"st cmd 0x${device.deviceNetworkId} 14 6 0 {}"
|
|
||||||
}
|
|
||||||
def z14on() {
|
|
||||||
"st cmd 0x${device.deviceNetworkId} 15 6 1 {}"
|
|
||||||
}
|
|
||||||
def z14off() {
|
|
||||||
"st cmd 0x${device.deviceNetworkId} 15 6 0 {}"
|
|
||||||
}
|
|
||||||
def z15on() {
|
|
||||||
"st cmd 0x${device.deviceNetworkId} 16 6 1 {}"
|
|
||||||
}
|
|
||||||
def z15off() {
|
|
||||||
"st cmd 0x${device.deviceNetworkId} 16 6 0 {}"
|
|
||||||
}
|
|
||||||
def z16on() {
|
|
||||||
"st cmd 0x${device.deviceNetworkId} 17 6 1 {}"
|
|
||||||
}
|
|
||||||
def z16off() {
|
|
||||||
"st cmd 0x${device.deviceNetworkId} 17 6 0 {}"
|
|
||||||
}
|
|
||||||
@@ -1,397 +0,0 @@
|
|||||||
/**
|
|
||||||
* Spruce Sensor -Pre-release V2 10/8/2015
|
|
||||||
*
|
|
||||||
* Copyright 2014 Plaid Systems
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*
|
|
||||||
-------10/20/2015 Updates--------
|
|
||||||
-Fix/add battery reporting interval to update
|
|
||||||
-remove polling and/or refresh(?)
|
|
||||||
*/
|
|
||||||
metadata {
|
|
||||||
definition (name: "Spruce Sensor", namespace: "plaidsystems", author: "NCauffman") {
|
|
||||||
|
|
||||||
capability "Configuration"
|
|
||||||
capability "Battery"
|
|
||||||
capability "Relative Humidity Measurement"
|
|
||||||
capability "Temperature Measurement"
|
|
||||||
capability "Sensor"
|
|
||||||
//capability "Polling"
|
|
||||||
|
|
||||||
attribute "maxHum", "string"
|
|
||||||
attribute "minHum", "string"
|
|
||||||
|
|
||||||
command "resetHumidity"
|
|
||||||
command "refresh"
|
|
||||||
|
|
||||||
fingerprint profileId: "0104", inClusters: "0000,0001,0003,0402,0405", outClusters: "0003, 0019", manufacturer: "PLAID SYSTEMS", model: "PS-SPRZMS-01"
|
|
||||||
}
|
|
||||||
|
|
||||||
preferences {
|
|
||||||
input description: "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\".", displayDuringSetup: false, type: "paragraph", element: "paragraph", title: ""
|
|
||||||
input "tempOffset", "number", title: "Temperature Offset", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false
|
|
||||||
input "interval", "number", title: "Measurement Interval 1-120 minutes (default: 10 minutes)", description: "Set how often you would like to check soil moisture in minutes", range: "1..120", defaultValue: 10, displayDuringSetup: false
|
|
||||||
input "resetMinMax", "bool", title: "Reset Humidity min and max", required: false, displayDuringSetup: false
|
|
||||||
}
|
|
||||||
|
|
||||||
tiles {
|
|
||||||
valueTile("temperature", "device.temperature", canChangeIcon: false, canChangeBackground: false) {
|
|
||||||
state "temperature", label:'${currentValue}°',
|
|
||||||
backgroundColors:[
|
|
||||||
[value: 31, color: "#153591"],
|
|
||||||
[value: 44, color: "#1e9cbb"],
|
|
||||||
[value: 59, color: "#90d2a7"],
|
|
||||||
[value: 74, color: "#44b621"],
|
|
||||||
[value: 84, color: "#f1d801"],
|
|
||||||
[value: 95, color: "#d04e00"],
|
|
||||||
[value: 96, color: "#bc2323"]
|
|
||||||
]
|
|
||||||
}
|
|
||||||
valueTile("humidity", "device.humidity", width: 2, height: 2, canChangeIcon: false, canChangeBackground: true) {
|
|
||||||
state "humidity", label:'${currentValue}%', unit:"",
|
|
||||||
backgroundColors:[
|
|
||||||
[value: 0, color: "#635C0C"],
|
|
||||||
[value: 16, color: "#EBEB21"],
|
|
||||||
[value: 22, color: "#C7DE6A"],
|
|
||||||
[value: 42, color: "#9AD290"],
|
|
||||||
[value: 64, color: "#44B621"],
|
|
||||||
[value: 80, color: "#3D79D9"],
|
|
||||||
[value: 96, color: "#0A50C2"]
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
valueTile("maxHum", "device.maxHum", canChangeIcon: false, canChangeBackground: false) {
|
|
||||||
state "maxHum", label:'High ${currentValue}%', unit:"",
|
|
||||||
backgroundColors:[
|
|
||||||
[value: 0, color: "#635C0C"],
|
|
||||||
[value: 16, color: "#EBEB21"],
|
|
||||||
[value: 22, color: "#C7DE6A"],
|
|
||||||
[value: 42, color: "#9AD290"],
|
|
||||||
[value: 64, color: "#44B621"],
|
|
||||||
[value: 80, color: "#3D79D9"],
|
|
||||||
[value: 96, color: "#0A50C2"]
|
|
||||||
]
|
|
||||||
}
|
|
||||||
valueTile("minHum", "device.minHum", canChangeIcon: false, canChangeBackground: false) {
|
|
||||||
state "minHum", label:'Low ${currentValue}%', unit:"",
|
|
||||||
backgroundColors:[
|
|
||||||
[value: 0, color: "#635C0C"],
|
|
||||||
[value: 16, color: "#EBEB21"],
|
|
||||||
[value: 22, color: "#C7DE6A"],
|
|
||||||
[value: 42, color: "#9AD290"],
|
|
||||||
[value: 64, color: "#44B621"],
|
|
||||||
[value: 80, color: "#3D79D9"],
|
|
||||||
[value: 96, color: "#0A50C2"]
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
valueTile("battery", "device.battery", decoration: "flat", canChangeIcon: false, canChangeBackground: false) {
|
|
||||||
state "battery", label:'${currentValue}% battery'
|
|
||||||
}
|
|
||||||
|
|
||||||
main (["humidity"])
|
|
||||||
details(["humidity","maxHum","minHum","temperature","battery"])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def parse(String description) {
|
|
||||||
log.debug "Parse description $description config: ${device.latestValue('configuration')} interval: $interval"
|
|
||||||
|
|
||||||
Map map = [:]
|
|
||||||
|
|
||||||
if (description?.startsWith('catchall:')) {
|
|
||||||
map = parseCatchAllMessage(description)
|
|
||||||
}
|
|
||||||
else if (description?.startsWith('read attr -')) {
|
|
||||||
map = parseReportAttributeMessage(description)
|
|
||||||
}
|
|
||||||
else if (description?.startsWith('temperature: ') || description?.startsWith('humidity: ')) {
|
|
||||||
map = parseCustomMessage(description)
|
|
||||||
}
|
|
||||||
def result = map ? createEvent(map) : null
|
|
||||||
|
|
||||||
//check in configuration change
|
|
||||||
if (!device.latestValue('configuration')) result = poll()
|
|
||||||
if (device.latestValue('configuration').toInteger() != interval && interval != null) {
|
|
||||||
result = poll()
|
|
||||||
}
|
|
||||||
log.debug "result: $result"
|
|
||||||
return result
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private Map parseCatchAllMessage(String description) {
|
|
||||||
Map resultMap = [:]
|
|
||||||
def linkText = getLinkText(device)
|
|
||||||
//log.debug "Catchall"
|
|
||||||
def descMap = zigbee.parse(description)
|
|
||||||
|
|
||||||
//check humidity configuration is complete
|
|
||||||
if (descMap.command == 0x07 && descMap.clusterId == 0x0405){
|
|
||||||
def configInterval = 10
|
|
||||||
if (interval != null) configInterval = interval
|
|
||||||
sendEvent(name: 'configuration',value: configInterval, descriptionText: "Configuration Successful")
|
|
||||||
//setConfig()
|
|
||||||
log.debug "config complete"
|
|
||||||
//return resultMap = [name: 'configuration', value: configInterval, descriptionText: "Settings configured successfully"]
|
|
||||||
}
|
|
||||||
else if (descMap.command == 0x0001){
|
|
||||||
def hexString = "${hex(descMap.data[5])}" + "${hex(descMap.data[4])}"
|
|
||||||
def intString = Integer.parseInt(hexString, 16)
|
|
||||||
//log.debug "command: $descMap.command clusterid: $descMap.clusterId $hexString $intString"
|
|
||||||
|
|
||||||
if (descMap.clusterId == 0x0402){
|
|
||||||
def value = getTemperature(hexString)
|
|
||||||
resultMap = getTemperatureResult(value)
|
|
||||||
}
|
|
||||||
else if (descMap.clusterId == 0x0405){
|
|
||||||
def value = Math.round(new BigDecimal(intString / 100)).toString()
|
|
||||||
resultMap = getHumidityResult(value)
|
|
||||||
|
|
||||||
}
|
|
||||||
else return null
|
|
||||||
}
|
|
||||||
else return null
|
|
||||||
|
|
||||||
return resultMap
|
|
||||||
}
|
|
||||||
|
|
||||||
private Map parseReportAttributeMessage(String description) {
|
|
||||||
def descMap = parseDescriptionAsMap(description)
|
|
||||||
log.debug "Desc Map: $descMap"
|
|
||||||
log.debug "Report Attributes"
|
|
||||||
|
|
||||||
Map resultMap = [:]
|
|
||||||
if (descMap.cluster == "0001" && descMap.attrId == "0000") {
|
|
||||||
resultMap = getBatteryResult(descMap.value)
|
|
||||||
}
|
|
||||||
return resultMap
|
|
||||||
}
|
|
||||||
|
|
||||||
def parseDescriptionAsMap(description) {
|
|
||||||
(description - "read attr - ").split(",").inject([:]) { map, param ->
|
|
||||||
def nameAndValue = param.split(":")
|
|
||||||
map += [(nameAndValue[0].trim()):nameAndValue[1].trim()]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Map parseCustomMessage(String description) {
|
|
||||||
Map resultMap = [:]
|
|
||||||
|
|
||||||
log.debug "parseCustom"
|
|
||||||
if (description?.startsWith('temperature: ')) {
|
|
||||||
def value = zigbee.parseHATemperatureValue(description, "temperature: ", getTemperatureScale())
|
|
||||||
resultMap = getTemperatureResult(value)
|
|
||||||
}
|
|
||||||
else if (description?.startsWith('humidity: ')) {
|
|
||||||
def pct = (description - "humidity: " - "%").trim()
|
|
||||||
if (pct.isNumber()) {
|
|
||||||
def value = Math.round(new BigDecimal(pct)).toString()
|
|
||||||
resultMap = getHumidityResult(value)
|
|
||||||
} else {
|
|
||||||
log.error "invalid humidity: ${pct}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return resultMap
|
|
||||||
}
|
|
||||||
|
|
||||||
private Map getHumidityResult(value) {
|
|
||||||
def linkText = getLinkText(device)
|
|
||||||
def maxHumValue = 0
|
|
||||||
def minHumValue = 0
|
|
||||||
if (device.currentValue("maxHum") != null) maxHumValue = device.currentValue("maxHum").toInteger()
|
|
||||||
if (device.currentValue("minHum") != null) minHumValue = device.currentValue("minHum").toInteger()
|
|
||||||
log.debug "Humidity max: ${maxHumValue} min: ${minHumValue}"
|
|
||||||
def compare = value.toInteger()
|
|
||||||
|
|
||||||
if (compare > maxHumValue) {
|
|
||||||
sendEvent(name: 'maxHum', value: value, unit: '%', descriptionText: "${linkText} soil moisture high is ${value}%")
|
|
||||||
}
|
|
||||||
else if (((compare < minHumValue) || (minHumValue <= 2)) && (compare != 0)) {
|
|
||||||
sendEvent(name: 'minHum', value: value, unit: '%', descriptionText: "${linkText} soil moisture low is ${value}%")
|
|
||||||
}
|
|
||||||
|
|
||||||
return [
|
|
||||||
name: 'humidity',
|
|
||||||
value: value,
|
|
||||||
unit: '%',
|
|
||||||
descriptionText: "${linkText} soil moisture is ${value}%"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def getTemperature(value) {
|
|
||||||
def celsius = (Integer.parseInt(value, 16).shortValue()/100)
|
|
||||||
//log.debug "Report Temp $value : $celsius C"
|
|
||||||
if(getTemperatureScale() == "C"){
|
|
||||||
return celsius
|
|
||||||
} else {
|
|
||||||
return celsiusToFahrenheit(celsius) as Integer
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Map getTemperatureResult(value) {
|
|
||||||
log.debug "Temperature: $value"
|
|
||||||
def linkText = getLinkText(device)
|
|
||||||
|
|
||||||
if (tempOffset) {
|
|
||||||
def offset = tempOffset as int
|
|
||||||
def v = value as int
|
|
||||||
value = v + offset
|
|
||||||
}
|
|
||||||
def descriptionText = "${linkText} is ${value}°${temperatureScale}"
|
|
||||||
return [
|
|
||||||
name: 'temperature',
|
|
||||||
value: value,
|
|
||||||
descriptionText: descriptionText
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
private Map getBatteryResult(value) {
|
|
||||||
log.debug 'Battery'
|
|
||||||
def linkText = getLinkText(device)
|
|
||||||
|
|
||||||
def result = [
|
|
||||||
name: 'battery'
|
|
||||||
]
|
|
||||||
|
|
||||||
def min = 2500
|
|
||||||
def percent = ((Integer.parseInt(value, 16) - min) / 5)
|
|
||||||
percent = Math.max(0, Math.min(percent, 100.0))
|
|
||||||
result.value = Math.round(percent)
|
|
||||||
|
|
||||||
def descriptionText
|
|
||||||
if (percent < 10) result.descriptionText = "${linkText} battery is getting low $percent %."
|
|
||||||
else result.descriptionText = "${linkText} battery is ${result.value}%"
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
def resetHumidity(){
|
|
||||||
def linkText = getLinkText(device)
|
|
||||||
def minHumValue = 0
|
|
||||||
def maxHumValue = 0
|
|
||||||
sendEvent(name: 'minHum', value: minHumValue, unit: '%', descriptionText: "${linkText} min soil moisture reset to ${minHumValue}%")
|
|
||||||
sendEvent(name: 'maxHum', value: maxHumValue, unit: '%', descriptionText: "${linkText} max soil moisture reset to ${maxHumValue}%")
|
|
||||||
}
|
|
||||||
|
|
||||||
def setConfig(){
|
|
||||||
def configInterval = 100
|
|
||||||
if (interval != null) configInterval = interval
|
|
||||||
sendEvent(name: 'configuration',value: configInterval, descriptionText: "Configuration initialized")
|
|
||||||
}
|
|
||||||
|
|
||||||
//when device preferences are changed
|
|
||||||
def updated(){
|
|
||||||
log.debug "device updated"
|
|
||||||
if (!device.latestValue('configuration')) configure()
|
|
||||||
else{
|
|
||||||
if (resetMinMax == true) resetHumidity()
|
|
||||||
if (device.latestValue('configuration').toInteger() != interval && interval != null){
|
|
||||||
sendEvent(name: 'configuration',value: 0, descriptionText: "Settings changed and will update at next report. Measure interval set to ${interval} mins")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//poll
|
|
||||||
def poll() {
|
|
||||||
log.debug "poll called"
|
|
||||||
List cmds = []
|
|
||||||
if (!device.latestValue('configuration')) cmds += configure()
|
|
||||||
else if (device.latestValue('configuration').toInteger() != interval && interval != null) {
|
|
||||||
cmds += intervalUpdate()
|
|
||||||
}
|
|
||||||
//cmds += refresh()
|
|
||||||
log.debug "commands $cmds"
|
|
||||||
return cmds?.collect { new physicalgraph.device.HubAction(it) }
|
|
||||||
}
|
|
||||||
|
|
||||||
//update intervals
|
|
||||||
def intervalUpdate(){
|
|
||||||
log.debug "intervalUpdate"
|
|
||||||
def minReport = 10
|
|
||||||
def maxReport = 610
|
|
||||||
if (interval != null) {
|
|
||||||
minReport = interval
|
|
||||||
maxReport = interval * 61
|
|
||||||
}
|
|
||||||
[
|
|
||||||
"zcl global send-me-a-report 0x405 0x0000 0x21 $minReport $maxReport {6400}", "delay 500",
|
|
||||||
"send 0x${device.deviceNetworkId} 1 1", "delay 500",
|
|
||||||
"zcl global send-me-a-report 1 0x0000 0x21 0x0C 0 {0500}", "delay 500",
|
|
||||||
"send 0x${device.deviceNetworkId} 1 1", "delay 500",
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
def refresh() {
|
|
||||||
log.debug "refresh"
|
|
||||||
[
|
|
||||||
"st rattr 0x${device.deviceNetworkId} 1 0x402 0", "delay 500",
|
|
||||||
"st rattr 0x${device.deviceNetworkId} 1 0x405 0", "delay 500",
|
|
||||||
"st rattr 0x${device.deviceNetworkId} 1 1 0"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
//configure
|
|
||||||
def configure() {
|
|
||||||
//set minReport = measurement in minutes
|
|
||||||
def minReport = 10
|
|
||||||
def maxReport = 610
|
|
||||||
|
|
||||||
//String zigbeeId = swapEndianHex(device.hub.zigbeeId)
|
|
||||||
//log.debug "zigbeeid ${device.zigbeeId} deviceId ${device.deviceNetworkId}"
|
|
||||||
if (!device.zigbeeId) sendEvent(name: 'configuration',value: 0, descriptionText: "Device Zigbee Id not found, remove and attempt to rejoin device")
|
|
||||||
else sendEvent(name: 'configuration',value: 100, descriptionText: "Configuration initialized")
|
|
||||||
//log.debug "Configuring Reporting and Bindings. min: $minReport max: $maxReport "
|
|
||||||
|
|
||||||
[
|
|
||||||
"zdo bind 0x${device.deviceNetworkId} 1 1 0x402 {${device.zigbeeId}} {}", "delay 500",
|
|
||||||
"zdo bind 0x${device.deviceNetworkId} 1 1 0x405 {${device.zigbeeId}} {}", "delay 500",
|
|
||||||
"zdo bind 0x${device.deviceNetworkId} 1 1 1 {${device.zigbeeId}} {}", "delay 1000",
|
|
||||||
|
|
||||||
//temperature
|
|
||||||
"zcl global send-me-a-report 0x402 0x0000 0x29 1 0 {3200}",
|
|
||||||
"send 0x${device.deviceNetworkId} 1 1", "delay 500",
|
|
||||||
|
|
||||||
//min = soil measure interval
|
|
||||||
"zcl global send-me-a-report 0x405 0x0000 0x21 $minReport $maxReport {6400}",
|
|
||||||
"send 0x${device.deviceNetworkId} 1 1", "delay 500",
|
|
||||||
|
|
||||||
//min = battery measure interval 1 = 1 hour
|
|
||||||
"zcl global send-me-a-report 1 0x0000 0x21 0x0C 0 {0500}",
|
|
||||||
"send 0x${device.deviceNetworkId} 1 1", "delay 500"
|
|
||||||
] + refresh()
|
|
||||||
}
|
|
||||||
|
|
||||||
private hex(value) {
|
|
||||||
new BigInteger(Math.round(value).toString()).toString(16)
|
|
||||||
}
|
|
||||||
|
|
||||||
private String swapEndianHex(String hex) {
|
|
||||||
reverseArray(hex.decodeHex()).encodeHex()
|
|
||||||
}
|
|
||||||
|
|
||||||
private byte[] reverseArray(byte[] array) {
|
|
||||||
int i = 0;
|
|
||||||
int j = array.length - 1;
|
|
||||||
byte tmp;
|
|
||||||
while (j > i) {
|
|
||||||
tmp = array[j];
|
|
||||||
array[j] = array[i];
|
|
||||||
array[i] = tmp;
|
|
||||||
j--;
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
return array
|
|
||||||
}
|
|
||||||
@@ -1,128 +0,0 @@
|
|||||||
/**
|
|
||||||
* Simple Sync
|
|
||||||
*
|
|
||||||
* Copyright 2015 Roomie Remote, Inc.
|
|
||||||
*
|
|
||||||
* Date: 2015-09-22
|
|
||||||
*/
|
|
||||||
metadata
|
|
||||||
{
|
|
||||||
definition (name: "Simple Sync", namespace: "roomieremote-agent", author: "Roomie Remote, Inc.")
|
|
||||||
{
|
|
||||||
capability "Media Controller"
|
|
||||||
}
|
|
||||||
|
|
||||||
// simulator metadata
|
|
||||||
simulator
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
// UI tile definitions
|
|
||||||
tiles
|
|
||||||
{
|
|
||||||
standardTile("mainTile", "device.status", width: 1, height: 1, icon: "st.Entertainment.entertainment11")
|
|
||||||
{
|
|
||||||
state "default", label: "Simple Sync", icon: "st.Home.home2", backgroundColor: "#55A7FF"
|
|
||||||
}
|
|
||||||
|
|
||||||
def detailTiles = ["mainTile"]
|
|
||||||
|
|
||||||
main "mainTile"
|
|
||||||
details(detailTiles)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def parse(String description)
|
|
||||||
{
|
|
||||||
def results = []
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
def msg = parseLanMessage(description)
|
|
||||||
|
|
||||||
if (msg.headers && msg.body)
|
|
||||||
{
|
|
||||||
switch (msg.headers["X-Roomie-Echo"])
|
|
||||||
{
|
|
||||||
case "getAllActivities":
|
|
||||||
handleGetAllActivitiesResponse(msg)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Throwable t)
|
|
||||||
{
|
|
||||||
sendEvent(name: "parseError", value: "$t", description: description)
|
|
||||||
throw t
|
|
||||||
}
|
|
||||||
|
|
||||||
results
|
|
||||||
}
|
|
||||||
|
|
||||||
def handleGetAllActivitiesResponse(response)
|
|
||||||
{
|
|
||||||
def body = parseJson(response.body)
|
|
||||||
|
|
||||||
if (body.status == "success")
|
|
||||||
{
|
|
||||||
def json = new groovy.json.JsonBuilder()
|
|
||||||
def root = json activities: body.data
|
|
||||||
def data = json.toString()
|
|
||||||
|
|
||||||
sendEvent(name: "activities", value: data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def getAllActivities(evt)
|
|
||||||
{
|
|
||||||
def host = getHostAddress(device.deviceNetworkId)
|
|
||||||
|
|
||||||
def action = new physicalgraph.device.HubAction(method: "GET",
|
|
||||||
path: "/api/v1/activities",
|
|
||||||
headers: [HOST: host, "X-Roomie-Echo": "getAllActivities"])
|
|
||||||
|
|
||||||
action
|
|
||||||
}
|
|
||||||
|
|
||||||
def startActivity(evt)
|
|
||||||
{
|
|
||||||
def uuid = evt
|
|
||||||
def host = getHostAddress(device.deviceNetworkId)
|
|
||||||
def activity = new groovy.json.JsonSlurper().parseText(device.currentValue('activities') ?: "{ 'activities' : [] }").activities.find { it.uuid == uuid }
|
|
||||||
def toggle = activity["toggle"]
|
|
||||||
def jsonMap = ["activity_uuid": uuid]
|
|
||||||
|
|
||||||
if (toggle != null)
|
|
||||||
{
|
|
||||||
jsonMap << ["toggle_state": toggle ? "on" : "off"]
|
|
||||||
}
|
|
||||||
|
|
||||||
def json = new groovy.json.JsonBuilder(jsonMap)
|
|
||||||
def jsonBody = json.toString()
|
|
||||||
def headers = [HOST: host, "Content-Type": "application/json"]
|
|
||||||
|
|
||||||
def action = new physicalgraph.device.HubAction(method: "POST",
|
|
||||||
path: "/api/v1/runactivity",
|
|
||||||
body: jsonBody,
|
|
||||||
headers: headers)
|
|
||||||
|
|
||||||
action
|
|
||||||
}
|
|
||||||
|
|
||||||
def getHostAddress(d)
|
|
||||||
{
|
|
||||||
def parts = d.split(":")
|
|
||||||
def ip = convertHexToIP(parts[0])
|
|
||||||
def port = convertHexToInt(parts[1])
|
|
||||||
return ip + ":" + port
|
|
||||||
}
|
|
||||||
|
|
||||||
def String convertHexToIP(hex)
|
|
||||||
{
|
|
||||||
[convertHexToInt(hex[0..1]),convertHexToInt(hex[2..3]),convertHexToInt(hex[4..5]),convertHexToInt(hex[6..7])].join(".")
|
|
||||||
}
|
|
||||||
|
|
||||||
def Integer convertHexToInt(hex)
|
|
||||||
{
|
|
||||||
Integer.parseInt(hex,16)
|
|
||||||
}
|
|
||||||
@@ -11,17 +11,6 @@
|
|||||||
* for the specific language governing permissions and limitations under the License.
|
* for the specific language governing permissions and limitations under the License.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/*
|
|
||||||
* Purpose: Arrival Sensor HA DTH File
|
|
||||||
*
|
|
||||||
* Filename: Arrival-Sensor-HA.src/Arrival-Sensor-HA.groovy
|
|
||||||
*
|
|
||||||
* Change History:
|
|
||||||
* 1. 20160115 TW - Update/Edit to support i18n translations
|
|
||||||
* 2. 20160121 TW - Update to V4 battery calcs, added pref's page title translations
|
|
||||||
*/
|
|
||||||
|
|
||||||
metadata {
|
metadata {
|
||||||
definition (name: "Arrival Sensor HA", namespace: "smartthings", author: "SmartThings") {
|
definition (name: "Arrival Sensor HA", namespace: "smartthings", author: "SmartThings") {
|
||||||
capability "Tone"
|
capability "Tone"
|
||||||
@@ -43,7 +32,7 @@ metadata {
|
|||||||
])
|
])
|
||||||
}
|
}
|
||||||
section {
|
section {
|
||||||
input "checkInterval", "enum", title: "Presence timeout (minutes)", description: "Tap to set",
|
input "checkInterval", "enum", title: "Presence timeout (minutes)",
|
||||||
defaultValue:"2", options: ["2", "3", "5"], displayDuringSetup: false
|
defaultValue:"2", options: ["2", "3", "5"], displayDuringSetup: false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -93,6 +82,7 @@ def parse(String description) {
|
|||||||
|
|
||||||
private handleReportAttributeMessage(String description) {
|
private handleReportAttributeMessage(String description) {
|
||||||
def descMap = zigbee.parseDescriptionAsMap(description)
|
def descMap = zigbee.parseDescriptionAsMap(description)
|
||||||
|
|
||||||
if (descMap.clusterInt == 0x0001 && descMap.attrInt == 0x0020) {
|
if (descMap.clusterInt == 0x0001 && descMap.attrInt == 0x0020) {
|
||||||
handleBatteryEvent(Integer.parseInt(descMap.value, 16))
|
handleBatteryEvent(Integer.parseInt(descMap.value, 16))
|
||||||
}
|
}
|
||||||
@@ -104,7 +94,6 @@ private handleReportAttributeMessage(String description) {
|
|||||||
* @param volts Battery voltage in .1V increments
|
* @param volts Battery voltage in .1V increments
|
||||||
*/
|
*/
|
||||||
private handleBatteryEvent(volts) {
|
private handleBatteryEvent(volts) {
|
||||||
def descriptionText
|
|
||||||
if (volts == 0 || volts == 255) {
|
if (volts == 0 || volts == 255) {
|
||||||
log.debug "Ignoring invalid value for voltage (${volts/10}V)"
|
log.debug "Ignoring invalid value for voltage (${volts/10}V)"
|
||||||
}
|
}
|
||||||
@@ -118,17 +107,15 @@ private handleBatteryEvent(volts) {
|
|||||||
volts = minVolts
|
volts = minVolts
|
||||||
else if (volts > maxVolts)
|
else if (volts > maxVolts)
|
||||||
volts = maxVolts
|
volts = maxVolts
|
||||||
def value = batteryMap[volts]
|
def pct = batteryMap[volts]
|
||||||
if (value != null) {
|
if (pct != null) {
|
||||||
def linkText = getLinkText(device)
|
def linkText = getLinkText(device)
|
||||||
descriptionText = '{{ linkText }} battery was {{ value }}'
|
|
||||||
def eventMap = [
|
def eventMap = [
|
||||||
name: 'battery',
|
name: 'battery',
|
||||||
value: value,
|
value: pct,
|
||||||
descriptionText: descriptionText,
|
descriptionText: "${linkText} battery was ${pct}%"
|
||||||
translatable: true
|
|
||||||
]
|
]
|
||||||
log.debug "Creating battery event for voltage=${volts/10}V: ${linkText} ${eventMap.name} is ${eventMap.value}%"
|
log.debug "Creating battery event for voltage=${volts/10}V: ${eventMap}"
|
||||||
sendEvent(eventMap)
|
sendEvent(eventMap)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -144,19 +131,13 @@ private handlePresenceEvent(present) {
|
|||||||
stopTimer()
|
stopTimer()
|
||||||
}
|
}
|
||||||
def linkText = getLinkText(device)
|
def linkText = getLinkText(device)
|
||||||
def descriptionText
|
|
||||||
if ( present )
|
|
||||||
descriptionText = "{{ linkText }} has arrived"
|
|
||||||
else
|
|
||||||
descriptionText = "{{ linkText }} has left"
|
|
||||||
def eventMap = [
|
def eventMap = [
|
||||||
name: "presence",
|
name: "presence",
|
||||||
value: present ? "present" : "not present",
|
value: present ? "present" : "not present",
|
||||||
linkText: linkText,
|
linkText: linkText,
|
||||||
descriptionText: descriptionText,
|
descriptionText: "${linkText} has ${present ? 'arrived' : 'left'}",
|
||||||
translatable: true
|
|
||||||
]
|
]
|
||||||
log.debug "Creating presence event: ${device.displayName} ${eventMap.name} is ${eventMap.value}"
|
log.debug "Creating presence event: ${eventMap}"
|
||||||
sendEvent(eventMap)
|
sendEvent(eventMap)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -177,4 +158,4 @@ def checkPresenceCallback() {
|
|||||||
if (timeSinceLastCheckin >= theCheckInterval) {
|
if (timeSinceLastCheckin >= theCheckInterval) {
|
||||||
handlePresenceEvent(false)
|
handlePresenceEvent(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,35 +0,0 @@
|
|||||||
#==============================================================================
|
|
||||||
# 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: Arrival Sensor HA i18n Translation File
|
|
||||||
#
|
|
||||||
# Filename: Arrival-Sensor-HA.src/i18n/messages.properties
|
|
||||||
#
|
|
||||||
# Change History:
|
|
||||||
# 1. 20160115 TW Initial release with informal Korean translation.
|
|
||||||
# 2. 20160121 TW Added def preference section titles.
|
|
||||||
#==============================================================================
|
|
||||||
# Korean (ko)
|
|
||||||
# Device Preferences
|
|
||||||
'''Give your device a name'''.ko=기기 이름 바꾸기
|
|
||||||
'''Set Device Image'''.ko=기기 이미지 설정
|
|
||||||
'''Presence timeout (minutes)'''.ko=시간 초과. 스마트폰 위치 정보
|
|
||||||
'''Tap to set'''.ko=눌러서 설정
|
|
||||||
'''Arrival Sensor'''.ko=도착알림 센서
|
|
||||||
# Events / Notifications
|
|
||||||
'''{{ linkText }} battery was {{ value }}'''.ko={{ linkText }}남아있는 배터리는 {{ value }}입니다.
|
|
||||||
'''{{ linkText }} has arrived'''.ko={{ linkText }}집에 도착했습니다.
|
|
||||||
'''{{ linkText }} has left'''.ko={{ linkText }}집을 나갔습니다.
|
|
||||||
#==============================================================================
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
* Cree Bulb
|
* Cree Bulb
|
||||||
*
|
*
|
||||||
* Copyright 2016 SmartThings
|
* Copyright 2014 SmartThings
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||||
* in compliance with the License. You may obtain a copy of the License at:
|
* in compliance with the License. You may obtain a copy of the License at:
|
||||||
@@ -15,29 +15,29 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
metadata {
|
metadata {
|
||||||
definition (name: "Cree Bulb", namespace: "smartthings", author: "SmartThings") {
|
definition (name: "Cree Bulb", namespace: "smartthings", author: "SmartThings") {
|
||||||
|
|
||||||
capability "Actuator"
|
capability "Actuator"
|
||||||
capability "Configuration"
|
capability "Configuration"
|
||||||
capability "Refresh"
|
capability "Refresh"
|
||||||
capability "Switch"
|
capability "Switch"
|
||||||
capability "Switch Level"
|
capability "Switch Level"
|
||||||
|
|
||||||
fingerprint profileId: "C05E", inClusters: "0000,0003,0004,0005,0006,0008,1000", outClusters: "0000,0019"
|
fingerprint profileId: "C05E", inClusters: "0000,0003,0004,0005,0006,0008,1000", outClusters: "0000,0019"
|
||||||
}
|
}
|
||||||
|
|
||||||
// simulator metadata
|
// simulator metadata
|
||||||
simulator {
|
simulator {
|
||||||
// status messages
|
// status messages
|
||||||
status "on": "on/off: 1"
|
status "on": "on/off: 1"
|
||||||
status "off": "on/off: 0"
|
status "off": "on/off: 0"
|
||||||
|
|
||||||
// reply messages
|
// reply messages
|
||||||
reply "zcl on-off on": "on/off: 1"
|
reply "zcl on-off on": "on/off: 1"
|
||||||
reply "zcl on-off off": "on/off: 0"
|
reply "zcl on-off off": "on/off: 0"
|
||||||
}
|
}
|
||||||
|
|
||||||
// UI tile definitions
|
// UI tile definitions
|
||||||
tiles(scale: 2) {
|
tiles(scale: 2) {
|
||||||
multiAttributeTile(name:"switch", type: "lighting", width: 6, height: 4, canChangeIcon: true){
|
multiAttributeTile(name:"switch", type: "lighting", width: 6, height: 4, canChangeIcon: true){
|
||||||
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
|
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
|
||||||
@@ -62,12 +62,18 @@ metadata {
|
|||||||
def parse(String description) {
|
def parse(String description) {
|
||||||
log.debug "description is $description"
|
log.debug "description is $description"
|
||||||
|
|
||||||
def resultMap = zigbee.getEvent(description)
|
def resultMap = zigbee.getKnownDescription(description)
|
||||||
if (resultMap) {
|
if (resultMap) {
|
||||||
sendEvent(resultMap)
|
log.info resultMap
|
||||||
|
if (resultMap.type == "update") {
|
||||||
|
log.info "$device updates: ${resultMap.value}"
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
sendEvent(name: resultMap.type, value: resultMap.value)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
log.debug "DID NOT PARSE MESSAGE for description : $description"
|
log.warn "DID NOT PARSE MESSAGE for description : $description"
|
||||||
log.debug zigbee.parseDescriptionAsMap(description)
|
log.debug zigbee.parseDescriptionAsMap(description)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -81,7 +87,7 @@ def on() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
def setLevel(value) {
|
def setLevel(value) {
|
||||||
zigbee.setLevel(value) + ["delay 500"] + zigbee.levelRefresh() //adding refresh because of ZLL bulb not conforming to send-me-a-report
|
zigbee.setLevel(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
def refresh() {
|
def refresh() {
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
* Copyright 2015 SmartThings
|
* Ecobee Sensor
|
||||||
|
*
|
||||||
|
* Copyright 2015 Juan Risso
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||||
* in compliance with the License. You may obtain a copy of the License at:
|
* in compliance with the License. You may obtain a copy of the License at:
|
||||||
@@ -10,9 +12,6 @@
|
|||||||
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
|
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
|
||||||
* for the specific language governing permissions and limitations under the License.
|
* for the specific language governing permissions and limitations under the License.
|
||||||
*
|
*
|
||||||
* Ecobee Sensor
|
|
||||||
*
|
|
||||||
* Author: SmartThings
|
|
||||||
*/
|
*/
|
||||||
metadata {
|
metadata {
|
||||||
definition (name: "Ecobee Sensor", namespace: "smartthings", author: "SmartThings") {
|
definition (name: "Ecobee Sensor", namespace: "smartthings", author: "SmartThings") {
|
||||||
@@ -20,22 +19,18 @@ metadata {
|
|||||||
capability "Temperature Measurement"
|
capability "Temperature Measurement"
|
||||||
capability "Motion Sensor"
|
capability "Motion Sensor"
|
||||||
capability "Refresh"
|
capability "Refresh"
|
||||||
|
capability "Polling"
|
||||||
|
}
|
||||||
|
|
||||||
|
simulator {
|
||||||
|
// TODO: define status and reply messages here
|
||||||
}
|
}
|
||||||
|
|
||||||
tiles {
|
tiles {
|
||||||
valueTile("temperature", "device.temperature", width: 2, height: 2) {
|
valueTile("temperature", "device.temperature", width: 2, height: 2) {
|
||||||
state("temperature", label:'${currentValue}°', unit:"F",
|
state("temperature", label:'${currentValue}°', unit:"F",
|
||||||
backgroundColors:[
|
backgroundColors:[
|
||||||
// Celsius
|
[value: 31, color: "#153591"],
|
||||||
[value: 0, color: "#153591"],
|
|
||||||
[value: 7, color: "#1e9cbb"],
|
|
||||||
[value: 15, color: "#90d2a7"],
|
|
||||||
[value: 23, color: "#44b621"],
|
|
||||||
[value: 28, color: "#f1d801"],
|
|
||||||
[value: 35, color: "#d04e00"],
|
|
||||||
[value: 37, color: "#bc2323"],
|
|
||||||
// Fahrenheit
|
|
||||||
[value: 40, color: "#153591"],
|
|
||||||
[value: 44, color: "#1e9cbb"],
|
[value: 44, color: "#1e9cbb"],
|
||||||
[value: 59, color: "#90d2a7"],
|
[value: 59, color: "#90d2a7"],
|
||||||
[value: 74, color: "#44b621"],
|
[value: 74, color: "#44b621"],
|
||||||
@@ -47,8 +42,8 @@ metadata {
|
|||||||
}
|
}
|
||||||
|
|
||||||
standardTile("motion", "device.motion") {
|
standardTile("motion", "device.motion") {
|
||||||
state("inactive", label:'no motion', icon:"st.motion.motion.inactive", backgroundColor:"#ffffff")
|
|
||||||
state("active", label:'motion', icon:"st.motion.motion.active", backgroundColor:"#53a7c0")
|
state("active", label:'motion', icon:"st.motion.motion.active", backgroundColor:"#53a7c0")
|
||||||
|
state("inactive", label:'no motion', icon:"st.motion.motion.inactive", backgroundColor:"#ffffff")
|
||||||
}
|
}
|
||||||
|
|
||||||
standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat") {
|
standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat") {
|
||||||
@@ -61,12 +56,16 @@ metadata {
|
|||||||
}
|
}
|
||||||
|
|
||||||
def refresh() {
|
def refresh() {
|
||||||
log.debug "refresh called"
|
log.debug "refresh..."
|
||||||
poll()
|
poll()
|
||||||
}
|
}
|
||||||
|
|
||||||
void poll() {
|
void poll() {
|
||||||
log.debug "Executing 'poll' using parent SmartApp"
|
log.debug "Executing 'poll' using parent SmartApp"
|
||||||
parent.pollChild()
|
parent.pollChildren(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
//generate custom mobile activity feeds event
|
||||||
|
def generateActivityFeedsEvent(notificationMessage) {
|
||||||
|
sendEvent(name: "notificationMessage", value: "$device.displayName $notificationMessage", descriptionText: "$device.displayName $notificationMessage", displayed: true)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,48 +19,34 @@ metadata {
|
|||||||
definition (name: "Ecobee Thermostat", namespace: "smartthings", author: "SmartThings") {
|
definition (name: "Ecobee Thermostat", namespace: "smartthings", author: "SmartThings") {
|
||||||
capability "Actuator"
|
capability "Actuator"
|
||||||
capability "Thermostat"
|
capability "Thermostat"
|
||||||
capability "Temperature Measurement"
|
capability "Polling"
|
||||||
capability "Sensor"
|
capability "Sensor"
|
||||||
capability "Refresh"
|
capability "Refresh"
|
||||||
capability "Relative Humidity Measurement"
|
|
||||||
|
|
||||||
command "generateEvent"
|
command "generateEvent"
|
||||||
command "raiseSetpoint"
|
command "raiseSetpoint"
|
||||||
command "lowerSetpoint"
|
command "lowerSetpoint"
|
||||||
command "resumeProgram"
|
command "resumeProgram"
|
||||||
command "switchMode"
|
command "switchMode"
|
||||||
command "switchFanMode"
|
|
||||||
|
|
||||||
attribute "thermostatSetpoint","number"
|
attribute "thermostatSetpoint","number"
|
||||||
attribute "thermostatStatus","string"
|
attribute "thermostatStatus","string"
|
||||||
attribute "maxHeatingSetpoint", "number"
|
|
||||||
attribute "minHeatingSetpoint", "number"
|
|
||||||
attribute "maxCoolingSetpoint", "number"
|
|
||||||
attribute "minCoolingSetpoint", "number"
|
|
||||||
attribute "deviceTemperatureUnit", "number"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tiles {
|
simulator { }
|
||||||
|
|
||||||
|
tiles {
|
||||||
valueTile("temperature", "device.temperature", width: 2, height: 2) {
|
valueTile("temperature", "device.temperature", width: 2, height: 2) {
|
||||||
state("temperature", label:'${currentValue}°', unit:"F",
|
state("temperature", label:'${currentValue}°', unit:"F",
|
||||||
backgroundColors:[
|
backgroundColors:[
|
||||||
// Celsius
|
[value: 31, color: "#153591"],
|
||||||
[value: 0, color: "#153591"],
|
[value: 44, color: "#1e9cbb"],
|
||||||
[value: 7, color: "#1e9cbb"],
|
[value: 59, color: "#90d2a7"],
|
||||||
[value: 15, color: "#90d2a7"],
|
[value: 74, color: "#44b621"],
|
||||||
[value: 23, color: "#44b621"],
|
[value: 84, color: "#f1d801"],
|
||||||
[value: 28, color: "#f1d801"],
|
[value: 95, color: "#d04e00"],
|
||||||
[value: 35, color: "#d04e00"],
|
[value: 96, color: "#bc2323"]
|
||||||
[value: 37, color: "#bc2323"],
|
]
|
||||||
// Fahrenheit
|
|
||||||
[value: 40, color: "#153591"],
|
|
||||||
[value: 44, color: "#1e9cbb"],
|
|
||||||
[value: 59, color: "#90d2a7"],
|
|
||||||
[value: 74, color: "#44b621"],
|
|
||||||
[value: 84, color: "#f1d801"],
|
|
||||||
[value: 95, color: "#d04e00"],
|
|
||||||
[value: 96, color: "#bc2323"]
|
|
||||||
]
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
standardTile("mode", "device.thermostatMode", inactiveLabel: false, decoration: "flat") {
|
standardTile("mode", "device.thermostatMode", inactiveLabel: false, decoration: "flat") {
|
||||||
@@ -72,9 +58,10 @@ metadata {
|
|||||||
state "updating", label:"Working", icon: "st.secondary.secondary"
|
state "updating", label:"Working", icon: "st.secondary.secondary"
|
||||||
}
|
}
|
||||||
standardTile("fanMode", "device.thermostatFanMode", inactiveLabel: false, decoration: "flat") {
|
standardTile("fanMode", "device.thermostatFanMode", inactiveLabel: false, decoration: "flat") {
|
||||||
state "auto", action:"switchFanMode", nextState: "updating", icon: "st.thermostat.fan-auto"
|
state "auto", label:'Fan: ${currentValue}', action:"switchFanMode", nextState: "on"
|
||||||
state "on", action:"switchFanMode", nextState: "updating", icon: "st.thermostat.fan-on"
|
state "on", label:'Fan: ${currentValue}', action:"switchFanMode", nextState: "off"
|
||||||
state "updating", label:"Working", icon: "st.secondary.secondary"
|
state "off", label:'Fan: ${currentValue}', action:"switchFanMode", nextState: "circulate"
|
||||||
|
state "circulate", label:'Fan: ${currentValue}', action:"switchFanMode", nextState: "auto"
|
||||||
}
|
}
|
||||||
standardTile("upButtonControl", "device.thermostatSetpoint", inactiveLabel: false, decoration: "flat") {
|
standardTile("upButtonControl", "device.thermostatSetpoint", inactiveLabel: false, decoration: "flat") {
|
||||||
state "setpoint", action:"raiseSetpoint", icon:"st.thermostat.thermostat-up"
|
state "setpoint", action:"raiseSetpoint", icon:"st.thermostat.thermostat-up"
|
||||||
@@ -104,14 +91,11 @@ metadata {
|
|||||||
state "default", action:"refresh.refresh", icon:"st.secondary.refresh"
|
state "default", action:"refresh.refresh", icon:"st.secondary.refresh"
|
||||||
}
|
}
|
||||||
standardTile("resumeProgram", "device.resumeProgram", inactiveLabel: false, decoration: "flat") {
|
standardTile("resumeProgram", "device.resumeProgram", inactiveLabel: false, decoration: "flat") {
|
||||||
state "resume", action:"resumeProgram", nextState: "updating", label:'Resume', icon:"st.samsung.da.oven_ic_send"
|
state "resume", action:"resumeProgram", nextState: "updating", label:'Resume Schedule', icon:"st.samsung.da.oven_ic_send"
|
||||||
state "updating", label:"Working", icon: "st.secondary.secondary"
|
state "updating", label:"Working", icon: "st.secondary.secondary"
|
||||||
}
|
}
|
||||||
valueTile("humidity", "device.humidity", decoration: "flat") {
|
|
||||||
state "humidity", label:'${currentValue}%'
|
|
||||||
}
|
|
||||||
main "temperature"
|
main "temperature"
|
||||||
details(["temperature", "upButtonControl", "thermostatSetpoint", "currentStatus", "downButtonControl", "mode", "fanMode","humidity", "resumeProgram", "refresh"])
|
details(["temperature", "upButtonControl", "thermostatSetpoint", "currentStatus", "downButtonControl", "mode", "resumeProgram", "refresh"])
|
||||||
}
|
}
|
||||||
|
|
||||||
preferences {
|
preferences {
|
||||||
@@ -123,6 +107,8 @@ metadata {
|
|||||||
// parse events into attributes
|
// parse events into attributes
|
||||||
def parse(String description) {
|
def parse(String description) {
|
||||||
log.debug "Parsing '${description}'"
|
log.debug "Parsing '${description}'"
|
||||||
|
// TODO: handle '' attribute
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def refresh() {
|
def refresh() {
|
||||||
@@ -133,7 +119,9 @@ def refresh() {
|
|||||||
|
|
||||||
void poll() {
|
void poll() {
|
||||||
log.debug "Executing 'poll' using parent SmartApp"
|
log.debug "Executing 'poll' using parent SmartApp"
|
||||||
parent.pollChild()
|
|
||||||
|
def results = parent.pollChild(this)
|
||||||
|
generateEvent(results) //parse received message from parent
|
||||||
}
|
}
|
||||||
|
|
||||||
def generateEvent(Map results) {
|
def generateEvent(Map results) {
|
||||||
@@ -145,27 +133,16 @@ def generateEvent(Map results) {
|
|||||||
def isChange = false
|
def isChange = false
|
||||||
def isDisplayed = true
|
def isDisplayed = true
|
||||||
def event = [name: name, linkText: linkText, descriptionText: getThermostatDescriptionText(name, value, linkText),
|
def event = [name: name, linkText: linkText, descriptionText: getThermostatDescriptionText(name, value, linkText),
|
||||||
handlerName: name]
|
handlerName: name]
|
||||||
|
|
||||||
if (name=="temperature" || name=="heatingSetpoint" || name=="coolingSetpoint" ) {
|
if (name=="temperature" || name=="heatingSetpoint" || name=="coolingSetpoint") {
|
||||||
def sendValue = convertTemperatureIfNeeded(value.toDouble(), "F", 1) //API return temperature value in F
|
def sendValue = value? convertTemperatureIfNeeded(value.toDouble(), "F", 1): value //API return temperature value in F
|
||||||
sendValue = location.temperatureScale == "C"? roundC(sendValue) : sendValue
|
|
||||||
isChange = isTemperatureStateChange(device, name, value.toString())
|
isChange = isTemperatureStateChange(device, name, value.toString())
|
||||||
isDisplayed = isChange
|
isDisplayed = isChange
|
||||||
event << [value: sendValue, isStateChange: isChange, displayed: isDisplayed]
|
event << [value: sendValue, isStateChange: isChange, displayed: isDisplayed]
|
||||||
} else if (name=="maxCoolingSetpoint" || name=="minCoolingSetpoint" || name=="maxHeatingSetpoint" || name=="minHeatingSetpoint") {
|
} else if (name=="heatMode" || name=="coolMode" || name=="autoMode" || name=="auxHeatMode"){
|
||||||
def sendValue = convertTemperatureIfNeeded(value.toDouble(), "F", 1) //API return temperature value in F
|
|
||||||
sendValue = location.temperatureScale == "C"? roundC(sendValue) : sendValue
|
|
||||||
event << [value: sendValue, displayed: false]
|
|
||||||
} else if (name=="heatMode" || name=="coolMode" || name=="autoMode" || name=="auxHeatMode"){
|
|
||||||
isChange = isStateChange(device, name, value.toString())
|
isChange = isStateChange(device, name, value.toString())
|
||||||
event << [value: value.toString(), isStateChange: isChange, displayed: false]
|
event << [value: value.toString(), isStateChange: isChange, displayed: false]
|
||||||
} else if (name=="thermostatFanMode"){
|
|
||||||
isChange = isStateChange(device, name, value.toString())
|
|
||||||
event << [value: value.toString(), isStateChange: isChange, displayed: false]
|
|
||||||
} else if (name=="humidity") {
|
|
||||||
isChange = isStateChange(device, name, value.toString())
|
|
||||||
event << [value: value.toString(), isStateChange: isChange, displayed: false, unit: "%"]
|
|
||||||
} else {
|
} else {
|
||||||
isChange = isStateChange(device, name, value.toString())
|
isChange = isStateChange(device, name, value.toString())
|
||||||
isDisplayed = isChange
|
isDisplayed = isChange
|
||||||
@@ -181,19 +158,13 @@ def generateEvent(Map results) {
|
|||||||
//return descriptionText to be shown on mobile activity feed
|
//return descriptionText to be shown on mobile activity feed
|
||||||
private getThermostatDescriptionText(name, value, linkText) {
|
private getThermostatDescriptionText(name, value, linkText) {
|
||||||
if(name == "temperature") {
|
if(name == "temperature") {
|
||||||
def sendValue = convertTemperatureIfNeeded(value.toDouble(), "F", 1) //API return temperature value in F
|
return "$linkText temperature is $value°F"
|
||||||
sendValue = location.temperatureScale == "C"? roundC(sendValue) : sendValue
|
|
||||||
return "$linkText temperature is $sendValue ${location.temperatureScale}"
|
|
||||||
|
|
||||||
} else if(name == "heatingSetpoint") {
|
} else if(name == "heatingSetpoint") {
|
||||||
def sendValue = convertTemperatureIfNeeded(value.toDouble(), "F", 1) //API return temperature value in F
|
return "heating setpoint is $value°F"
|
||||||
sendValue = location.temperatureScale == "C"? roundC(sendValue) : sendValue
|
|
||||||
return "heating setpoint is $sendValue ${location.temperatureScale}"
|
|
||||||
|
|
||||||
} else if(name == "coolingSetpoint"){
|
} else if(name == "coolingSetpoint"){
|
||||||
def sendValue = convertTemperatureIfNeeded(value.toDouble(), "F", 1) //API return temperature value in F
|
return "cooling setpoint is $value°F"
|
||||||
sendValue = location.temperatureScale == "C"? roundC(sendValue) : sendValue
|
|
||||||
return "cooling setpoint is $sendValue ${location.temperatureScale}"
|
|
||||||
|
|
||||||
} else if (name == "thermostatMode") {
|
} else if (name == "thermostatMode") {
|
||||||
return "thermostat mode is ${value}"
|
return "thermostat mode is ${value}"
|
||||||
@@ -201,26 +172,26 @@ private getThermostatDescriptionText(name, value, linkText) {
|
|||||||
} else if (name == "thermostatFanMode") {
|
} else if (name == "thermostatFanMode") {
|
||||||
return "thermostat fan mode is ${value}"
|
return "thermostat fan mode is ${value}"
|
||||||
|
|
||||||
} else if (name == "humidity") {
|
|
||||||
return "humidity is ${value} %"
|
|
||||||
} else {
|
} else {
|
||||||
return "${name} = ${value}"
|
return "${name} = ${value}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void setHeatingSetpoint(setpoint) {
|
void setHeatingSetpoint(setpoint) {
|
||||||
log.debug "***heating setpoint $setpoint"
|
setHeatingSetpoint(setpoint.toDouble())
|
||||||
|
}
|
||||||
|
|
||||||
|
void setHeatingSetpoint(Double setpoint) {
|
||||||
|
// def mode = device.currentValue("thermostatMode")
|
||||||
def heatingSetpoint = setpoint
|
def heatingSetpoint = setpoint
|
||||||
def coolingSetpoint = device.currentValue("coolingSetpoint")
|
def coolingSetpoint = device.currentValue("coolingSetpoint").toDouble()
|
||||||
def deviceId = device.deviceNetworkId.split(/\./).last()
|
def deviceId = device.deviceNetworkId.split(/\./).last()
|
||||||
def maxHeatingSetpoint = device.currentValue("maxHeatingSetpoint")
|
|
||||||
def minHeatingSetpoint = device.currentValue("minHeatingSetpoint")
|
|
||||||
|
|
||||||
//enforce limits of heatingSetpoint
|
//enforce limits of heatingSetpoint
|
||||||
if (heatingSetpoint > maxHeatingSetpoint) {
|
if (heatingSetpoint > 79) {
|
||||||
heatingSetpoint = maxHeatingSetpoint
|
heatingSetpoint = 79
|
||||||
} else if (heatingSetpoint < minHeatingSetpoint) {
|
} else if (heatingSetpoint < 45) {
|
||||||
heatingSetpoint = minHeatingSetpoint
|
heatingSetpoint = 45
|
||||||
}
|
}
|
||||||
|
|
||||||
//enforce limits of heatingSetpoint vs coolingSetpoint
|
//enforce limits of heatingSetpoint vs coolingSetpoint
|
||||||
@@ -230,34 +201,32 @@ void setHeatingSetpoint(setpoint) {
|
|||||||
|
|
||||||
log.debug "Sending setHeatingSetpoint> coolingSetpoint: ${coolingSetpoint}, heatingSetpoint: ${heatingSetpoint}"
|
log.debug "Sending setHeatingSetpoint> coolingSetpoint: ${coolingSetpoint}, heatingSetpoint: ${heatingSetpoint}"
|
||||||
|
|
||||||
def coolingValue = location.temperatureScale == "C"? convertCtoF(coolingSetpoint) : coolingSetpoint
|
|
||||||
def heatingValue = location.temperatureScale == "C"? convertCtoF(heatingSetpoint) : heatingSetpoint
|
|
||||||
|
|
||||||
def sendHoldType = holdType ? (holdType=="Temporary")? "nextTransition" : (holdType=="Permanent")? "indefinite" : "indefinite" : "indefinite"
|
def sendHoldType = holdType ? (holdType=="Temporary")? "nextTransition" : (holdType=="Permanent")? "indefinite" : "indefinite" : "indefinite"
|
||||||
if (parent.setHold(this, heatingValue, coolingValue, deviceId, sendHoldType)) {
|
if (parent.setHold (this, heatingSetpoint, coolingSetpoint, deviceId, sendHoldType)) {
|
||||||
sendEvent("name":"heatingSetpoint", "value":heatingSetpoint)
|
sendEvent("name":"heatingSetpoint", "value":heatingSetpoint)
|
||||||
sendEvent("name":"coolingSetpoint", "value":coolingSetpoint)
|
sendEvent("name":"coolingSetpoint", "value":coolingSetpoint)
|
||||||
log.debug "Done setHeatingSetpoint> coolingSetpoint: ${coolingSetpoint}, heatingSetpoint: ${heatingSetpoint}"
|
log.debug "Done setHeatingSetpoint> coolingSetpoint: ${coolingSetpoint}, heatingSetpoint: ${heatingSetpoint}"
|
||||||
generateSetpointEvent()
|
generateSetpointEvent()
|
||||||
generateStatusEvent()
|
generateStatusEvent()
|
||||||
} else {
|
} else {
|
||||||
log.error "Error setHeatingSetpoint(setpoint)"
|
log.error "Error setHeatingSetpoint(setpoint)" //This error is handled by the connect app
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void setCoolingSetpoint(setpoint) {
|
void setCoolingSetpoint(setpoint) {
|
||||||
log.debug "***cooling setpoint $setpoint"
|
setCoolingSetpoint(setpoint.toDouble())
|
||||||
def heatingSetpoint = device.currentValue("heatingSetpoint")
|
}
|
||||||
|
|
||||||
|
void setCoolingSetpoint(Double setpoint) {
|
||||||
|
// def mode = device.currentValue("thermostatMode")
|
||||||
|
def heatingSetpoint = device.currentValue("heatingSetpoint").toDouble()
|
||||||
def coolingSetpoint = setpoint
|
def coolingSetpoint = setpoint
|
||||||
def deviceId = device.deviceNetworkId.split(/\./).last()
|
def deviceId = device.deviceNetworkId.split(/\./).last()
|
||||||
def maxCoolingSetpoint = device.currentValue("maxCoolingSetpoint")
|
|
||||||
def minCoolingSetpoint = device.currentValue("minCoolingSetpoint")
|
|
||||||
|
|
||||||
|
if (coolingSetpoint > 92) {
|
||||||
if (coolingSetpoint > maxCoolingSetpoint) {
|
coolingSetpoint = 92
|
||||||
coolingSetpoint = maxCoolingSetpoint
|
} else if (coolingSetpoint < 65) {
|
||||||
} else if (coolingSetpoint < minCoolingSetpoint) {
|
coolingSetpoint = 65
|
||||||
coolingSetpoint = minCoolingSetpoint
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//enforce limits of heatingSetpoint vs coolingSetpoint
|
//enforce limits of heatingSetpoint vs coolingSetpoint
|
||||||
@@ -267,18 +236,15 @@ void setCoolingSetpoint(setpoint) {
|
|||||||
|
|
||||||
log.debug "Sending setCoolingSetpoint> coolingSetpoint: ${coolingSetpoint}, heatingSetpoint: ${heatingSetpoint}"
|
log.debug "Sending setCoolingSetpoint> coolingSetpoint: ${coolingSetpoint}, heatingSetpoint: ${heatingSetpoint}"
|
||||||
|
|
||||||
def coolingValue = location.temperatureScale == "C"? convertCtoF(coolingSetpoint) : coolingSetpoint
|
|
||||||
def heatingValue = location.temperatureScale == "C"? convertCtoF(heatingSetpoint) : heatingSetpoint
|
|
||||||
|
|
||||||
def sendHoldType = holdType ? (holdType=="Temporary")? "nextTransition" : (holdType=="Permanent")? "indefinite" : "indefinite" : "indefinite"
|
def sendHoldType = holdType ? (holdType=="Temporary")? "nextTransition" : (holdType=="Permanent")? "indefinite" : "indefinite" : "indefinite"
|
||||||
if (parent.setHold(this, heatingValue, coolingValue, deviceId, sendHoldType)) {
|
if (parent.setHold (this, heatingSetpoint, coolingSetpoint, deviceId, sendHoldType)) {
|
||||||
sendEvent("name":"heatingSetpoint", "value":heatingSetpoint)
|
sendEvent("name":"heatingSetpoint", "value":heatingSetpoint)
|
||||||
sendEvent("name":"coolingSetpoint", "value":coolingSetpoint)
|
sendEvent("name":"coolingSetpoint", "value":coolingSetpoint)
|
||||||
log.debug "Done setCoolingSetpoint>> coolingSetpoint = ${coolingSetpoint}, heatingSetpoint = ${heatingSetpoint}"
|
log.debug "Done setCoolingSetpoint>> coolingSetpoint = ${coolingSetpoint}, heatingSetpoint = ${heatingSetpoint}"
|
||||||
generateSetpointEvent()
|
generateSetpointEvent()
|
||||||
generateStatusEvent()
|
generateStatusEvent()
|
||||||
} else {
|
} else {
|
||||||
log.error "Error setCoolingSetpoint(setpoint)"
|
log.error "Error setCoolingSetpoint(setpoint)" //This error is handled by the connect app
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -312,7 +278,7 @@ def modes() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
def fanModes() {
|
def fanModes() {
|
||||||
["on", "auto"]
|
["off", "on", "auto", "circulate"]
|
||||||
}
|
}
|
||||||
|
|
||||||
def switchMode() {
|
def switchMode() {
|
||||||
@@ -341,15 +307,17 @@ def switchFanMode() {
|
|||||||
def returnCommand
|
def returnCommand
|
||||||
|
|
||||||
switch (currentFanMode) {
|
switch (currentFanMode) {
|
||||||
case "on":
|
case "fanAuto":
|
||||||
returnCommand = switchToFanMode("auto")
|
returnCommand = switchToFanMode("fanOn")
|
||||||
break
|
break
|
||||||
case "auto":
|
case "fanOn":
|
||||||
returnCommand = switchToFanMode("on")
|
returnCommand = switchToFanMode("fanCirculate")
|
||||||
|
break
|
||||||
|
case "fanCirculate":
|
||||||
|
returnCommand = switchToFanMode("fanAuto")
|
||||||
break
|
break
|
||||||
|
|
||||||
}
|
}
|
||||||
if(!currentFanMode) { returnCommand = switchToFanMode("auto") }
|
if(!currentFanMode) { returnCommand = switchToFanMode("fanOn") }
|
||||||
returnCommand
|
returnCommand
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -358,20 +326,25 @@ def switchToFanMode(nextMode) {
|
|||||||
log.debug "switching to fan mode: $nextMode"
|
log.debug "switching to fan mode: $nextMode"
|
||||||
def returnCommand
|
def returnCommand
|
||||||
|
|
||||||
if(nextMode == "auto") {
|
if(nextMode == "fanAuto") {
|
||||||
if(!fanModes.contains("auto")) {
|
if(!fanModes.contains("fanAuto")) {
|
||||||
returnCommand = fanAuto()
|
returnCommand = fanAuto()
|
||||||
} else {
|
} else {
|
||||||
returnCommand = switchToFanMode("on")
|
returnCommand = switchToFanMode("fanOn")
|
||||||
}
|
}
|
||||||
} else if(nextMode == "on") {
|
} else if(nextMode == "fanOn") {
|
||||||
if(!fanModes.contains("on")) {
|
if(!fanModes.contains("fanOn")) {
|
||||||
returnCommand = fanOn()
|
returnCommand = fanOn()
|
||||||
} else {
|
} else {
|
||||||
returnCommand = switchToFanMode("auto")
|
returnCommand = switchToFanMode("fanCirculate")
|
||||||
|
}
|
||||||
|
} else if(nextMode == "fanCirculate") {
|
||||||
|
if(!fanModes.contains("fanCirculate")) {
|
||||||
|
returnCommand = fanCirculate()
|
||||||
|
} else {
|
||||||
|
returnCommand = switchToFanMode("fanAuto")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
returnCommand
|
returnCommand
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -379,16 +352,15 @@ def getDataByName(String name) {
|
|||||||
state[name] ?: device.getDataValue(name)
|
state[name] ?: device.getDataValue(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
def setThermostatMode(String mode) {
|
def setThermostatMode(String value) {
|
||||||
log.debug "setThermostatMode($mode)"
|
log.debug "setThermostatMode({$value})"
|
||||||
mode = mode.toLowerCase()
|
|
||||||
switchToMode(mode)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def setThermostatFanMode(String mode) {
|
def setThermostatFanMode(String value) {
|
||||||
log.debug "setThermostatFanMode($mode)"
|
|
||||||
mode = mode.toLowerCase()
|
log.debug "setThermostatFanMode({$value})"
|
||||||
switchToFanMode(mode)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def generateModeEvent(mode) {
|
def generateModeEvent(mode) {
|
||||||
@@ -396,7 +368,7 @@ def generateModeEvent(mode) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
def generateFanModeEvent(fanMode) {
|
def generateFanModeEvent(fanMode) {
|
||||||
sendEvent(name: "thermostatFanMode", value: fanMode, descriptionText: "$device.displayName fan is in ${fanMode} mode", displayed: true)
|
sendEvent(name: "thermostatFanMode", value: fanMode, descriptionText: "$device.displayName fan is in ${mode} mode", displayed: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
def generateOperatingStateEvent(operatingState) {
|
def generateOperatingStateEvent(operatingState) {
|
||||||
@@ -431,10 +403,6 @@ def heat() {
|
|||||||
generateStatusEvent()
|
generateStatusEvent()
|
||||||
}
|
}
|
||||||
|
|
||||||
def emergencyHeat() {
|
|
||||||
auxHeatOnly()
|
|
||||||
}
|
|
||||||
|
|
||||||
def auxHeatOnly() {
|
def auxHeatOnly() {
|
||||||
log.debug "auxHeatOnly"
|
log.debug "auxHeatOnly"
|
||||||
def deviceId = device.deviceNetworkId.split(/\./).last()
|
def deviceId = device.deviceNetworkId.split(/\./).last()
|
||||||
@@ -479,44 +447,26 @@ def auto() {
|
|||||||
|
|
||||||
def fanOn() {
|
def fanOn() {
|
||||||
log.debug "fanOn"
|
log.debug "fanOn"
|
||||||
String fanMode = "on"
|
// parent.setFanMode (this,"on")
|
||||||
def heatingSetpoint = device.currentValue("heatingSetpoint")
|
|
||||||
def coolingSetpoint = device.currentValue("coolingSetpoint")
|
|
||||||
def deviceId = device.deviceNetworkId.split(/\./).last()
|
|
||||||
|
|
||||||
def sendHoldType = holdType ? (holdType=="Temporary")? "nextTransition" : (holdType=="Permanent")? "indefinite" : "indefinite" : "indefinite"
|
|
||||||
|
|
||||||
def coolingValue = location.temperatureScale == "C"? convertCtoF(coolingSetpoint) : coolingSetpoint
|
|
||||||
def heatingValue = location.temperatureScale == "C"? convertCtoF(heatingSetpoint) : heatingSetpoint
|
|
||||||
|
|
||||||
if (parent.setFanMode(this, heatingValue, coolingValue, deviceId, sendHoldType, fanMode)) {
|
|
||||||
generateFanModeEvent(fanMode)
|
|
||||||
} else {
|
|
||||||
log.debug "Error setting new mode."
|
|
||||||
def currentFanMode = device.currentState("thermostatFanMode")?.value
|
|
||||||
generateFanModeEvent(currentFanMode) // reset the tile back
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def fanAuto() {
|
def fanAuto() {
|
||||||
log.debug "fanAuto"
|
log.debug "fanAuto"
|
||||||
String fanMode = "auto"
|
// parent.setFanMode (this,"auto")
|
||||||
def heatingSetpoint = device.currentValue("heatingSetpoint")
|
|
||||||
def coolingSetpoint = device.currentValue("coolingSetpoint")
|
|
||||||
def deviceId = device.deviceNetworkId.split(/\./).last()
|
|
||||||
|
|
||||||
def sendHoldType = holdType ? (holdType=="Temporary")? "nextTransition" : (holdType=="Permanent")? "indefinite" : "indefinite" : "indefinite"
|
}
|
||||||
|
|
||||||
def coolingValue = location.temperatureScale == "C"? convertCtoF(coolingSetpoint) : coolingSetpoint
|
def fanCirculate() {
|
||||||
def heatingValue = location.temperatureScale == "C"? convertCtoF(heatingSetpoint) : heatingSetpoint
|
log.debug "fanCirculate"
|
||||||
|
// parent.setFanMode (this,"circulate")
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
def fanOff() {
|
||||||
|
log.debug "fanOff"
|
||||||
|
// parent.setFanMode (this,"off")
|
||||||
|
|
||||||
if (parent.setFanMode(this, heatingValue, coolingValue, deviceId, sendHoldType, fanMode)) {
|
|
||||||
generateFanModeEvent(fanMode)
|
|
||||||
} else {
|
|
||||||
log.debug "Error setting new mode."
|
|
||||||
def currentFanMode = device.currentState("thermostatFanMode")?.value
|
|
||||||
generateFanModeEvent(currentFanMode) // reset the tile back
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def generateSetpointEvent() {
|
def generateSetpointEvent() {
|
||||||
@@ -526,42 +476,20 @@ def generateSetpointEvent() {
|
|||||||
def mode = device.currentValue("thermostatMode")
|
def mode = device.currentValue("thermostatMode")
|
||||||
log.debug "Current Mode = ${mode}"
|
log.debug "Current Mode = ${mode}"
|
||||||
|
|
||||||
def heatingSetpoint = device.currentValue("heatingSetpoint")
|
def heatingSetpoint = device.currentValue("heatingSetpoint").toInteger()
|
||||||
log.debug "Heating Setpoint = ${heatingSetpoint}"
|
log.debug "Heating Setpoint = ${heatingSetpoint}"
|
||||||
|
|
||||||
def coolingSetpoint = device.currentValue("coolingSetpoint")
|
def coolingSetpoint = device.currentValue("coolingSetpoint").toInteger()
|
||||||
log.debug "Cooling Setpoint = ${coolingSetpoint}"
|
log.debug "Cooling Setpoint = ${coolingSetpoint}"
|
||||||
|
|
||||||
def maxHeatingSetpoint = device.currentValue("maxHeatingSetpoint")
|
|
||||||
def maxCoolingSetpoint = device.currentValue("maxCoolingSetpoint")
|
|
||||||
def minHeatingSetpoint = device.currentValue("minHeatingSetpoint")
|
|
||||||
def minCoolingSetpoint = device.currentValue("minCoolingSetpoint")
|
|
||||||
|
|
||||||
if(location.temperatureScale == "C")
|
|
||||||
{
|
|
||||||
maxHeatingSetpoint = roundC(maxHeatingSetpoint)
|
|
||||||
maxCoolingSetpoint = roundC(maxCoolingSetpoint)
|
|
||||||
minHeatingSetpoint = roundC(minHeatingSetpoint)
|
|
||||||
minCoolingSetpoint = roundC(minCoolingSetpoint)
|
|
||||||
heatingSetpoint = roundC(heatingSetpoint)
|
|
||||||
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)
|
|
||||||
sendEvent("name":"minCoolingSetpoint", "value":minCoolingSetpoint, "unit":location.temperatureScale)
|
|
||||||
|
|
||||||
|
|
||||||
if (mode == "heat") {
|
if (mode == "heat") {
|
||||||
|
|
||||||
sendEvent("name":"thermostatSetpoint", "value":heatingSetpoint )
|
sendEvent("name":"thermostatSetpoint", "value":heatingSetpoint.toString())
|
||||||
|
|
||||||
}
|
}
|
||||||
else if (mode == "cool") {
|
else if (mode == "cool") {
|
||||||
|
|
||||||
sendEvent("name":"thermostatSetpoint", "value":coolingSetpoint)
|
sendEvent("name":"thermostatSetpoint", "value":coolingSetpoint.toString())
|
||||||
|
|
||||||
} else if (mode == "auto") {
|
} else if (mode == "auto") {
|
||||||
|
|
||||||
@@ -571,9 +499,9 @@ def generateSetpointEvent() {
|
|||||||
|
|
||||||
sendEvent("name":"thermostatSetpoint", "value":"Off")
|
sendEvent("name":"thermostatSetpoint", "value":"Off")
|
||||||
|
|
||||||
} else if (mode == "auxHeatOnly") {
|
} else if (mode == "emergencyHeat") {
|
||||||
|
|
||||||
sendEvent("name":"thermostatSetpoint", "value":heatingSetpoint)
|
sendEvent("name":"thermostatSetpoint", "value":heatingSetpoint.toString())
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -582,30 +510,26 @@ def generateSetpointEvent() {
|
|||||||
void raiseSetpoint() {
|
void raiseSetpoint() {
|
||||||
def mode = device.currentValue("thermostatMode")
|
def mode = device.currentValue("thermostatMode")
|
||||||
def targetvalue
|
def targetvalue
|
||||||
def maxHeatingSetpoint = device.currentValue("maxHeatingSetpoint")
|
|
||||||
def maxCoolingSetpoint = device.currentValue("maxCoolingSetpoint")
|
|
||||||
|
|
||||||
|
|
||||||
if (mode == "off" || mode == "auto") {
|
if (mode == "off" || mode == "auto") {
|
||||||
log.warn "this mode: $mode does not allow raiseSetpoint"
|
log.warn "this mode: $mode does not allow raiseSetpoint"
|
||||||
} else {
|
} else {
|
||||||
def heatingSetpoint = device.currentValue("heatingSetpoint")
|
def heatingSetpoint = device.currentValue("heatingSetpoint").toInteger()
|
||||||
def coolingSetpoint = device.currentValue("coolingSetpoint")
|
def coolingSetpoint = device.currentValue("coolingSetpoint").toInteger()
|
||||||
def thermostatSetpoint = device.currentValue("thermostatSetpoint")
|
def thermostatSetpoint = device.currentValue("thermostatSetpoint").toInteger()
|
||||||
log.debug "raiseSetpoint() mode = ${mode}, heatingSetpoint: ${heatingSetpoint}, coolingSetpoint:${coolingSetpoint}, thermostatSetpoint:${thermostatSetpoint}"
|
log.debug "raiseSetpoint() mode = ${mode}, heatingSetpoint: ${heatingSetpoint}, coolingSetpoint:${coolingSetpoint}, thermostatSetpoint:${thermostatSetpoint}"
|
||||||
|
|
||||||
if (device.latestState('thermostatSetpoint')) {
|
if (device.latestState('thermostatSetpoint')) {
|
||||||
targetvalue = device.latestState('thermostatSetpoint').value
|
targetvalue = device.latestState('thermostatSetpoint').value as Integer
|
||||||
targetvalue = location.temperatureScale == "F"? targetvalue.toInteger() : targetvalue.toDouble()
|
|
||||||
} else {
|
} else {
|
||||||
targetvalue = 0
|
targetvalue = 0
|
||||||
}
|
}
|
||||||
targetvalue = location.temperatureScale == "F"? targetvalue + 1 : targetvalue + 0.5
|
targetvalue = targetvalue + 1
|
||||||
|
|
||||||
if ((mode == "heat" || mode == "auxHeatOnly") && targetvalue > maxHeatingSetpoint) {
|
if (mode == "heat" && targetvalue > 79) {
|
||||||
targetvalue = maxHeatingSetpoint
|
targetvalue = 79
|
||||||
} else if (mode == "cool" && targetvalue > maxCoolingSetpoint) {
|
} else if (mode == "cool" && targetvalue > 92) {
|
||||||
targetvalue = maxCoolingSetpoint
|
targetvalue = 92
|
||||||
}
|
}
|
||||||
|
|
||||||
sendEvent("name":"thermostatSetpoint", "value":targetvalue, displayed: false)
|
sendEvent("name":"thermostatSetpoint", "value":targetvalue, displayed: false)
|
||||||
@@ -619,29 +543,25 @@ void raiseSetpoint() {
|
|||||||
void lowerSetpoint() {
|
void lowerSetpoint() {
|
||||||
def mode = device.currentValue("thermostatMode")
|
def mode = device.currentValue("thermostatMode")
|
||||||
def targetvalue
|
def targetvalue
|
||||||
def minHeatingSetpoint = device.currentValue("minHeatingSetpoint")
|
|
||||||
def minCoolingSetpoint = device.currentValue("minCoolingSetpoint")
|
|
||||||
|
|
||||||
|
|
||||||
if (mode == "off" || mode == "auto") {
|
if (mode == "off" || mode == "auto") {
|
||||||
log.warn "this mode: $mode does not allow lowerSetpoint"
|
log.warn "this mode: $mode does not allow lowerSetpoint"
|
||||||
} else {
|
} else {
|
||||||
def heatingSetpoint = device.currentValue("heatingSetpoint")
|
def heatingSetpoint = device.currentValue("heatingSetpoint").toInteger()
|
||||||
def coolingSetpoint = device.currentValue("coolingSetpoint")
|
def coolingSetpoint = device.currentValue("coolingSetpoint").toInteger()
|
||||||
def thermostatSetpoint = device.currentValue("thermostatSetpoint")
|
def thermostatSetpoint = device.currentValue("thermostatSetpoint").toInteger()
|
||||||
log.debug "lowerSetpoint() mode = ${mode}, heatingSetpoint: ${heatingSetpoint}, coolingSetpoint:${coolingSetpoint}, thermostatSetpoint:${thermostatSetpoint}"
|
log.debug "lowerSetpoint() mode = ${mode}, heatingSetpoint: ${heatingSetpoint}, coolingSetpoint:${coolingSetpoint}, thermostatSetpoint:${thermostatSetpoint}"
|
||||||
if (device.latestState('thermostatSetpoint')) {
|
if (device.latestState('thermostatSetpoint')) {
|
||||||
targetvalue = device.latestState('thermostatSetpoint').value
|
targetvalue = device.latestState('thermostatSetpoint').value as Integer
|
||||||
targetvalue = location.temperatureScale == "F"? targetvalue.toInteger() : targetvalue.toDouble()
|
|
||||||
} else {
|
} else {
|
||||||
targetvalue = 0
|
targetvalue = 0
|
||||||
}
|
}
|
||||||
targetvalue = location.temperatureScale == "F"? targetvalue - 1 : targetvalue - 0.5
|
targetvalue = targetvalue - 1
|
||||||
|
|
||||||
if ((mode == "heat" || mode == "auxHeatOnly") && targetvalue < minHeatingSetpoint) {
|
if (mode == "heat" && targetvalue.toInteger() < 45) {
|
||||||
targetvalue = minHeatingSetpoint
|
targetvalue = 45
|
||||||
} else if (mode == "cool" && targetvalue < minCoolingSetpoint) {
|
} else if (mode == "cool" && targetvalue.toInteger() < 65) {
|
||||||
targetvalue = minCoolingSetpoint
|
targetvalue = 65
|
||||||
}
|
}
|
||||||
|
|
||||||
sendEvent("name":"thermostatSetpoint", "value":targetvalue, displayed: false)
|
sendEvent("name":"thermostatSetpoint", "value":targetvalue, displayed: false)
|
||||||
@@ -655,15 +575,15 @@ void lowerSetpoint() {
|
|||||||
void alterSetpoint(temp) {
|
void alterSetpoint(temp) {
|
||||||
|
|
||||||
def mode = device.currentValue("thermostatMode")
|
def mode = device.currentValue("thermostatMode")
|
||||||
def heatingSetpoint = device.currentValue("heatingSetpoint")
|
def heatingSetpoint = device.currentValue("heatingSetpoint").toInteger()
|
||||||
def coolingSetpoint = device.currentValue("coolingSetpoint")
|
def coolingSetpoint = device.currentValue("coolingSetpoint").toInteger()
|
||||||
def deviceId = device.deviceNetworkId.split(/\./).last()
|
def deviceId = device.deviceNetworkId.split(/\./).last()
|
||||||
|
|
||||||
def targetHeatingSetpoint
|
def targetHeatingSetpoint
|
||||||
def targetCoolingSetpoint
|
def targetCoolingSetpoint
|
||||||
|
|
||||||
//step1: check thermostatMode, enforce limits before sending request to cloud
|
//step1: check thermostatMode, enforce limits before sending request to cloud
|
||||||
if (mode == "heat" || mode == "auxHeatOnly"){
|
if (mode == "heat"){
|
||||||
if (temp.value > coolingSetpoint){
|
if (temp.value > coolingSetpoint){
|
||||||
targetHeatingSetpoint = temp.value
|
targetHeatingSetpoint = temp.value
|
||||||
targetCoolingSetpoint = temp.value
|
targetCoolingSetpoint = temp.value
|
||||||
@@ -682,22 +602,19 @@ void alterSetpoint(temp) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log.debug "alterSetpoint >> in mode ${mode} trying to change heatingSetpoint to $targetHeatingSetpoint " +
|
log.debug "alterSetpoint >> in mode ${mode} trying to change heatingSetpoint to ${targetHeatingSetpoint} " +
|
||||||
"coolingSetpoint to $targetCoolingSetpoint with holdType : ${holdType}"
|
"coolingSetpoint to ${targetCoolingSetpoint} with holdType : ${holdType}"
|
||||||
|
|
||||||
def sendHoldType = holdType ? (holdType=="Temporary")? "nextTransition" : (holdType=="Permanent")? "indefinite" : "indefinite" : "indefinite"
|
def sendHoldType = holdType ? (holdType=="Temporary")? "nextTransition" : (holdType=="Permanent")? "indefinite" : "indefinite" : "indefinite"
|
||||||
|
//step2: call parent.setHold to send http request to 3rd party cloud
|
||||||
def coolingValue = location.temperatureScale == "C"? convertCtoF(targetCoolingSetpoint) : targetCoolingSetpoint
|
if (parent.setHold(this, targetHeatingSetpoint, targetCoolingSetpoint, deviceId, sendHoldType)) {
|
||||||
def heatingValue = location.temperatureScale == "C"? convertCtoF(targetHeatingSetpoint) : targetHeatingSetpoint
|
sendEvent("name": "thermostatSetpoint", "value": temp.value.toString(), displayed: false)
|
||||||
|
|
||||||
if (parent.setHold(this, heatingValue, coolingValue, deviceId, sendHoldType)) {
|
|
||||||
sendEvent("name": "thermostatSetpoint", "value": temp.value, displayed: false)
|
|
||||||
sendEvent("name": "heatingSetpoint", "value": targetHeatingSetpoint)
|
sendEvent("name": "heatingSetpoint", "value": targetHeatingSetpoint)
|
||||||
sendEvent("name": "coolingSetpoint", "value": targetCoolingSetpoint)
|
sendEvent("name": "coolingSetpoint", "value": targetCoolingSetpoint)
|
||||||
log.debug "alterSetpoint in mode $mode succeed change setpoint to= ${temp.value}"
|
log.debug "alterSetpoint in mode $mode succeed change setpoint to= ${temp.value}"
|
||||||
} else {
|
} else {
|
||||||
log.error "Error alterSetpoint()"
|
log.error "Error alterSetpoint()"
|
||||||
if (mode == "heat" || mode == "auxHeatOnly"){
|
if (mode == "heat"){
|
||||||
sendEvent("name": "thermostatSetpoint", "value": heatingSetpoint.toString(), displayed: false)
|
sendEvent("name": "thermostatSetpoint", "value": heatingSetpoint.toString(), displayed: false)
|
||||||
} else if (mode == "cool") {
|
} else if (mode == "cool") {
|
||||||
sendEvent("name": "thermostatSetpoint", "value": coolingSetpoint.toString(), displayed: false)
|
sendEvent("name": "thermostatSetpoint", "value": coolingSetpoint.toString(), displayed: false)
|
||||||
@@ -709,9 +626,9 @@ void alterSetpoint(temp) {
|
|||||||
def generateStatusEvent() {
|
def generateStatusEvent() {
|
||||||
|
|
||||||
def mode = device.currentValue("thermostatMode")
|
def mode = device.currentValue("thermostatMode")
|
||||||
def heatingSetpoint = device.currentValue("heatingSetpoint")
|
def heatingSetpoint = device.currentValue("heatingSetpoint").toInteger()
|
||||||
def coolingSetpoint = device.currentValue("coolingSetpoint")
|
def coolingSetpoint = device.currentValue("coolingSetpoint").toInteger()
|
||||||
def temperature = device.currentValue("temperature")
|
def temperature = device.currentValue("temperature").toInteger()
|
||||||
|
|
||||||
def statusText
|
def statusText
|
||||||
|
|
||||||
@@ -726,14 +643,14 @@ def generateStatusEvent() {
|
|||||||
if (temperature >= heatingSetpoint)
|
if (temperature >= heatingSetpoint)
|
||||||
statusText = "Right Now: Idle"
|
statusText = "Right Now: Idle"
|
||||||
else
|
else
|
||||||
statusText = "Heating to ${heatingSetpoint} ${location.temperatureScale}"
|
statusText = "Heating to ${heatingSetpoint}° F"
|
||||||
|
|
||||||
} else if (mode == "cool") {
|
} else if (mode == "cool") {
|
||||||
|
|
||||||
if (temperature <= coolingSetpoint)
|
if (temperature <= coolingSetpoint)
|
||||||
statusText = "Right Now: Idle"
|
statusText = "Right Now: Idle"
|
||||||
else
|
else
|
||||||
statusText = "Cooling to ${coolingSetpoint} ${location.temperatureScale}"
|
statusText = "Cooling to ${coolingSetpoint}° F"
|
||||||
|
|
||||||
} else if (mode == "auto") {
|
} else if (mode == "auto") {
|
||||||
|
|
||||||
@@ -743,7 +660,7 @@ def generateStatusEvent() {
|
|||||||
|
|
||||||
statusText = "Right Now: Off"
|
statusText = "Right Now: Off"
|
||||||
|
|
||||||
} else if (mode == "auxHeatOnly") {
|
} else if (mode == "emergencyHeat") {
|
||||||
|
|
||||||
statusText = "Emergency Heat"
|
statusText = "Emergency Heat"
|
||||||
|
|
||||||
@@ -756,18 +673,7 @@ def generateStatusEvent() {
|
|||||||
sendEvent("name":"thermostatStatus", "value":statusText, "description":statusText, displayed: true)
|
sendEvent("name":"thermostatStatus", "value":statusText, "description":statusText, displayed: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//generate custom mobile activity feeds event
|
||||||
def generateActivityFeedsEvent(notificationMessage) {
|
def generateActivityFeedsEvent(notificationMessage) {
|
||||||
sendEvent(name: "notificationMessage", value: "$device.displayName $notificationMessage", descriptionText: "$device.displayName $notificationMessage", displayed: true)
|
sendEvent(name: "notificationMessage", value: "$device.displayName $notificationMessage", descriptionText: "$device.displayName $notificationMessage", displayed: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
def roundC (tempC) {
|
|
||||||
return (Math.round(tempC.toDouble() * 2))/2
|
|
||||||
}
|
|
||||||
|
|
||||||
def convertFtoC (tempF) {
|
|
||||||
return String.format("%.1f", (Math.round(((tempF - 32)*(5/9)) * 2))/2)
|
|
||||||
}
|
|
||||||
|
|
||||||
def convertCtoF (tempC) {
|
|
||||||
return (Math.round(tempC * (9/5)) + 32).toInteger()
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -36,71 +36,155 @@
|
|||||||
* Slider range from 0..100
|
* Slider range from 0..100
|
||||||
* Change 9: 2015-03-06 (Juan Risso)
|
* Change 9: 2015-03-06 (Juan Risso)
|
||||||
* Setlevel -> value to integer (to prevent smartapp calling this function from not working).
|
* Setlevel -> value to integer (to prevent smartapp calling this function from not working).
|
||||||
* Change 10: 2016-03-06 (Vinay Rao/Tom Manley)
|
|
||||||
* changed 2/3rds of the file to clean up code and add zigbee library improvements
|
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
metadata {
|
metadata {
|
||||||
definition (name: "GE Link Bulb", namespace: "smartthings", author: "SmartThings") {
|
definition (name: "GE Link Bulb", namespace: "smartthings", author: "SmartThings") {
|
||||||
|
|
||||||
capability "Actuator"
|
capability "Actuator"
|
||||||
capability "Configuration"
|
capability "Configuration"
|
||||||
capability "Refresh"
|
capability "Refresh"
|
||||||
capability "Sensor"
|
capability "Sensor"
|
||||||
capability "Switch"
|
capability "Switch"
|
||||||
capability "Switch Level"
|
capability "Switch Level"
|
||||||
capability "Polling"
|
capability "Polling"
|
||||||
|
|
||||||
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,1000", outClusters: "0019", manufacturer: "GE_Appliances", model: "ZLL Light", deviceJoinName: "GE Link Bulb"
|
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,1000", outClusters: "0019", manufacturer: "GE_Appliances", model: "ZLL Light", deviceJoinName: "GE Link Bulb"
|
||||||
}
|
}
|
||||||
|
|
||||||
// UI tile definitions
|
// UI tile definitions
|
||||||
tiles(scale: 2) {
|
tiles {
|
||||||
multiAttributeTile(name:"switch", type: "lighting", width: 6, height: 4, canChangeIcon: true){
|
standardTile("switch", "device.switch", width: 2, height: 2, canChangeIcon: true) {
|
||||||
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
|
state "on", label: '${name}', action: "switch.off", icon: "st.switches.light.on", backgroundColor: "#79b821", nextState:"turningOff"
|
||||||
attributeState "on", label:'${name}', action:"switch.off", icon:"st.switches.light.on", backgroundColor:"#79b821", nextState:"turningOff"
|
state "off", label: '${name}', action: "switch.on", icon: "st.switches.light.off", backgroundColor: "#ffffff", nextState:"turningOn"
|
||||||
attributeState "off", label:'${name}', action:"switch.on", icon:"st.switches.light.off", backgroundColor:"#ffffff", nextState:"turningOn"
|
state "turningOn", label:'${name}', action: "switch.off", icon:"st.switches.light.on", backgroundColor:"#79b821", nextState:"turningOff"
|
||||||
attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.switches.light.on", backgroundColor:"#79b821", nextState:"turningOff"
|
state "turningOff", label:'${name}', action: "switch.on", icon:"st.switches.light.off", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||||
attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.switches.light.off", backgroundColor:"#ffffff", nextState:"turningOn"
|
}
|
||||||
}
|
standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat") {
|
||||||
tileAttribute ("device.level", key: "SLIDER_CONTROL") {
|
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
|
||||||
attributeState "level", action:"switch level.setLevel"
|
}
|
||||||
}
|
controlTile("levelSliderControl", "device.level", "slider", height: 1, width: 3, inactiveLabel: false, range:"(0..100)") {
|
||||||
}
|
state "level", action:"switch level.setLevel"
|
||||||
standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
}
|
||||||
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
|
valueTile("level", "device.level", inactiveLabel: false, decoration: "flat") {
|
||||||
}
|
state "level", label: 'Level ${currentValue}%'
|
||||||
main "switch"
|
}
|
||||||
details(["switch", "refresh"])
|
|
||||||
}
|
main(["switch"])
|
||||||
|
details(["switch", "level", "levelSliderControl", "refresh"])
|
||||||
|
}
|
||||||
|
|
||||||
|
preferences {
|
||||||
|
|
||||||
|
input("dimRate", "enum", title: "Dim Rate", options: ["Instant", "Normal", "Slow", "Very Slow"], defaultValue: "Normal", required: false, displayDuringSetup: true)
|
||||||
|
input("dimOnOff", "enum", title: "Dim transition for On/Off commands?", options: ["Yes", "No"], defaultValue: "No", required: false, displayDuringSetup: true)
|
||||||
|
|
||||||
preferences {
|
|
||||||
input("dimRate", "enum", title: "Dim Rate", options: ["Instant", "Normal", "Slow", "Very Slow"], defaultValue: "Normal", required: false, displayDuringSetup: true)
|
|
||||||
input("dimOnOff", "enum", title: "Dim transition for On/Off commands?", options: ["Yes", "No"], defaultValue: "No", required: false, displayDuringSetup: true)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse incoming device messages to generate events
|
// Parse incoming device messages to generate events
|
||||||
def parse(String description) {
|
def parse(String description) {
|
||||||
def resultMap = zigbee.getEvent(description)
|
log.trace description
|
||||||
if (resultMap) {
|
|
||||||
if ((resultMap.name == "level" && state.trigger == "setLevel") || resultMap.name != "level") { //doing this to account for weird level reporting bug with GE Link Bulbs
|
if (description?.startsWith("on/off:")) {
|
||||||
sendEvent(resultMap)
|
log.debug "The bulb was sent a command to do something just now..."
|
||||||
|
if (description[-1] == "1") {
|
||||||
|
def result = createEvent(name: "switch", value: "on")
|
||||||
|
log.debug "On command was sent maybe from manually turning on? : Parse returned ${result?.descriptionText}"
|
||||||
|
return result
|
||||||
|
} else if (description[-1] == "0") {
|
||||||
|
def result = createEvent(name: "switch", value: "off")
|
||||||
|
log.debug "Off command was sent : Parse returned ${result?.descriptionText}"
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
log.debug "DID NOT PARSE MESSAGE for description : $description"
|
def msg = zigbee.parse(description)
|
||||||
log.debug zigbee.parseDescriptionAsMap(description)
|
|
||||||
|
if (description?.startsWith("catchall:")) {
|
||||||
|
// log.trace msg
|
||||||
|
// log.trace "data: $msg.data"
|
||||||
|
|
||||||
|
def x = description[-4..-1]
|
||||||
|
// log.debug x
|
||||||
|
|
||||||
|
switch (x)
|
||||||
|
{
|
||||||
|
|
||||||
|
case "0000":
|
||||||
|
|
||||||
|
def result = createEvent(name: "switch", value: "off")
|
||||||
|
log.debug "${result?.descriptionText}"
|
||||||
|
return result
|
||||||
|
break
|
||||||
|
|
||||||
|
case "1000":
|
||||||
|
|
||||||
|
def result = createEvent(name: "switch", value: "off")
|
||||||
|
log.debug "${result?.descriptionText}"
|
||||||
|
return result
|
||||||
|
break
|
||||||
|
|
||||||
|
case "0100":
|
||||||
|
|
||||||
|
def result = createEvent(name: "switch", value: "on")
|
||||||
|
log.debug "${result?.descriptionText}"
|
||||||
|
return result
|
||||||
|
break
|
||||||
|
|
||||||
|
case "1001":
|
||||||
|
|
||||||
|
def result = createEvent(name: "switch", value: "on")
|
||||||
|
log.debug "${result?.descriptionText}"
|
||||||
|
return result
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (description?.startsWith("read attr")) {
|
||||||
|
|
||||||
|
// log.trace description[27..28]
|
||||||
|
// log.trace description[-2..-1]
|
||||||
|
|
||||||
|
if (description[27..28] == "0A") {
|
||||||
|
|
||||||
|
// log.debug description[-2..-1]
|
||||||
|
def i = Math.round(convertHexToInt(description[-2..-1]) / 256 * 100 )
|
||||||
|
sendEvent( name: "level", value: i )
|
||||||
|
sendEvent( name: "switch.setLevel", value: i) //added to help subscribers
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
|
||||||
|
if (description[-2..-1] == "00" && state.trigger == "setLevel") {
|
||||||
|
// log.debug description[-2..-1]
|
||||||
|
def i = Math.round(convertHexToInt(description[-2..-1]) / 256 * 100 )
|
||||||
|
sendEvent( name: "level", value: i )
|
||||||
|
sendEvent( name: "switch.setLevel", value: i) //added to help subscribers
|
||||||
|
}
|
||||||
|
|
||||||
|
if (description[-2..-1] == state.lvl) {
|
||||||
|
// log.debug description[-2..-1]
|
||||||
|
def i = Math.round(convertHexToInt(description[-2..-1]) / 256 * 100 )
|
||||||
|
sendEvent( name: "level", value: i )
|
||||||
|
sendEvent( name: "switch.setLevel", value: i) //added to help subscribers
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def poll() {
|
def poll() {
|
||||||
def refreshCmds = [
|
|
||||||
"st wattr 0x${device.deviceNetworkId} 1 8 0x10 0x21 {${state?.dOnOff ?: '0000'}}", "delay 500"
|
[
|
||||||
|
"st rattr 0x${device.deviceNetworkId} 1 6 0", "delay 500",
|
||||||
|
"st rattr 0x${device.deviceNetworkId} 1 8 0", "delay 500",
|
||||||
|
"st wattr 0x${device.deviceNetworkId} 1 8 0x10 0x21 {${state?.dOnOff ?: '0000'}}"
|
||||||
]
|
]
|
||||||
|
|
||||||
return refreshCmds + zigbee.onOffRefresh() + zigbee.levelRefresh()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def updated() {
|
def updated() {
|
||||||
@@ -186,63 +270,109 @@ def updated() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
def on() {
|
def on() {
|
||||||
|
state.lvl = "00"
|
||||||
state.trigger = "on/off"
|
state.trigger = "on/off"
|
||||||
zigbee.on()
|
|
||||||
|
// log.debug "on()"
|
||||||
|
sendEvent(name: "switch", value: "on")
|
||||||
|
"st cmd 0x${device.deviceNetworkId} 1 6 1 {}"
|
||||||
}
|
}
|
||||||
|
|
||||||
def off() {
|
def off() {
|
||||||
|
state.lvl = "00"
|
||||||
state.trigger = "on/off"
|
state.trigger = "on/off"
|
||||||
zigbee.off()
|
|
||||||
|
// log.debug "off()"
|
||||||
|
sendEvent(name: "switch", value: "off")
|
||||||
|
"st cmd 0x${device.deviceNetworkId} 1 6 0 {}"
|
||||||
}
|
}
|
||||||
|
|
||||||
def refresh() {
|
def refresh() {
|
||||||
def refreshCmds = [
|
|
||||||
"st wattr 0x${device.deviceNetworkId} 1 8 0x10 0x21 {${state?.dOnOff ?: '0000'}}", "delay 500"
|
|
||||||
]
|
|
||||||
|
|
||||||
return refreshCmds + zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.onOffConfig()
|
[
|
||||||
|
"st rattr 0x${device.deviceNetworkId} 1 6 0", "delay 500",
|
||||||
|
"st rattr 0x${device.deviceNetworkId} 1 8 0", "delay 500",
|
||||||
|
"st wattr 0x${device.deviceNetworkId} 1 8 0x10 0x21 {${state?.dOnOff ?: '0000'}}"
|
||||||
|
]
|
||||||
|
poll()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def setLevel(value) {
|
def setLevel(value) {
|
||||||
|
|
||||||
|
def cmds = []
|
||||||
|
value = value as Integer
|
||||||
|
if (value == 0) {
|
||||||
|
sendEvent(name: "switch", value: "off")
|
||||||
|
cmds << "st cmd 0x${device.deviceNetworkId} 1 8 0 {0000 ${state.rate}}"
|
||||||
|
}
|
||||||
|
else if (device.latestValue("switch") == "off") {
|
||||||
|
sendEvent(name: "switch", value: "on")
|
||||||
|
}
|
||||||
|
|
||||||
|
sendEvent(name: "level", value: value)
|
||||||
|
value = (value * 255 / 100)
|
||||||
|
def level = hex(value);
|
||||||
|
|
||||||
state.trigger = "setLevel"
|
state.trigger = "setLevel"
|
||||||
def cmd
|
state.lvl = "${level}"
|
||||||
def delayForRefresh = 500
|
|
||||||
if (dimRate && (state?.rate != null)) {
|
if (dimRate && (state?.rate != null)) {
|
||||||
def computedRate = convertRateValue(state.rate)
|
cmds << "st cmd 0x${device.deviceNetworkId} 1 8 4 {${level} ${state.rate}}"
|
||||||
cmd = zigbee.setLevel(value, computedRate)
|
|
||||||
delayForRefresh += computedRate * 100 //converting tenth of second to milliseconds
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
cmd = zigbee.setLevel(value, 20)
|
cmds << "st cmd 0x${device.deviceNetworkId} 1 8 4 {${level} 1500}"
|
||||||
delayForRefresh += 2000
|
|
||||||
}
|
}
|
||||||
cmd + ["delay $delayForRefresh"] + zigbee.levelRefresh()
|
|
||||||
}
|
|
||||||
|
|
||||||
int convertRateValue(rate) {
|
log.debug cmds
|
||||||
int convertedRate = 0
|
cmds
|
||||||
switch (rate)
|
|
||||||
{
|
|
||||||
case "0000":
|
|
||||||
convertedRate = 0
|
|
||||||
break
|
|
||||||
|
|
||||||
case "1500":
|
|
||||||
convertedRate = 20 //0015 hex in int is 2.1
|
|
||||||
break
|
|
||||||
|
|
||||||
case "2500":
|
|
||||||
convertedRate = 35 //0025 hex in int is 3.7
|
|
||||||
break
|
|
||||||
|
|
||||||
case "3500":
|
|
||||||
convertedRate = 50 //0035 hex in int is 5.1
|
|
||||||
break
|
|
||||||
}
|
|
||||||
convertedRate
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def configure() {
|
def configure() {
|
||||||
log.debug "Configuring Reporting and Bindings."
|
|
||||||
return zigbee.onOffConfig() + zigbee.onOffRefresh() + zigbee.levelRefresh()
|
log.debug "Configuring Reporting and Bindings."
|
||||||
|
def configCmds = [
|
||||||
|
|
||||||
|
//Switch Reporting
|
||||||
|
"zcl global send-me-a-report 6 0 0x10 0 3600 {01}", "delay 500",
|
||||||
|
"send 0x${device.deviceNetworkId} 1 1", "delay 1000",
|
||||||
|
|
||||||
|
//Level Control Reporting
|
||||||
|
"zcl global send-me-a-report 8 0 0x20 5 3600 {0010}", "delay 200",
|
||||||
|
"send 0x${device.deviceNetworkId} 1 1", "delay 1500",
|
||||||
|
|
||||||
|
"zdo bind 0x${device.deviceNetworkId} 1 1 6 {${device.zigbeeId}} {}", "delay 1000",
|
||||||
|
"zdo bind 0x${device.deviceNetworkId} 1 1 8 {${device.zigbeeId}} {}", "delay 500",
|
||||||
|
]
|
||||||
|
return configCmds + refresh() // send refresh cmds as part of config
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private hex(value, width=2) {
|
||||||
|
def s = new BigInteger(Math.round(value).toString()).toString(16)
|
||||||
|
while (s.size() < width) {
|
||||||
|
s = "0" + s
|
||||||
|
}
|
||||||
|
s
|
||||||
|
}
|
||||||
|
|
||||||
|
private Integer convertHexToInt(hex) {
|
||||||
|
Integer.parseInt(hex,16)
|
||||||
|
}
|
||||||
|
|
||||||
|
private String swapEndianHex(String hex) {
|
||||||
|
reverseArray(hex.decodeHex()).encodeHex()
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] reverseArray(byte[] array) {
|
||||||
|
int i = 0;
|
||||||
|
int j = array.length - 1;
|
||||||
|
byte tmp;
|
||||||
|
while (j > i) {
|
||||||
|
tmp = array[j];
|
||||||
|
array[j] = array[i];
|
||||||
|
array[i] = tmp;
|
||||||
|
j--;
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
return array
|
||||||
|
}
|
||||||
@@ -80,12 +80,19 @@ def parse(String description) {
|
|||||||
if (cmd) {
|
if (cmd) {
|
||||||
result = zwaveEvent(cmd)
|
result = zwaveEvent(cmd)
|
||||||
}
|
}
|
||||||
log.debug "Parsed ${description.inspect()} to ${result.inspect()}"
|
// log.debug "Parsed ${description.inspect()} to ${result.inspect()}"
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.multiinstancev1.MultiInstanceCmdEncap cmd) {
|
def zwaveEvent(physicalgraph.zwave.commands.multiinstancev1.MultiInstanceCmdEncap cmd) {
|
||||||
def encapsulated = cmd.encapsulatedCommand([0x31: 1, 0x84: 2, 0x60: 1, 0x85: 1, 0x70: 1])
|
def encapsulated = null
|
||||||
|
if (cmd.respondsTo("encapsulatedCommand")) {
|
||||||
|
encapsulated = cmd.encapsulatedCommand()
|
||||||
|
} else {
|
||||||
|
def hex1 = { n -> String.format("%02X", n) }
|
||||||
|
def sorry = "command: ${hex1(cmd.commandClass)}${hex1(cmd.command)}, payload: " + cmd.parameter.collect{ hex1(it) }.join(" ")
|
||||||
|
encapsulated = zwave.parse(sorry, [0x31: 1, 0x84: 2, 0x60: 1, 0x85: 1, 0x70: 1])
|
||||||
|
}
|
||||||
return encapsulated ? zwaveEvent(encapsulated) : null
|
return encapsulated ? zwaveEvent(encapsulated) : null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,13 +17,16 @@ metadata {
|
|||||||
|
|
||||||
tiles(scale: 2) {
|
tiles(scale: 2) {
|
||||||
multiAttributeTile(name:"rich-control"){
|
multiAttributeTile(name:"rich-control"){
|
||||||
tileAttribute ("", key: "PRIMARY_CONTROL") {
|
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
|
||||||
attributeState "default", label: "Hue Bridge", action: "", icon: "st.Lighting.light99-hue", backgroundColor: "#F3C200"
|
attributeState "default", label: "Hue Bridge", action: "", icon: "st.Lighting.light99-hue", backgroundColor: "#F3C200"
|
||||||
}
|
}
|
||||||
tileAttribute ("serialNumber", key: "SECONDARY_CONTROL") {
|
tileAttribute ("serialNumber", key: "SECONDARY_CONTROL") {
|
||||||
attributeState "default", label:'SN: ${currentValue}'
|
attributeState "default", label:'SN: ${currentValue}'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
standardTile("icon", "icon", width: 1, height: 1, canChangeIcon: false, inactiveLabel: true, canChangeBackground: false) {
|
||||||
|
state "default", label: "Hue Bridge", action: "", icon: "st.Lighting.light99-hue", backgroundColor: "#FFFFFF"
|
||||||
|
}
|
||||||
valueTile("serialNumber", "device.serialNumber", decoration: "flat", height: 1, width: 2, inactiveLabel: false) {
|
valueTile("serialNumber", "device.serialNumber", decoration: "flat", height: 1, width: 2, inactiveLabel: false) {
|
||||||
state "default", label:'SN: ${currentValue}'
|
state "default", label:'SN: ${currentValue}'
|
||||||
}
|
}
|
||||||
@@ -31,7 +34,7 @@ metadata {
|
|||||||
state "default", label:'${currentValue}', height: 1, width: 2, inactiveLabel: false
|
state "default", label:'${currentValue}', height: 1, width: 2, inactiveLabel: false
|
||||||
}
|
}
|
||||||
|
|
||||||
main (["rich-control"])
|
main (["icon"])
|
||||||
details(["rich-control", "networkAddress"])
|
details(["rich-control", "networkAddress"])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -72,7 +75,6 @@ def parse(description) {
|
|||||||
}
|
}
|
||||||
else if (contentType?.contains("xml")) {
|
else if (contentType?.contains("xml")) {
|
||||||
log.debug "HUE BRIDGE ALREADY PRESENT"
|
log.debug "HUE BRIDGE ALREADY PRESENT"
|
||||||
parent.hubVerification(device.hub.id, msg.body)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Hue Bulb
|
* Hue Bulb
|
||||||
*
|
*
|
||||||
* Author: SmartThings
|
* Author: SmartThings
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// for the UI
|
// for the UI
|
||||||
metadata {
|
metadata {
|
||||||
// Automatically generated. Make future change here.
|
// Automatically generated. Make future change here.
|
||||||
@@ -11,14 +11,13 @@ metadata {
|
|||||||
capability "Switch Level"
|
capability "Switch Level"
|
||||||
capability "Actuator"
|
capability "Actuator"
|
||||||
capability "Color Control"
|
capability "Color Control"
|
||||||
capability "Color Temperature"
|
|
||||||
capability "Switch"
|
capability "Switch"
|
||||||
capability "Refresh"
|
capability "Refresh"
|
||||||
capability "Sensor"
|
capability "Sensor"
|
||||||
|
|
||||||
command "setAdjustedColor"
|
command "setAdjustedColor"
|
||||||
command "reset"
|
command "reset"
|
||||||
command "refresh"
|
command "refresh"
|
||||||
}
|
}
|
||||||
|
|
||||||
simulator {
|
simulator {
|
||||||
@@ -26,43 +25,31 @@ metadata {
|
|||||||
}
|
}
|
||||||
|
|
||||||
tiles (scale: 2){
|
tiles (scale: 2){
|
||||||
multiAttributeTile(name:"rich-control", type: "lighting", width: 6, height: 4, canChangeIcon: true){
|
multiAttributeTile(name:"switch", type: "lighting", width: 6, height: 4, canChangeIcon: true){
|
||||||
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
|
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
|
||||||
attributeState "on", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#00A0DC", nextState:"turningOff"
|
attributeState "on", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff"
|
||||||
attributeState "off", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#C6C7CC", nextState:"turningOn"
|
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:"#00A0DC", nextState:"turningOff"
|
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:"#C6C7CC", nextState:"turningOn"
|
attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||||
}
|
}
|
||||||
tileAttribute ("device.level", key: "SLIDER_CONTROL") {
|
tileAttribute ("device.level", key: "SLIDER_CONTROL") {
|
||||||
attributeState "level", action:"switch level.setLevel", range:"(0..100)"
|
attributeState "level", action:"switch level.setLevel"
|
||||||
}
|
|
||||||
tileAttribute ("device.level", key: "SECONDARY_CONTROL") {
|
|
||||||
attributeState "level", label: 'Level ${currentValue}%'
|
|
||||||
}
|
}
|
||||||
tileAttribute ("device.color", key: "COLOR_CONTROL") {
|
tileAttribute ("device.color", key: "COLOR_CONTROL") {
|
||||||
attributeState "color", action:"setAdjustedColor"
|
attributeState "color", action:"setAdjustedColor"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
controlTile("colorTempSliderControl", "device.colorTemperature", "slider", width: 4, height: 2, inactiveLabel: false, range:"(2000..6500)") {
|
standardTile("reset", "device.reset", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
||||||
state "colorTemperature", action:"color temperature.setColorTemperature"
|
|
||||||
}
|
|
||||||
|
|
||||||
valueTile("colorTemp", "device.colorTemperature", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
|
||||||
state "colorTemperature", label: '${currentValue} K'
|
|
||||||
}
|
|
||||||
|
|
||||||
standardTile("reset", "device.reset", height: 2, width: 2, inactiveLabel: false, decoration: "flat") {
|
|
||||||
state "default", label:"Reset Color", action:"reset", icon:"st.lights.philips.hue-single"
|
state "default", label:"Reset Color", action:"reset", icon:"st.lights.philips.hue-single"
|
||||||
}
|
}
|
||||||
|
standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
||||||
standardTile("refresh", "device.refresh", height: 2, width: 2, inactiveLabel: false, decoration: "flat") {
|
|
||||||
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
|
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
|
||||||
}
|
}
|
||||||
|
|
||||||
main(["rich-control"])
|
|
||||||
details(["rich-control", "colorTempSliderControl", "colorTemp", "reset", "refresh"])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
main(["switch"])
|
||||||
|
details(["switch", "levelSliderControl", "rgbSelector", "refresh", "reset"])
|
||||||
}
|
}
|
||||||
|
|
||||||
// parse events into attributes
|
// parse events into attributes
|
||||||
@@ -81,17 +68,17 @@ def parse(description) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// handle commands
|
// handle commands
|
||||||
void on() {
|
def on() {
|
||||||
log.trace parent.on(this)
|
log.trace parent.on(this)
|
||||||
sendEvent(name: "switch", value: "on")
|
sendEvent(name: "switch", value: "on")
|
||||||
}
|
}
|
||||||
|
|
||||||
void off() {
|
def off() {
|
||||||
log.trace parent.off(this)
|
log.trace parent.off(this)
|
||||||
sendEvent(name: "switch", value: "off")
|
sendEvent(name: "switch", value: "off")
|
||||||
}
|
}
|
||||||
|
|
||||||
void nextLevel() {
|
def nextLevel() {
|
||||||
def level = device.latestValue("level") as Integer ?: 0
|
def level = device.latestValue("level") as Integer ?: 0
|
||||||
if (level <= 100) {
|
if (level <= 100) {
|
||||||
level = Math.min(25 * (Math.round(level / 25) + 1), 100) as Integer
|
level = Math.min(25 * (Math.round(level / 25) + 1), 100) as Integer
|
||||||
@@ -102,105 +89,55 @@ void nextLevel() {
|
|||||||
setLevel(level)
|
setLevel(level)
|
||||||
}
|
}
|
||||||
|
|
||||||
void setLevel(percent) {
|
def setLevel(percent) {
|
||||||
log.debug "Executing 'setLevel'"
|
log.debug "Executing 'setLevel'"
|
||||||
if (verifyPercent(percent)) {
|
parent.setLevel(this, percent)
|
||||||
parent.setLevel(this, percent)
|
sendEvent(name: "level", value: percent)
|
||||||
sendEvent(name: "level", value: percent, descriptionText: "Level has changed to ${percent}%")
|
|
||||||
sendEvent(name: "switch", value: "on")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void setSaturation(percent) {
|
def setSaturation(percent) {
|
||||||
log.debug "Executing 'setSaturation'"
|
log.debug "Executing 'setSaturation'"
|
||||||
if (verifyPercent(percent)) {
|
parent.setSaturation(this, percent)
|
||||||
parent.setSaturation(this, percent)
|
sendEvent(name: "saturation", value: percent)
|
||||||
sendEvent(name: "saturation", value: percent, displayed: false)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void setHue(percent) {
|
def setHue(percent) {
|
||||||
log.debug "Executing 'setHue'"
|
log.debug "Executing 'setHue'"
|
||||||
if (verifyPercent(percent)) {
|
parent.setHue(this, percent)
|
||||||
parent.setHue(this, percent)
|
sendEvent(name: "hue", value: percent)
|
||||||
sendEvent(name: "hue", value: percent, displayed: false)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void setColor(value) {
|
def setColor(value) {
|
||||||
log.debug "setColor: ${value}, $this"
|
log.debug "setColor: ${value}, $this"
|
||||||
def events = []
|
parent.setColor(this, value)
|
||||||
def validValues = [:]
|
if (value.hue) { sendEvent(name: "hue", value: value.hue)}
|
||||||
|
if (value.saturation) { sendEvent(name: "saturation", value: value.saturation)}
|
||||||
if (verifyPercent(value.hue)) {
|
if (value.hex) { sendEvent(name: "color", value: value.hex)}
|
||||||
events << createEvent(name: "hue", value: value.hue, displayed: false)
|
if (value.level) { sendEvent(name: "level", value: value.level)}
|
||||||
validValues.hue = value.hue
|
if (value.switch) { sendEvent(name: "switch", value: value.switch)}
|
||||||
}
|
|
||||||
if (verifyPercent(value.saturation)) {
|
|
||||||
events << createEvent(name: "saturation", value: value.saturation, displayed: false)
|
|
||||||
validValues.saturation = value.saturation
|
|
||||||
}
|
|
||||||
if (value.hex != null) {
|
|
||||||
if (value.hex ==~ /^\#([A-Fa-f0-9]){6}$/) {
|
|
||||||
events << createEvent(name: "color", value: value.hex)
|
|
||||||
validValues.hex = value.hex
|
|
||||||
} else {
|
|
||||||
log.warn "$value.hex is not a valid color"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (verifyPercent(value.level)) {
|
|
||||||
events << createEvent(name: "level", value: value.level, descriptionText: "Level has changed to ${value.level}%")
|
|
||||||
validValues.level = value.level
|
|
||||||
}
|
|
||||||
if (value.switch == "off" || (value.level != null && value.level <= 0)) {
|
|
||||||
events << createEvent(name: "switch", value: "off")
|
|
||||||
validValues.switch = "off"
|
|
||||||
} else {
|
|
||||||
events << createEvent(name: "switch", value: "on")
|
|
||||||
validValues.switch = "on"
|
|
||||||
}
|
|
||||||
if (!events.isEmpty()) {
|
|
||||||
parent.setColor(this, validValues)
|
|
||||||
}
|
|
||||||
events.each {
|
|
||||||
sendEvent(it)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void reset() {
|
def reset() {
|
||||||
log.debug "Executing 'reset'"
|
log.debug "Executing 'reset'"
|
||||||
def value = [level:100, saturation:56, hue:23]
|
def value = [level:100, hex:"#90C638", saturation:56, hue:23]
|
||||||
setAdjustedColor(value)
|
setAdjustedColor(value)
|
||||||
parent.poll()
|
parent.poll()
|
||||||
}
|
}
|
||||||
|
|
||||||
void setAdjustedColor(value) {
|
def setAdjustedColor(value) {
|
||||||
if (value) {
|
if (value) {
|
||||||
log.trace "setAdjustedColor: ${value}"
|
log.trace "setAdjustedColor: ${value}"
|
||||||
def adjusted = value + [:]
|
def adjusted = value + [:]
|
||||||
adjusted.hue = adjustOutgoingHue(value.hue)
|
adjusted.hue = adjustOutgoingHue(value.hue)
|
||||||
// Needed because color picker always sends 100
|
// Needed because color picker always sends 100
|
||||||
adjusted.level = null
|
adjusted.level = null
|
||||||
setColor(adjusted)
|
setColor(adjusted)
|
||||||
} else {
|
|
||||||
log.warn "Invalid color input"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void setColorTemperature(value) {
|
def refresh() {
|
||||||
if (value) {
|
log.debug "Executing 'refresh'"
|
||||||
log.trace "setColorTemperature: ${value}k"
|
parent.manualRefresh()
|
||||||
parent.setColorTemperature(this, value)
|
|
||||||
sendEvent(name: "colorTemperature", value: value)
|
|
||||||
sendEvent(name: "switch", value: "on")
|
|
||||||
} else {
|
|
||||||
log.warn "Invalid color temperature"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void refresh() {
|
|
||||||
log.debug "Executing 'refresh'"
|
|
||||||
parent.manualRefresh()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def adjustOutgoingHue(percent) {
|
def adjustOutgoingHue(percent) {
|
||||||
@@ -219,14 +156,3 @@ def adjustOutgoingHue(percent) {
|
|||||||
log.info "percent: $percent, adjusted: $adjusted"
|
log.info "percent: $percent, adjusted: $adjusted"
|
||||||
adjusted
|
adjusted
|
||||||
}
|
}
|
||||||
|
|
||||||
def verifyPercent(percent) {
|
|
||||||
if (percent == null)
|
|
||||||
return false
|
|
||||||
else if (percent >= 0 && percent <= 100) {
|
|
||||||
return true
|
|
||||||
} else {
|
|
||||||
log.warn "$percent is not 0-100"
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -12,14 +12,14 @@ metadata {
|
|||||||
capability "Switch"
|
capability "Switch"
|
||||||
capability "Refresh"
|
capability "Refresh"
|
||||||
capability "Sensor"
|
capability "Sensor"
|
||||||
|
|
||||||
command "refresh"
|
command "refresh"
|
||||||
}
|
}
|
||||||
|
|
||||||
simulator {
|
simulator {
|
||||||
// TODO: define status and reply messages here
|
// TODO: define status and reply messages here
|
||||||
}
|
}
|
||||||
|
|
||||||
tiles(scale: 2) {
|
tiles(scale: 2) {
|
||||||
multiAttributeTile(name:"rich-control", type: "lighting", canChangeIcon: true){
|
multiAttributeTile(name:"rich-control", type: "lighting", canChangeIcon: true){
|
||||||
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
|
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
|
||||||
@@ -35,18 +35,25 @@ metadata {
|
|||||||
attributeState "level", label: 'Level ${currentValue}%'
|
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)") {
|
controlTile("levelSliderControl", "device.level", "slider", height: 1, width: 2, inactiveLabel: false, range:"(0..100)") {
|
||||||
state "level", action:"switch level.setLevel"
|
state "level", action:"switch level.setLevel"
|
||||||
}
|
}
|
||||||
|
|
||||||
standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
standardTile("refresh", "device.switch", inactiveLabel: false, height: 2, width: 2, decoration: "flat") {
|
||||||
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
|
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
|
||||||
}
|
}
|
||||||
|
|
||||||
main(["rich-control"])
|
main(["switch"])
|
||||||
details(["rich-control", "refresh"])
|
details(["rich-control", "refresh"])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// parse events into attributes
|
// parse events into attributes
|
||||||
@@ -67,27 +74,23 @@ def parse(description) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// handle commands
|
// handle commands
|
||||||
void on() {
|
def on() {
|
||||||
parent.on(this)
|
parent.on(this)
|
||||||
sendEvent(name: "switch", value: "on")
|
sendEvent(name: "switch", value: "on")
|
||||||
}
|
}
|
||||||
|
|
||||||
void off() {
|
def off() {
|
||||||
parent.off(this)
|
parent.off(this)
|
||||||
sendEvent(name: "switch", value: "off")
|
sendEvent(name: "switch", value: "off")
|
||||||
}
|
}
|
||||||
|
|
||||||
void setLevel(percent) {
|
def setLevel(percent) {
|
||||||
log.debug "Executing 'setLevel'"
|
log.debug "Executing 'setLevel'"
|
||||||
if (percent != null && percent >= 0 && percent <= 100) {
|
parent.setLevel(this, percent)
|
||||||
parent.setLevel(this, percent)
|
sendEvent(name: "level", value: percent)
|
||||||
sendEvent(name: "level", value: percent)
|
|
||||||
} else {
|
|
||||||
log.warn "$percent is not 0-100"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void refresh() {
|
def refresh() {
|
||||||
log.debug "Executing 'refresh'"
|
log.debug "Executing 'refresh'"
|
||||||
parent.manualRefresh()
|
parent.manualRefresh()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -84,7 +84,6 @@ def setHue(percentage) {
|
|||||||
log.error("Bad setHue result: [${resp.status}] ${resp.data}")
|
log.error("Bad setHue result: [${resp.status}] ${resp.data}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return []
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def setSaturation(percentage) {
|
def setSaturation(percentage) {
|
||||||
@@ -98,7 +97,6 @@ def setSaturation(percentage) {
|
|||||||
log.error("Bad setSaturation result: [${resp.status}] ${resp.data}")
|
log.error("Bad setSaturation result: [${resp.status}] ${resp.data}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return []
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def setColor(Map color) {
|
def setColor(Map color) {
|
||||||
@@ -124,15 +122,13 @@ def setColor(Map color) {
|
|||||||
parent.logErrors(logObject:log) {
|
parent.logErrors(logObject:log) {
|
||||||
def resp = parent.apiPUT("/lights/${selector()}/state", [color: attrs.join(" "), power: "on"])
|
def resp = parent.apiPUT("/lights/${selector()}/state", [color: attrs.join(" "), power: "on"])
|
||||||
if (resp.status < 300) {
|
if (resp.status < 300) {
|
||||||
if (color.hex)
|
sendEvent(name: "color", value: color.hex)
|
||||||
sendEvent(name: "color", value: color.hex)
|
|
||||||
sendEvent(name: "switch", value: "on")
|
sendEvent(name: "switch", value: "on")
|
||||||
events.each { sendEvent(it) }
|
events.each { sendEvent(it) }
|
||||||
} else {
|
} else {
|
||||||
log.error("Bad setColor result: [${resp.status}] ${resp.data}")
|
log.error("Bad setColor result: [${resp.status}] ${resp.data}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return []
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def setLevel(percentage) {
|
def setLevel(percentage) {
|
||||||
@@ -154,7 +150,6 @@ def setLevel(percentage) {
|
|||||||
log.error("Bad setLevel result: [${resp.status}] ${resp.data}")
|
log.error("Bad setLevel result: [${resp.status}] ${resp.data}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return []
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def setColorTemperature(kelvin) {
|
def setColorTemperature(kelvin) {
|
||||||
@@ -170,7 +165,6 @@ def setColorTemperature(kelvin) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
return []
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def on() {
|
def on() {
|
||||||
@@ -180,7 +174,6 @@ def on() {
|
|||||||
sendEvent(name: "switch", value: "on")
|
sendEvent(name: "switch", value: "on")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return []
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def off() {
|
def off() {
|
||||||
@@ -190,7 +183,6 @@ def off() {
|
|||||||
sendEvent(name: "switch", value: "off")
|
sendEvent(name: "switch", value: "off")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return []
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def poll() {
|
def poll() {
|
||||||
|
|||||||
@@ -84,7 +84,6 @@ def setLevel(percentage) {
|
|||||||
log.error("Bad setLevel result: [${resp.status}] ${resp.data}")
|
log.error("Bad setLevel result: [${resp.status}] ${resp.data}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return []
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def setColorTemperature(kelvin) {
|
def setColorTemperature(kelvin) {
|
||||||
@@ -100,7 +99,6 @@ def setColorTemperature(kelvin) {
|
|||||||
log.error("Bad setColorTemperature result: [${resp.status}] ${resp.data}")
|
log.error("Bad setColorTemperature result: [${resp.status}] ${resp.data}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return []
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def on() {
|
def on() {
|
||||||
@@ -110,7 +108,6 @@ def on() {
|
|||||||
sendEvent(name: "switch", value: "on")
|
sendEvent(name: "switch", value: "on")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return []
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def off() {
|
def off() {
|
||||||
@@ -120,7 +117,6 @@ def off() {
|
|||||||
sendEvent(name: "switch", value: "off")
|
sendEvent(name: "switch", value: "off")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return []
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def poll() {
|
def poll() {
|
||||||
|
|||||||
@@ -1,32 +0,0 @@
|
|||||||
#==============================================================================
|
|
||||||
# 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.
|
|
||||||
# 2. 20160224 TW Updated with formal 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,28 +1,16 @@
|
|||||||
/*
|
/**
|
||||||
===============================================================================
|
* Copyright 2015 SmartThings
|
||||||
* Copyright 2016 SmartThings
|
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||||
* use this file except in compliance with the License. You may obtain a copy
|
* in compliance with the License. You may obtain a copy of the License at:
|
||||||
* of the License at:
|
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
* for the specific language governing permissions and limitations under the License.
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
===============================================================================
|
|
||||||
* Purpose: Mobile Presence DTH File
|
|
||||||
*
|
*
|
||||||
* Filename: mobile-presence.src/mobile-presence.groovy
|
|
||||||
*
|
|
||||||
* Change History:
|
|
||||||
* 1. 20160205 TW - Update/Edit to support i18n translations
|
|
||||||
===============================================================================
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
metadata {
|
metadata {
|
||||||
definition (name: "Mobile Presence", namespace: "smartthings", author: "SmartThings") {
|
definition (name: "Mobile Presence", namespace: "smartthings", author: "SmartThings") {
|
||||||
capability "Presence Sensor"
|
capability "Presence Sensor"
|
||||||
@@ -53,7 +41,6 @@ def parse(String description) {
|
|||||||
def isStateChange = isStateChange(device, name, value)
|
def isStateChange = isStateChange(device, name, value)
|
||||||
|
|
||||||
def results = [
|
def results = [
|
||||||
translatable: true,
|
|
||||||
name: name,
|
name: name,
|
||||||
value: value,
|
value: value,
|
||||||
unit: null,
|
unit: null,
|
||||||
@@ -85,8 +72,8 @@ private String parseValue(String description) {
|
|||||||
|
|
||||||
private parseDescriptionText(String linkText, String value, String description) {
|
private parseDescriptionText(String linkText, String value, String description) {
|
||||||
switch(value) {
|
switch(value) {
|
||||||
case "present": return "{{ linkText }} has arrived"
|
case "present": return "$linkText has arrived"
|
||||||
case "not present": return "{{ linkText }} has left"
|
case "not present": return "$linkText has left"
|
||||||
default: return value
|
default: return value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -97,4 +84,4 @@ private getState(String value) {
|
|||||||
case "not present": return "left"
|
case "not present": return "left"
|
||||||
default: return value
|
default: return value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,235 @@
|
|||||||
|
/**
|
||||||
|
* 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)
|
||||||
|
}
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
#==============================================================================
|
|
||||||
# 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: SmartPower Outlet i18n Translation File
|
|
||||||
#
|
|
||||||
# Filename: SmartPower-Outlet.src/i18n/messages.properties
|
|
||||||
#
|
|
||||||
# Change History:
|
|
||||||
# 1. 20160116 TW Initial release with informal Korean translation.
|
|
||||||
#==============================================================================
|
|
||||||
# Korean (ko)
|
|
||||||
# Device Preferences
|
|
||||||
'''Give your device a name'''.ko=기기 이름 바꾸기
|
|
||||||
'''Outlet'''.ko=플러그
|
|
||||||
# Events descriptionText
|
|
||||||
'''{{ device.displayName }} is On'''.ko={{ device.displayName }}켜졌습니다.
|
|
||||||
'''{{ device.displayName }} is Off'''.ko={{ device.displayName }}꺼졌습니다.
|
|
||||||
'''{{ device.displayName }} power is {{ value }} Watts'''.ko={{ device.displayName }} 전원은 {{ value }}와트입니다
|
|
||||||
#==============================================================================
|
|
||||||
@@ -1,26 +1,19 @@
|
|||||||
/*
|
/**
|
||||||
===============================================================================
|
* Copyright 2015 SmartThings
|
||||||
* Copyright 2016 SmartThings
|
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||||
* use this file except in compliance with the License. You may obtain a copy
|
* in compliance with the License. You may obtain a copy of the License at:
|
||||||
* of the License at:
|
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
* for the specific language governing permissions and limitations under the License.
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
===============================================================================
|
|
||||||
* Purpose: SmartPower Outlet DTH File
|
|
||||||
*
|
*
|
||||||
* Filename: SmartPower-Outlet.src/SmartPower-Outlet.groovy
|
* SmartPower Outlet (CentraLite)
|
||||||
*
|
*
|
||||||
* Change History:
|
* Author: SmartThings
|
||||||
* 1. 20160117 TW - Update/Edit to support i18n translations
|
* Date: 2015-08-23
|
||||||
===============================================================================
|
|
||||||
*/
|
*/
|
||||||
metadata {
|
metadata {
|
||||||
// Automatically generated. Make future change here.
|
// Automatically generated. Make future change here.
|
||||||
@@ -98,22 +91,22 @@ def parse(String description) {
|
|||||||
finalResult = getPowerDescription(zigbee.parseDescriptionAsMap(description))
|
finalResult = getPowerDescription(zigbee.parseDescriptionAsMap(description))
|
||||||
|
|
||||||
if (finalResult) {
|
if (finalResult) {
|
||||||
log.info "final result = $finalResult"
|
log.info finalResult
|
||||||
if (finalResult.type == "update") {
|
if (finalResult.type == "update") {
|
||||||
log.info "$device updates: ${finalResult.value}"
|
log.info "$device updates: ${finalResult.value}"
|
||||||
}
|
}
|
||||||
else if (finalResult.type == "power") {
|
else if (finalResult.type == "power") {
|
||||||
def powerValue = (finalResult.value as Integer)/10
|
def powerValue = (finalResult.value as Integer)/10
|
||||||
sendEvent(name: "power", value: powerValue, descriptionText: '{{ device.displayName }} power is {{ value }} Watts', translatable: true )
|
sendEvent(name: "power", value: powerValue)
|
||||||
/*
|
/*
|
||||||
Dividing by 10 as the Divisor is 10000 and unit is kW for the device. AttrId: 0302 and 0300. Simplifying to 10
|
Dividing by 10 as the Divisor is 10000 and unit is kW for the device. AttrId: 0302 and 0300. Simplifying to 10
|
||||||
|
|
||||||
power level is an integer. The exact power level with correct units needs to be handled in the device type
|
power level is an integer. The exact power level with correct units needs to be handled in the device type
|
||||||
to account for the different Divisor value (AttrId: 0302) and POWER Unit (AttrId: 0300). CLUSTER for simple metering is 0702
|
to account for the different Divisor value (AttrId: 0302) and POWER Unit (AttrId: 0300). CLUSTER for simple metering is 0702
|
||||||
*/
|
*/
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
def descriptionText = finalResult.value == "on" ? '{{ device.displayName }} is On' : '{{ device.displayName }} is Off'
|
sendEvent(name: finalResult.type, value: finalResult.value)
|
||||||
sendEvent(name: finalResult.type, value: finalResult.value, descriptionText: descriptionText, translatable: true)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|||||||
@@ -1,41 +0,0 @@
|
|||||||
#==============================================================================
|
|
||||||
# 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: SmartSense Moisture Sensor i18n Translation File
|
|
||||||
#
|
|
||||||
# Filename: SmartSense-Moisture-Sensor.src/i18n/messages.properties
|
|
||||||
#
|
|
||||||
# Change History:
|
|
||||||
# 1. 20160116 TW Initial release with formal Korean translation.
|
|
||||||
#==============================================================================
|
|
||||||
# Korean (ko)
|
|
||||||
# Device Preferences
|
|
||||||
'''dry'''.ko=건조
|
|
||||||
'''wet'''.ko=누수
|
|
||||||
'''battery'''.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=기준 온도를 원하는대로 몇 도 올리거나 내려서 설정할 수 있습니다.
|
|
||||||
'''Degrees'''.ko=온도
|
|
||||||
'''Adjust temperature by this many degrees'''.ko=몇 도씩 온도를 조절하십시오
|
|
||||||
'''Give your device a name'''.ko=기기 이름 바꾸기
|
|
||||||
'''Water Leak Sensor'''.ko=누수센서
|
|
||||||
# Events descriptionText
|
|
||||||
'''{{ device.displayName }} is dry'''.ko={{ device.displayName }}가 건조
|
|
||||||
'''{{ device.displayName }} is wet'''.ko={{ device.displayName }}누수
|
|
||||||
'''{{ device.displayName }} was {{ value }}°C'''.ko={{ device.displayName }}이(가) {{ value }}°C였습니다
|
|
||||||
'''{{ device.displayName }} was {{ value }}°F'''.ko={{ device.displayName }}이(가) {{ value }}°F였습니다
|
|
||||||
'''{{ device.displayName }} battery has too much power: (> 3.5) volts.'''.ko={{ device.displayName }} 배터리 전력이 너무 높습니다(3.5볼트 초과).
|
|
||||||
'''{{ device.displayName }} battery was {{ value }}%'''.ko={{ device.displayName }}남아있는 배터리는 {{ value }}%입니다.
|
|
||||||
#==============================================================================
|
|
||||||
@@ -1,29 +1,18 @@
|
|||||||
/*
|
/**
|
||||||
===============================================================================
|
* SmartSense Moisture Sensor
|
||||||
* Copyright 2016 SmartThings
|
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
* Copyright 2014 SmartThings
|
||||||
* use this file except in compliance with the License. You may obtain a copy
|
*
|
||||||
* of the License at:
|
* 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
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
* for the specific language governing permissions and limitations under the License.
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
===============================================================================
|
|
||||||
* Purpose: SmartSense Moisture Sensor DTH File
|
|
||||||
*
|
*
|
||||||
* Filename: SmartSense-Moisture-Sensor.src/SmartSense-Moisture-Sensor.groovy
|
|
||||||
*
|
|
||||||
* Change History:
|
|
||||||
* 1. 20160116 TW - Update/Edit to support i18n translations
|
|
||||||
* 2. 20160125 TW = Incorporated new battery mapping from TM
|
|
||||||
===============================================================================
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
metadata {
|
metadata {
|
||||||
definition (name: "SmartSense Moisture Sensor",namespace: "smartthings", author: "SmartThings") {
|
definition (name: "SmartSense Moisture Sensor",namespace: "smartthings", author: "SmartThings") {
|
||||||
capability "Configuration"
|
capability "Configuration"
|
||||||
@@ -54,7 +43,7 @@ metadata {
|
|||||||
])
|
])
|
||||||
}
|
}
|
||||||
section {
|
section {
|
||||||
input title: "Temperature Offset", description: "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'.", displayDuringSetup: false, type: "paragraph", element: "paragraph"
|
input title: "Temperature Offset", description: "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\".", displayDuringSetup: false, type: "paragraph", element: "paragraph"
|
||||||
input "tempOffset", "number", title: "Degrees", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false
|
input "tempOffset", "number", title: "Degrees", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -127,16 +116,16 @@ private Map parseCatchAllMessage(String description) {
|
|||||||
resultMap = getBatteryResult(cluster.data.last())
|
resultMap = getBatteryResult(cluster.data.last())
|
||||||
break
|
break
|
||||||
|
|
||||||
case 0x0402:
|
case 0x0402:
|
||||||
// temp is last 2 data values. reverse to swap endian
|
// temp is last 2 data values. reverse to swap endian
|
||||||
String temp = cluster.data[-2..-1].reverse().collect { cluster.hex1(it) }.join()
|
String temp = cluster.data[-2..-1].reverse().collect { cluster.hex1(it) }.join()
|
||||||
def value = getTemperature(temp)
|
def value = getTemperature(temp)
|
||||||
resultMap = getTemperatureResult(value)
|
resultMap = getTemperatureResult(value)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return resultMap
|
return resultMap
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean shouldProcessMessage(cluster) {
|
private boolean shouldProcessMessage(cluster) {
|
||||||
@@ -231,8 +220,7 @@ private Map getBatteryResult(rawValue) {
|
|||||||
|
|
||||||
def result = [
|
def result = [
|
||||||
name: 'battery',
|
name: 'battery',
|
||||||
value: '--',
|
value: '--'
|
||||||
translatable: true
|
|
||||||
]
|
]
|
||||||
|
|
||||||
def volts = rawValue / 10
|
def volts = rawValue / 10
|
||||||
@@ -240,7 +228,7 @@ private Map getBatteryResult(rawValue) {
|
|||||||
if (rawValue == 0 || rawValue == 255) {}
|
if (rawValue == 0 || rawValue == 255) {}
|
||||||
else {
|
else {
|
||||||
if (volts > 3.5) {
|
if (volts > 3.5) {
|
||||||
result.descriptionText = "{{ device.displayName }} battery has too much power: (> 3.5) volts."
|
result.descriptionText = "${linkText} battery has too much power (${volts} volts)."
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if (device.getDataValue("manufacturer") == "SmartThings") {
|
if (device.getDataValue("manufacturer") == "SmartThings") {
|
||||||
@@ -257,7 +245,7 @@ private Map getBatteryResult(rawValue) {
|
|||||||
def pct = batteryMap[volts]
|
def pct = batteryMap[volts]
|
||||||
if (pct != null) {
|
if (pct != null) {
|
||||||
result.value = pct
|
result.value = pct
|
||||||
result.descriptionText = "{{ device.displayName }} battery was {{ value }}%"
|
result.descriptionText = "${linkText} battery was ${result.value}%"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -265,7 +253,7 @@ private Map getBatteryResult(rawValue) {
|
|||||||
def maxVolts = 3.0
|
def maxVolts = 3.0
|
||||||
def pct = (volts - minVolts) / (maxVolts - minVolts)
|
def pct = (volts - minVolts) / (maxVolts - minVolts)
|
||||||
result.value = Math.min(100, (int) pct * 100)
|
result.value = Math.min(100, (int) pct * 100)
|
||||||
result.descriptionText = "{{ device.displayName }} battery was {{ value }}%"
|
result.descriptionText = "${linkText} battery was ${result.value}%"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -275,37 +263,27 @@ private Map getBatteryResult(rawValue) {
|
|||||||
|
|
||||||
private Map getTemperatureResult(value) {
|
private Map getTemperatureResult(value) {
|
||||||
log.debug 'TEMP'
|
log.debug 'TEMP'
|
||||||
|
def linkText = getLinkText(device)
|
||||||
if (tempOffset) {
|
if (tempOffset) {
|
||||||
def offset = tempOffset as int
|
def offset = tempOffset as int
|
||||||
def v = value as int
|
def v = value as int
|
||||||
value = v + offset
|
value = v + offset
|
||||||
}
|
}
|
||||||
def descriptionText
|
def descriptionText = "${linkText} was ${value}°${temperatureScale}"
|
||||||
if ( temperatureScale == 'C' )
|
|
||||||
descriptionText = '{{ device.displayName }} was {{ value }}°C'
|
|
||||||
else
|
|
||||||
descriptionText = '{{ device.displayName }} was {{ value }}°F'
|
|
||||||
|
|
||||||
return [
|
return [
|
||||||
name: 'temperature',
|
name: 'temperature',
|
||||||
value: value,
|
value: value,
|
||||||
descriptionText: descriptionText,
|
descriptionText: descriptionText
|
||||||
translatable: true
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
private Map getMoistureResult(value) {
|
private Map getMoistureResult(value) {
|
||||||
log.debug "water"
|
log.debug 'water'
|
||||||
def descriptionText
|
String descriptionText = "${device.displayName} is ${value}"
|
||||||
if ( value == "wet" )
|
|
||||||
descriptionText = '{{ device.displayName }} is wet'
|
|
||||||
else
|
|
||||||
descriptionText = '{{ device.displayName }} is dry'
|
|
||||||
return [
|
return [
|
||||||
name: 'water',
|
name: 'water',
|
||||||
value: value,
|
value: value,
|
||||||
descriptionText: descriptionText,
|
descriptionText: descriptionText
|
||||||
translatable: true
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ metadata {
|
|||||||
capability "Water Sensor"
|
capability "Water Sensor"
|
||||||
capability "Sensor"
|
capability "Sensor"
|
||||||
capability "Battery"
|
capability "Battery"
|
||||||
capability "Temperature Measurement"
|
|
||||||
|
|
||||||
fingerprint deviceId: "0x2001", inClusters: "0x30,0x9C,0x9D,0x85,0x80,0x72,0x31,0x84,0x86"
|
fingerprint deviceId: "0x2001", inClusters: "0x30,0x9C,0x9D,0x85,0x80,0x72,0x31,0x84,0x86"
|
||||||
fingerprint deviceId: "0x2101", inClusters: "0x71,0x70,0x85,0x80,0x72,0x31,0x84,0x86"
|
fingerprint deviceId: "0x2101", inClusters: "0x71,0x70,0x85,0x80,0x72,0x31,0x84,0x86"
|
||||||
@@ -40,29 +39,17 @@ metadata {
|
|||||||
attributeState "wet", label: "Wet", icon:"st.alarm.water.wet", backgroundColor:"#53a7c0"
|
attributeState "wet", label: "Wet", icon:"st.alarm.water.wet", backgroundColor:"#53a7c0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
standardTile("temperatureState", "device.temperature", width: 2, height: 2) {
|
standardTile("temperature", "device.temperature", width: 2, height: 2) {
|
||||||
state "normal", icon:"st.alarm.temperature.normal", backgroundColor:"#ffffff"
|
state "normal", icon:"st.alarm.temperature.normal", backgroundColor:"#ffffff"
|
||||||
state "freezing", icon:"st.alarm.temperature.freeze", backgroundColor:"#53a7c0"
|
state "freezing", icon:"st.alarm.temperature.freeze", backgroundColor:"#53a7c0"
|
||||||
state "overheated", icon:"st.alarm.temperature.overheat", backgroundColor:"#F80000"
|
state "overheated", icon:"st.alarm.temperature.overheat", backgroundColor:"#F80000"
|
||||||
}
|
}
|
||||||
valueTile("temperature", "device.temperature", width: 2, height: 2) {
|
|
||||||
state("temperature", label:'${currentValue}°',
|
|
||||||
backgroundColors:[
|
|
||||||
[value: 31, color: "#153591"],
|
|
||||||
[value: 44, color: "#1e9cbb"],
|
|
||||||
[value: 59, color: "#90d2a7"],
|
|
||||||
[value: 74, color: "#44b621"],
|
|
||||||
[value: 84, color: "#f1d801"],
|
|
||||||
[value: 95, color: "#d04e00"],
|
|
||||||
[value: 96, color: "#bc2323"]
|
|
||||||
]
|
|
||||||
)
|
|
||||||
}
|
|
||||||
valueTile("battery", "device.battery", decoration: "flat", inactiveLabel: false, width: 2, height: 2) {
|
valueTile("battery", "device.battery", decoration: "flat", inactiveLabel: false, width: 2, height: 2) {
|
||||||
state "battery", label:'${currentValue}% battery', unit:""
|
state "battery", label:'${currentValue}% battery', unit:""
|
||||||
}
|
}
|
||||||
main (["water", "temperatureState"])
|
|
||||||
details(["water", "temperatureState", "temperature", "battery"])
|
main (["water", "temperature"])
|
||||||
|
details(["water", "temperature", "battery"])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -128,7 +115,7 @@ def zwaveEvent(physicalgraph.zwave.commands.alarmv2.AlarmReport cmd)
|
|||||||
map.descriptionText = "${device.displayName} is ${map.value}"
|
map.descriptionText = "${device.displayName} is ${map.value}"
|
||||||
}
|
}
|
||||||
if(cmd.zwaveAlarmType == physicalgraph.zwave.commands.alarmv2.AlarmReport.ZWAVE_ALARM_TYPE_HEAT) {
|
if(cmd.zwaveAlarmType == physicalgraph.zwave.commands.alarmv2.AlarmReport.ZWAVE_ALARM_TYPE_HEAT) {
|
||||||
map.name = "temperatureState"
|
map.name = "temperature"
|
||||||
if(cmd.zwaveAlarmEvent == 1) { map.value = "overheated"}
|
if(cmd.zwaveAlarmEvent == 1) { map.value = "overheated"}
|
||||||
if(cmd.zwaveAlarmEvent == 2) { map.value = "overheated"}
|
if(cmd.zwaveAlarmEvent == 2) { map.value = "overheated"}
|
||||||
if(cmd.zwaveAlarmEvent == 3) { map.value = "changing temperature rapidly"}
|
if(cmd.zwaveAlarmEvent == 3) { map.value = "changing temperature rapidly"}
|
||||||
@@ -142,30 +129,17 @@ def zwaveEvent(physicalgraph.zwave.commands.alarmv2.AlarmReport cmd)
|
|||||||
map
|
map
|
||||||
}
|
}
|
||||||
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.sensormultilevelv5.SensorMultilevelReport cmd)
|
def zwaveEvent(physicalgraph.zwave.commands.basicv1.BasicSet cmd)
|
||||||
{
|
{
|
||||||
def map = [:]
|
def map = [:]
|
||||||
if(cmd.sensorType == 1) {
|
map.name = "water"
|
||||||
map.name = "temperature"
|
map.value = cmd.value ? "wet" : "dry"
|
||||||
if(cmd.scale == 0) {
|
map.descriptionText = "${device.displayName} is ${map.value}"
|
||||||
map.value = getTemperature(cmd.scaledSensorValue)
|
|
||||||
} else {
|
|
||||||
map.value = cmd.scaledSensorValue
|
|
||||||
}
|
|
||||||
map.unit = location.temperatureScale
|
|
||||||
}
|
|
||||||
map
|
map
|
||||||
}
|
}
|
||||||
|
|
||||||
def getTemperature(value) {
|
|
||||||
if(location.temperatureScale == "C"){
|
|
||||||
return value
|
|
||||||
} else {
|
|
||||||
return Math.round(celsiusToFahrenheit(value))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def zwaveEvent(physicalgraph.zwave.Command cmd)
|
def zwaveEvent(physicalgraph.zwave.Command cmd)
|
||||||
{
|
{
|
||||||
log.debug "COMMAND CLASS: $cmd"
|
log.debug "COMMAND CLASS: $cmd"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,40 +0,0 @@
|
|||||||
#==============================================================================
|
|
||||||
# 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: SmartSense Motion Sensor i18n Translation File
|
|
||||||
#
|
|
||||||
# Filename: SmartSense-Motion-Sensor.src/i18n/messages.properties
|
|
||||||
#
|
|
||||||
# Change History:
|
|
||||||
# 1. 20160116 TW Initial release with formal Korean translation.
|
|
||||||
# 2. 20160224 TW Updated formal Korean translations from Mike Stoller.
|
|
||||||
#==============================================================================
|
|
||||||
# Korean (ko)
|
|
||||||
# Device Preferences
|
|
||||||
'''battery'''.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=기준 온도를 원하는대로 몇 도 올리거나 내려서 설정할 수 있습니다.
|
|
||||||
'''Degrees'''.ko=온도
|
|
||||||
'''Adjust temperature by this many degrees'''.ko=몇 도씩 온도를 조절하십시오
|
|
||||||
'''Give your device a name'''.ko=기기 이름 바꾸기
|
|
||||||
'''Motion Sensor'''.ko=모션 센서
|
|
||||||
# Events descriptionText
|
|
||||||
'''{{ 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였습니다
|
|
||||||
'''{{ device.displayName }} battery has too much power: (> 3.5) volts.'''.ko={{ device.displayName }} 배터리 전력이 너무 높습니다(3.5볼트 초과).
|
|
||||||
'''{{ device.displayName }} battery was {{ value }}%'''.ko={{ device.displayName }}남아있는 배터리는 {{ value }}%입니다.
|
|
||||||
#==============================================================================
|
|
||||||
@@ -1,27 +1,17 @@
|
|||||||
/*
|
/**
|
||||||
===============================================================================
|
* SmartSense Motion/Temp Sensor
|
||||||
* Copyright 2016 SmartThings
|
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
* Copyright 2014 SmartThings
|
||||||
* use this file except in compliance with the License. You may obtain a copy
|
*
|
||||||
* of the License at:
|
* 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
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
* for the specific language governing permissions and limitations under the License.
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
===============================================================================
|
|
||||||
* Purpose: SmartSense Motion Sensor DTH File
|
|
||||||
*
|
*
|
||||||
* Filename: SmartSense-Motion-Sensor.src/SmartSense-Motion-Sensor.groovy
|
|
||||||
*
|
|
||||||
* Change History:
|
|
||||||
* 1. 20160116 TW - Update/Edit to support i18n translations
|
|
||||||
* 2. 20160125 TW = Incorporated new battery mapping from TM
|
|
||||||
===============================================================================
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
metadata {
|
metadata {
|
||||||
@@ -39,7 +29,6 @@ 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: "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: "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"
|
||||||
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"
|
fingerprint inClusters: "0000,0001,0003,000F,0020,0402,0500", outClusters: "0019", manufacturer: "SmartThings", model: "motionv4", deviceJoinName: "Motion Sensor"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,7 +46,7 @@ metadata {
|
|||||||
])
|
])
|
||||||
}
|
}
|
||||||
section {
|
section {
|
||||||
input title: "Temperature Offset", description: "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'.", displayDuringSetup: false, type: "paragraph", element: "paragraph"
|
input title: "Temperature Offset", description: "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\".", displayDuringSetup: false, type: "paragraph", element: "paragraph"
|
||||||
input "tempOffset", "number", title: "Degrees", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false
|
input "tempOffset", "number", title: "Degrees", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -246,8 +235,7 @@ private Map getBatteryResult(rawValue) {
|
|||||||
|
|
||||||
def result = [
|
def result = [
|
||||||
name: 'battery',
|
name: 'battery',
|
||||||
value: '--',
|
value: '--'
|
||||||
translatable: true
|
|
||||||
]
|
]
|
||||||
|
|
||||||
def volts = rawValue / 10
|
def volts = rawValue / 10
|
||||||
@@ -255,7 +243,7 @@ private Map getBatteryResult(rawValue) {
|
|||||||
if (rawValue == 0 || rawValue == 255) {}
|
if (rawValue == 0 || rawValue == 255) {}
|
||||||
else {
|
else {
|
||||||
if (volts > 3.5) {
|
if (volts > 3.5) {
|
||||||
result.descriptionText = "{{ device.displayName }} battery has too much power: (> 3.5) volts."
|
result.descriptionText = "${linkText} battery has too much power (${volts} volts)."
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if (device.getDataValue("manufacturer") == "SmartThings") {
|
if (device.getDataValue("manufacturer") == "SmartThings") {
|
||||||
@@ -272,8 +260,7 @@ private Map getBatteryResult(rawValue) {
|
|||||||
def pct = batteryMap[volts]
|
def pct = batteryMap[volts]
|
||||||
if (pct != null) {
|
if (pct != null) {
|
||||||
result.value = pct
|
result.value = pct
|
||||||
def value = pct
|
result.descriptionText = "${linkText} battery was ${result.value}%"
|
||||||
result.descriptionText = "{{ device.displayName }} battery was {{ value }}%"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -281,7 +268,7 @@ private Map getBatteryResult(rawValue) {
|
|||||||
def maxVolts = 3.0
|
def maxVolts = 3.0
|
||||||
def pct = (volts - minVolts) / (maxVolts - minVolts)
|
def pct = (volts - minVolts) / (maxVolts - minVolts)
|
||||||
result.value = Math.min(100, (int) pct * 100)
|
result.value = Math.min(100, (int) pct * 100)
|
||||||
result.descriptionText = "{{ device.displayName }} battery was {{ value }}%"
|
result.descriptionText = "${linkText} battery was ${result.value}%"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -291,33 +278,28 @@ private Map getBatteryResult(rawValue) {
|
|||||||
|
|
||||||
private Map getTemperatureResult(value) {
|
private Map getTemperatureResult(value) {
|
||||||
log.debug 'TEMP'
|
log.debug 'TEMP'
|
||||||
|
def linkText = getLinkText(device)
|
||||||
if (tempOffset) {
|
if (tempOffset) {
|
||||||
def offset = tempOffset as int
|
def offset = tempOffset as int
|
||||||
def v = value as int
|
def v = value as int
|
||||||
value = v + offset
|
value = v + offset
|
||||||
}
|
}
|
||||||
def descriptionText
|
def descriptionText = "${linkText} was ${value}°${temperatureScale}"
|
||||||
if ( temperatureScale == 'C' )
|
|
||||||
descriptionText = '{{ device.displayName }} was {{ value }}°C'
|
|
||||||
else
|
|
||||||
descriptionText = '{{ device.displayName }} was {{ value }}°F'
|
|
||||||
|
|
||||||
return [
|
return [
|
||||||
name: 'temperature',
|
name: 'temperature',
|
||||||
value: value,
|
value: value,
|
||||||
descriptionText: descriptionText,
|
descriptionText: descriptionText
|
||||||
translatable: true
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
private Map getMotionResult(value) {
|
private Map getMotionResult(value) {
|
||||||
log.debug 'motion'
|
log.debug 'motion'
|
||||||
String descriptionText = value == 'active' ? "{{ device.displayName }} detected motion" : "{{ device.displayName }} motion has stopped"
|
String linkText = getLinkText(device)
|
||||||
|
String descriptionText = value == 'active' ? "${linkText} detected motion" : "${linkText} motion has stopped"
|
||||||
return [
|
return [
|
||||||
name: 'motion',
|
name: 'motion',
|
||||||
value: value,
|
value: value,
|
||||||
descriptionText: descriptionText,
|
descriptionText: descriptionText
|
||||||
translatable: true
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,8 +14,6 @@
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
//DEPRECATED - Using the smartsense-motion-sensor.groovy DTH for this device. Users need to be moved before deleting this DTH
|
|
||||||
|
|
||||||
metadata {
|
metadata {
|
||||||
definition (name: "SmartSense Motion/Temp Sensor", namespace: "smartthings", author: "SmartThings") {
|
definition (name: "SmartSense Motion/Temp Sensor", namespace: "smartthings", author: "SmartThings") {
|
||||||
capability "Motion Sensor"
|
capability "Motion Sensor"
|
||||||
@@ -27,6 +25,10 @@ metadata {
|
|||||||
|
|
||||||
command "enrollResponse"
|
command "enrollResponse"
|
||||||
|
|
||||||
|
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3305-S"
|
||||||
|
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"
|
||||||
}
|
}
|
||||||
|
|
||||||
simulator {
|
simulator {
|
||||||
@@ -231,7 +233,7 @@ private Map getBatteryResult(rawValue) {
|
|||||||
def volts = rawValue / 10
|
def volts = rawValue / 10
|
||||||
def descriptionText
|
def descriptionText
|
||||||
|
|
||||||
if (rawValue == 0 || rawValue == 255) {}
|
if (rawValue == 0) {}
|
||||||
else {
|
else {
|
||||||
if (volts > 3.5) {
|
if (volts > 3.5) {
|
||||||
result.descriptionText = "${linkText} battery has too much power (${volts} volts)."
|
result.descriptionText = "${linkText} battery has too much power (${volts} volts)."
|
||||||
|
|||||||
@@ -1,45 +0,0 @@
|
|||||||
#==============================================================================
|
|
||||||
# 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: SmartSense Multi Sensor i18n Translation File
|
|
||||||
#
|
|
||||||
# Filename: SmartSense-Multi-Sensor.src/i18n/messages.properties
|
|
||||||
#
|
|
||||||
# Change History:
|
|
||||||
# 1. 20160117 TW Initial release with informal Korean translation.
|
|
||||||
#==============================================================================
|
|
||||||
# Korean (ko)
|
|
||||||
# Device Preferences
|
|
||||||
'''Yes'''.ko=예
|
|
||||||
'''No'''.ko=아니요
|
|
||||||
'''battery'''.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=기준 온도를 원하는대로 몇 도 올리거나 내려서 설정할 수 있습니다.
|
|
||||||
'''Degrees'''.ko=온도
|
|
||||||
'''Adjust temperature by this many degrees'''.ko=몇 도씩 온도를 조절하십시오
|
|
||||||
'''Do you want to use this sensor on a garage door?'''.ko=차고 문의 센서 사용 설정하기
|
|
||||||
'''Tap to set'''.ko=눌러서 설정
|
|
||||||
'''Give your device a name'''.ko=기기 이름 바꾸기
|
|
||||||
'''Multipurpose Sensor'''.ko=멀티 센서
|
|
||||||
# Events descriptionText
|
|
||||||
'''{{ device.displayName }} was opened'''.ko={{ device.displayName }}열림을 감지하였습니다.
|
|
||||||
'''{{ device.displayName }} was closed'''.ko={{ device.displayName }}닫혔습니다.
|
|
||||||
'''{{ device.displayName }} was active'''.ko={{ device.displayName }}활성화되었습니다.
|
|
||||||
'''{{ device.displayName }} was inactive'''.ko={{ device.displayName }}비활성화되었습니다.
|
|
||||||
'''{{ device.displayName }} was {{ value }}°C'''.ko={{ device.displayName }}이(가){{ value }}°C였습니다.
|
|
||||||
'''{{ device.displayName }} was {{ value }}°F'''.ko={{ device.displayName }}이(가) {{ value }}°F였습니다
|
|
||||||
'''{{ device.displayName }} battery was {{ value }}%'''.ko={{ device.displayName }}남아있는 배터리는 {{ value }}%입니다.
|
|
||||||
'''Updating device to garage sensor'''.ko=기기-차고 센서 업데이트 중
|
|
||||||
'''Updating device to open/close sensor'''.ko=기기-열림/닫힘 센서 업데이트 중
|
|
||||||
@@ -1,27 +1,17 @@
|
|||||||
/*
|
/**
|
||||||
===============================================================================
|
* SmartSense Multi
|
||||||
* Copyright 2016 SmartThings
|
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
* Copyright 2015 SmartThings
|
||||||
* use this file except in compliance with the License. You may obtain a copy
|
*
|
||||||
* of the License at:
|
* 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
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
|
||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
* for the specific language governing permissions and limitations under the License.
|
||||||
* License for the specific language governing permissions and limitations
|
|
||||||
* under the License.
|
|
||||||
===============================================================================
|
|
||||||
* Purpose: SmartSense Multi Sensor DTH File
|
|
||||||
*
|
*
|
||||||
* Filename: SmartSense-Multi-Sensor.src/SmartSense-Multi-Sensor.groovy
|
|
||||||
*
|
|
||||||
* Change History:
|
|
||||||
* 1. 20160117 TW - Update/Edit to support i18n translations
|
|
||||||
* 2. 20160125 TW = Incorporated new battery mapping from TM
|
|
||||||
===============================================================================
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
metadata {
|
metadata {
|
||||||
@@ -72,11 +62,11 @@ metadata {
|
|||||||
])
|
])
|
||||||
}
|
}
|
||||||
section {
|
section {
|
||||||
input title: "Temperature Offset", description: "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'.", displayDuringSetup: false, type: "paragraph", element: "paragraph"
|
input title: "Temperature Offset", description: "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\".", displayDuringSetup: false, type: "paragraph", element: "paragraph"
|
||||||
input "tempOffset", "number", title: "Degrees", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false
|
input "tempOffset", "number", title: "Degrees", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false
|
||||||
}
|
}
|
||||||
section {
|
section {
|
||||||
input("garageSensor", "enum", title: "Do you want to use this sensor on a garage door?", description: "Tap to set", options: ["Yes", "No"], defaultValue: "No", required: false, displayDuringSetup: false)
|
input("garageSensor", "enum", title: "Do you want to use this sensor on a garage door?", options: ["Yes", "No"], defaultValue: "No", required: false, displayDuringSetup: false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,6 +100,9 @@ metadata {
|
|||||||
]
|
]
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
valueTile("3axis", "device.threeAxis", decoration: "flat", wordWrap: false, width: 2, height: 2) {
|
||||||
|
state("threeAxis", label:'${currentValue}', unit:"", backgroundColor:"#ffffff")
|
||||||
|
}
|
||||||
valueTile("battery", "device.battery", decoration: "flat", inactiveLabel: false, width: 2, height: 2) {
|
valueTile("battery", "device.battery", decoration: "flat", inactiveLabel: false, width: 2, height: 2) {
|
||||||
state "battery", label:'${currentValue}% battery', unit:""
|
state "battery", label:'${currentValue}% battery', unit:""
|
||||||
}
|
}
|
||||||
@@ -119,7 +112,7 @@ metadata {
|
|||||||
|
|
||||||
|
|
||||||
main(["status", "acceleration", "temperature"])
|
main(["status", "acceleration", "temperature"])
|
||||||
details(["status", "acceleration", "temperature", "battery", "refresh"])
|
details(["status", "acceleration", "temperature", "3axis", "battery", "refresh"])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -283,19 +276,19 @@ def updated() {
|
|||||||
if (garageSensor == "Yes") {
|
if (garageSensor == "Yes") {
|
||||||
def descriptionText = "Updating device to garage sensor"
|
def descriptionText = "Updating device to garage sensor"
|
||||||
if (device.latestValue("status") == "open") {
|
if (device.latestValue("status") == "open") {
|
||||||
sendEvent(name: 'status', value: 'garage-open', descriptionText: descriptionText, translatable: true)
|
sendEvent(name: 'status', value: 'garage-open', descriptionText: descriptionText)
|
||||||
}
|
}
|
||||||
else if (device.latestValue("status") == "closed") {
|
else if (device.latestValue("status") == "closed") {
|
||||||
sendEvent(name: 'status', value: 'garage-closed', descriptionText: descriptionText, translatable: true)
|
sendEvent(name: 'status', value: 'garage-closed', descriptionText: descriptionText)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
def descriptionText = "Updating device to open/close sensor"
|
def descriptionText = "Updating device to open/close sensor"
|
||||||
if (device.latestValue("status") == "garage-open") {
|
if (device.latestValue("status") == "garage-open") {
|
||||||
sendEvent(name: 'status', value: 'open', descriptionText: descriptionText, translatable: true)
|
sendEvent(name: 'status', value: 'open', descriptionText: descriptionText)
|
||||||
}
|
}
|
||||||
else if (device.latestValue("status") == "garage-closed") {
|
else if (device.latestValue("status") == "garage-closed") {
|
||||||
sendEvent(name: 'status', value: 'closed', descriptionText: descriptionText, translatable: true)
|
sendEvent(name: 'status', value: 'closed', descriptionText: descriptionText)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -311,11 +304,11 @@ def getTemperature(value) {
|
|||||||
|
|
||||||
private Map getBatteryResult(rawValue) {
|
private Map getBatteryResult(rawValue) {
|
||||||
log.debug "Battery rawValue = ${rawValue}"
|
log.debug "Battery rawValue = ${rawValue}"
|
||||||
|
def linkText = getLinkText(device)
|
||||||
|
|
||||||
def result = [
|
def result = [
|
||||||
name: 'battery',
|
name: 'battery',
|
||||||
value: '--',
|
value: '--'
|
||||||
translatable: true
|
|
||||||
]
|
]
|
||||||
|
|
||||||
def volts = rawValue / 10
|
def volts = rawValue / 10
|
||||||
@@ -323,7 +316,7 @@ private Map getBatteryResult(rawValue) {
|
|||||||
if (rawValue == 0 || rawValue == 255) {}
|
if (rawValue == 0 || rawValue == 255) {}
|
||||||
else {
|
else {
|
||||||
if (volts > 3.5) {
|
if (volts > 3.5) {
|
||||||
result.descriptionText = "{{ device.displayName }} battery has too much power: (> 3.5) volts."
|
result.descriptionText = "${linkText} battery has too much power (${volts} volts)."
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if (device.getDataValue("manufacturer") == "SmartThings") {
|
if (device.getDataValue("manufacturer") == "SmartThings") {
|
||||||
@@ -340,7 +333,7 @@ private Map getBatteryResult(rawValue) {
|
|||||||
def pct = batteryMap[volts]
|
def pct = batteryMap[volts]
|
||||||
if (pct != null) {
|
if (pct != null) {
|
||||||
result.value = pct
|
result.value = pct
|
||||||
result.descriptionText = "{{ device.displayName }} battery was {{ value }}%"
|
result.descriptionText = "${linkText} battery was ${result.value}%"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -348,7 +341,7 @@ private Map getBatteryResult(rawValue) {
|
|||||||
def maxVolts = 3.0
|
def maxVolts = 3.0
|
||||||
def pct = (volts - minVolts) / (maxVolts - minVolts)
|
def pct = (volts - minVolts) / (maxVolts - minVolts)
|
||||||
result.value = Math.min(100, (int) pct * 100)
|
result.value = Math.min(100, (int) pct * 100)
|
||||||
result.descriptionText = "{{ device.displayName }} battery was {{ value }}%"
|
result.descriptionText = "${linkText} battery was ${result.value}%"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -358,50 +351,40 @@ private Map getBatteryResult(rawValue) {
|
|||||||
|
|
||||||
private Map getTemperatureResult(value) {
|
private Map getTemperatureResult(value) {
|
||||||
log.debug "Temperature"
|
log.debug "Temperature"
|
||||||
|
def linkText = getLinkText(device)
|
||||||
if (tempOffset) {
|
if (tempOffset) {
|
||||||
def offset = tempOffset as int
|
def offset = tempOffset as int
|
||||||
def v = value as int
|
def v = value as int
|
||||||
value = v + offset
|
value = v + offset
|
||||||
}
|
}
|
||||||
def descriptionText = temperatureScale == 'C' ? '{{ device.displayName }} was {{ value }}°C':
|
def descriptionText = "${linkText} was ${value}°${temperatureScale}"
|
||||||
'{{ device.displayName }} was {{ value }}°F'
|
|
||||||
|
|
||||||
return [
|
return [
|
||||||
name: 'temperature',
|
name: 'temperature',
|
||||||
value: value,
|
value: value,
|
||||||
descriptionText: descriptionText,
|
descriptionText: descriptionText
|
||||||
translatable: true
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
private Map getContactResult(value) {
|
private Map getContactResult(value) {
|
||||||
log.debug "Contact: ${device.displayName} value = ${value}"
|
log.debug "Contact"
|
||||||
def descriptionText = value == 'open' ? '{{ device.displayName }} was opened' : '{{ device.displayName }} was closed'
|
def linkText = getLinkText(device)
|
||||||
sendEvent(name: 'contact', value: value, descriptionText: descriptionText, displayed: false, translatable: true)
|
def descriptionText = "${linkText} was ${value == 'open' ? 'opened' : 'closed'}"
|
||||||
sendEvent(name: 'status', value: value, descriptionText: descriptionText, translatable: true)
|
sendEvent(name: 'contact', value: value, descriptionText: descriptionText, displayed:false)
|
||||||
|
sendEvent(name: 'status', value: value, descriptionText: descriptionText)
|
||||||
}
|
}
|
||||||
|
|
||||||
private getAccelerationResult(numValue) {
|
private getAccelerationResult(numValue) {
|
||||||
log.debug "Acceleration"
|
log.debug "Acceleration"
|
||||||
def name = "acceleration"
|
def name = "acceleration"
|
||||||
def value
|
def value = numValue.endsWith("1") ? "active" : "inactive"
|
||||||
def descriptionText
|
def linkText = getLinkText(device)
|
||||||
|
def descriptionText = "$linkText was $value"
|
||||||
if ( numValue.endsWith("1") ) {
|
|
||||||
value = "active"
|
|
||||||
descriptionText = '{{ device.displayName }} was active'
|
|
||||||
} else {
|
|
||||||
value = "inactive"
|
|
||||||
descriptionText = '{{ device.displayName }} was inactive'
|
|
||||||
}
|
|
||||||
|
|
||||||
def isStateChange = isStateChange(device, name, value)
|
def isStateChange = isStateChange(device, name, value)
|
||||||
return [
|
[
|
||||||
name: name,
|
name: name,
|
||||||
value: value,
|
value: value,
|
||||||
descriptionText: descriptionText,
|
descriptionText: descriptionText,
|
||||||
isStateChange: isStateChange,
|
isStateChange: isStateChange
|
||||||
translatable: true
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -458,28 +441,28 @@ def configure() {
|
|||||||
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
|
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
|
||||||
|
|
||||||
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 1 {${device.zigbeeId}} {}", "delay 200",
|
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 1 {${device.zigbeeId}} {}", "delay 200",
|
||||||
"zcl global send-me-a-report 1 0x20 0x20 30 21600 {01}", "delay 200", //checkin time 6 hrs
|
"zcl global send-me-a-report 1 0x20 0x20 30 21600 {01}", //checkin time 6 hrs
|
||||||
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
|
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
|
||||||
|
|
||||||
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 0x402 {${device.zigbeeId}} {}", "delay 200",
|
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 0x402 {${device.zigbeeId}} {}", "delay 200",
|
||||||
"zcl global send-me-a-report 0x402 0 0x29 30 3600 {6400}", "delay 200",
|
"zcl global send-me-a-report 0x402 0 0x29 30 3600 {6400}",
|
||||||
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
|
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
|
||||||
|
|
||||||
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 0xFC02 {${device.zigbeeId}} {}", "delay 200",
|
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 0xFC02 {${device.zigbeeId}} {}", "delay 200",
|
||||||
"zcl mfg-code ${manufacturerCode}", "delay 200",
|
"zcl mfg-code ${manufacturerCode}",
|
||||||
"zcl global send-me-a-report 0xFC02 0x0010 0x18 10 3600 {01}", "delay 200",
|
"zcl global send-me-a-report 0xFC02 0x0010 0x18 10 3600 {01}",
|
||||||
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
|
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
|
||||||
|
|
||||||
"zcl mfg-code ${manufacturerCode}", "delay 200",
|
"zcl mfg-code ${manufacturerCode}",
|
||||||
"zcl global send-me-a-report 0xFC02 0x0012 0x29 1 3600 {0100}", "delay 200",
|
"zcl global send-me-a-report 0xFC02 0x0012 0x29 1 3600 {01}",
|
||||||
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
|
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
|
||||||
|
|
||||||
"zcl mfg-code ${manufacturerCode}", "delay 200",
|
"zcl mfg-code ${manufacturerCode}",
|
||||||
"zcl global send-me-a-report 0xFC02 0x0013 0x29 1 3600 {0100}", "delay 200",
|
"zcl global send-me-a-report 0xFC02 0x0013 0x29 1 3600 {01}",
|
||||||
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
|
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
|
||||||
|
|
||||||
"zcl mfg-code ${manufacturerCode}", "delay 200",
|
"zcl mfg-code ${manufacturerCode}",
|
||||||
"zcl global send-me-a-report 0xFC02 0x0014 0x29 1 3600 {0100}", "delay 200",
|
"zcl global send-me-a-report 0xFC02 0x0014 0x29 1 3600 {01}",
|
||||||
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500"
|
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500"
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -498,12 +481,17 @@ def enrollResponse() {
|
|||||||
"zcl global write 0x500 0x10 0xf0 {${zigbeeEui}}", "delay 200",
|
"zcl global write 0x500 0x10 0xf0 {${zigbeeEui}}", "delay 200",
|
||||||
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
|
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
|
||||||
//Enroll Response
|
//Enroll Response
|
||||||
"raw 0x500 {01 23 00 00 00}", "delay 200",
|
"raw 0x500 {01 23 00 00 00}",
|
||||||
"send 0x${device.deviceNetworkId} 1 1", "delay 200"
|
"send 0x${device.deviceNetworkId} 1 1", "delay 200"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
private Map parseAxis(String description) {
|
private Map parseAxis(String description) {
|
||||||
|
def hexToSignedInt = { hexVal ->
|
||||||
|
def unsignedVal = hexToInt(hexVal)
|
||||||
|
unsignedVal > 32767 ? unsignedVal - 65536 : unsignedVal
|
||||||
|
}
|
||||||
|
|
||||||
def z = hexToSignedInt(description[0..3])
|
def z = hexToSignedInt(description[0..3])
|
||||||
def y = hexToSignedInt(description[10..13])
|
def y = hexToSignedInt(description[10..13])
|
||||||
def x = hexToSignedInt(description[20..23])
|
def x = hexToSignedInt(description[20..23])
|
||||||
@@ -530,11 +518,6 @@ private Map parseAxis(String description) {
|
|||||||
getXyzResult(xyzResults, description)
|
getXyzResult(xyzResults, description)
|
||||||
}
|
}
|
||||||
|
|
||||||
private hexToSignedInt(hexVal) {
|
|
||||||
def unsignedVal = hexToInt(hexVal)
|
|
||||||
unsignedVal > 32767 ? unsignedVal - 65536 : unsignedVal
|
|
||||||
}
|
|
||||||
|
|
||||||
def garageEvent(zValue) {
|
def garageEvent(zValue) {
|
||||||
def absValue = zValue.abs()
|
def absValue = zValue.abs()
|
||||||
def contactValue = null
|
def contactValue = null
|
||||||
@@ -548,9 +531,10 @@ def garageEvent(zValue) {
|
|||||||
garageValue = 'garage-open'
|
garageValue = 'garage-open'
|
||||||
}
|
}
|
||||||
if (contactValue != null){
|
if (contactValue != null){
|
||||||
def descriptionText = contactValue == 'open' ? '{{ device.displayName }} was opened' :'{{ device.displayName }} was closed'
|
def linkText = getLinkText(device)
|
||||||
sendEvent(name: 'contact', value: contactValue, descriptionText: descriptionText, displayed:false, translatable: true)
|
def descriptionText = "${linkText} was ${contactValue == 'open' ? 'opened' : 'closed'}"
|
||||||
sendEvent(name: 'status', value: garageValue, descriptionText: descriptionText, translatable: true)
|
sendEvent(name: 'contact', value: contactValue, descriptionText: descriptionText, displayed:false)
|
||||||
|
sendEvent(name: 'status', value: garageValue, descriptionText: descriptionText)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,8 +22,6 @@ metadata {
|
|||||||
capability "Battery"
|
capability "Battery"
|
||||||
|
|
||||||
fingerprint profileId: "FC01", deviceId: "0139"
|
fingerprint profileId: "FC01", deviceId: "0139"
|
||||||
|
|
||||||
attribute "status", "string"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
simulator {
|
simulator {
|
||||||
@@ -74,12 +72,15 @@ metadata {
|
|||||||
]
|
]
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
valueTile("3axis", "device.threeAxis", decoration: "flat", wordWrap: false, width: 2, height: 2) {
|
||||||
|
state("threeAxis", label:'${currentValue}', unit:"", backgroundColor:"#ffffff")
|
||||||
|
}
|
||||||
valueTile("battery", "device.battery", decoration: "flat", inactiveLabel: false, width: 2, height: 2) {
|
valueTile("battery", "device.battery", decoration: "flat", inactiveLabel: false, width: 2, height: 2) {
|
||||||
state "battery", label:'${currentValue}% battery', unit:""
|
state "battery", label:'${currentValue}% battery', unit:""
|
||||||
}
|
}
|
||||||
|
|
||||||
main(["contact", "acceleration", "temperature"])
|
main(["contact", "acceleration", "temperature"])
|
||||||
details(["contact", "acceleration", "temperature", "battery"])
|
details(["contact", "acceleration", "temperature", "3axis", "battery"])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,7 +102,7 @@ def parse(String description) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private List parseSingleMessage(description) {
|
private Map parseSingleMessage(description) {
|
||||||
|
|
||||||
def name = parseName(description)
|
def name = parseName(description)
|
||||||
def value = parseValue(description)
|
def value = parseValue(description)
|
||||||
@@ -110,9 +111,8 @@ private List parseSingleMessage(description) {
|
|||||||
def handlerName = value == 'open' ? 'opened' : value
|
def handlerName = value == 'open' ? 'opened' : value
|
||||||
def isStateChange = isStateChange(device, name, value)
|
def isStateChange = isStateChange(device, name, value)
|
||||||
|
|
||||||
def results = []
|
def results = [
|
||||||
results << createEvent(
|
name: name,
|
||||||
name: "contact",
|
|
||||||
value: value,
|
value: value,
|
||||||
unit: null,
|
unit: null,
|
||||||
linkText: linkText,
|
linkText: linkText,
|
||||||
@@ -120,18 +120,8 @@ private List parseSingleMessage(description) {
|
|||||||
handlerName: handlerName,
|
handlerName: handlerName,
|
||||||
isStateChange: isStateChange,
|
isStateChange: isStateChange,
|
||||||
displayed: displayed(description, isStateChange)
|
displayed: displayed(description, isStateChange)
|
||||||
)
|
]
|
||||||
|
log.debug "Parse results for $device: $results"
|
||||||
results << createEvent(
|
|
||||||
name: "status",
|
|
||||||
value: value,
|
|
||||||
unit: null,
|
|
||||||
linkText: linkText,
|
|
||||||
descriptionText: descriptionText,
|
|
||||||
handlerName: handlerName,
|
|
||||||
isStateChange: isStateChange,
|
|
||||||
displayed: displayed(description, isStateChange)
|
|
||||||
)
|
|
||||||
|
|
||||||
results
|
results
|
||||||
}
|
}
|
||||||
@@ -203,7 +193,7 @@ private List parseContactMessage(String description) {
|
|||||||
parts.each { part ->
|
parts.each { part ->
|
||||||
part = part.trim()
|
part = part.trim()
|
||||||
if (part.startsWith('contactState:')) {
|
if (part.startsWith('contactState:')) {
|
||||||
results.addAll(getContactResult(part, description))
|
results << getContactResult(part, description)
|
||||||
}
|
}
|
||||||
else if (part.startsWith('accelerationState:')) {
|
else if (part.startsWith('accelerationState:')) {
|
||||||
results << getAccelerationResult(part, description)
|
results << getAccelerationResult(part, description)
|
||||||
@@ -282,7 +272,7 @@ private List parseRssiLqiMessage(String description) {
|
|||||||
results
|
results
|
||||||
}
|
}
|
||||||
|
|
||||||
private List getContactResult(part, description) {
|
private getContactResult(part, description) {
|
||||||
def name = "contact"
|
def name = "contact"
|
||||||
def value = part.endsWith("1") ? "open" : "closed"
|
def value = part.endsWith("1") ? "open" : "closed"
|
||||||
def handlerName = value == 'open' ? 'opened' : value
|
def handlerName = value == 'open' ? 'opened' : value
|
||||||
@@ -290,33 +280,19 @@ private List getContactResult(part, description) {
|
|||||||
def descriptionText = "$linkText was $handlerName"
|
def descriptionText = "$linkText was $handlerName"
|
||||||
def isStateChange = isStateChange(device, name, value)
|
def isStateChange = isStateChange(device, name, value)
|
||||||
|
|
||||||
def results = []
|
[
|
||||||
results << createEvent(
|
name: name,
|
||||||
name: "contact",
|
value: value,
|
||||||
value: value,
|
unit: null,
|
||||||
unit: null,
|
linkText: linkText,
|
||||||
linkText: linkText,
|
descriptionText: descriptionText,
|
||||||
descriptionText: descriptionText,
|
handlerName: handlerName,
|
||||||
handlerName: handlerName,
|
isStateChange: isStateChange,
|
||||||
isStateChange: isStateChange,
|
displayed: displayed(description, isStateChange)
|
||||||
displayed:false
|
]
|
||||||
)
|
|
||||||
|
|
||||||
results << createEvent(
|
|
||||||
name: "status",
|
|
||||||
value: value,
|
|
||||||
unit: null,
|
|
||||||
linkText: linkText,
|
|
||||||
descriptionText: descriptionText,
|
|
||||||
handlerName: handlerName,
|
|
||||||
isStateChange: isStateChange,
|
|
||||||
displayed: displayed(description, isStateChange)
|
|
||||||
)
|
|
||||||
|
|
||||||
results
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Map getAccelerationResult(part, description) {
|
private getAccelerationResult(part, description) {
|
||||||
def name = "acceleration"
|
def name = "acceleration"
|
||||||
def value = part.endsWith("1") ? "active" : "inactive"
|
def value = part.endsWith("1") ? "active" : "inactive"
|
||||||
def linkText = getLinkText(device)
|
def linkText = getLinkText(device)
|
||||||
@@ -335,7 +311,7 @@ private Map getAccelerationResult(part, description) {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
private Map getTempResult(part, description) {
|
private getTempResult(part, description) {
|
||||||
def name = "temperature"
|
def name = "temperature"
|
||||||
def temperatureScale = getTemperatureScale()
|
def temperatureScale = getTemperatureScale()
|
||||||
def value = zigbee.parseSmartThingsTemperatureValue(part, "temp: ", temperatureScale)
|
def value = zigbee.parseSmartThingsTemperatureValue(part, "temp: ", temperatureScale)
|
||||||
@@ -360,7 +336,7 @@ private Map getTempResult(part, description) {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
private Map getXyzResult(results, description) {
|
private getXyzResult(results, description) {
|
||||||
def name = "threeAxis"
|
def name = "threeAxis"
|
||||||
def value = "${results.x},${results.y},${results.z}"
|
def value = "${results.x},${results.y},${results.z}"
|
||||||
def linkText = getLinkText(device)
|
def linkText = getLinkText(device)
|
||||||
@@ -379,7 +355,7 @@ private Map getXyzResult(results, description) {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
private Map getBatteryResult(part, description) {
|
private getBatteryResult(part, description) {
|
||||||
def batteryDivisor = description.split(",").find {it.split(":")[0].trim() == "batteryDivisor"} ? description.split(",").find {it.split(":")[0].trim() == "batteryDivisor"}.split(":")[1].trim() : null
|
def batteryDivisor = description.split(",").find {it.split(":")[0].trim() == "batteryDivisor"} ? description.split(",").find {it.split(":")[0].trim() == "batteryDivisor"}.split(":")[1].trim() : null
|
||||||
def name = "battery"
|
def name = "battery"
|
||||||
def value = zigbee.parseSmartThingsBatteryValue(part, batteryDivisor)
|
def value = zigbee.parseSmartThingsBatteryValue(part, batteryDivisor)
|
||||||
@@ -400,7 +376,7 @@ private Map getBatteryResult(part, description) {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
private Map getRssiResult(part, description, lastHop=false) {
|
private getRssiResult(part, description, lastHop=false) {
|
||||||
def name = lastHop ? "lastHopRssi" : "rssi"
|
def name = lastHop ? "lastHopRssi" : "rssi"
|
||||||
def valueString = part.split(":")[1].trim()
|
def valueString = part.split(":")[1].trim()
|
||||||
def value = (Integer.parseInt(valueString) - 128).toString()
|
def value = (Integer.parseInt(valueString) - 128).toString()
|
||||||
@@ -431,7 +407,7 @@ private Map getRssiResult(part, description, lastHop=false) {
|
|||||||
* Note: To make the signal strength indicator more accurate, we could combine
|
* Note: To make the signal strength indicator more accurate, we could combine
|
||||||
* LQI with RSSI.
|
* LQI with RSSI.
|
||||||
*/
|
*/
|
||||||
private Map getLqiResult(part, description, lastHop=false) {
|
private getLqiResult(part, description, lastHop=false) {
|
||||||
def name = lastHop ? "lastHopLqi" : "lqi"
|
def name = lastHop ? "lastHopLqi" : "lqi"
|
||||||
def valueString = part.split(":")[1].trim()
|
def valueString = part.split(":")[1].trim()
|
||||||
def percentageOf = 255
|
def percentageOf = 255
|
||||||
@@ -476,7 +452,6 @@ private Boolean isOrientationMessage(String description) {
|
|||||||
description ==~ /x:.*y:.*z:.*rssi:.*lqi:.*/
|
description ==~ /x:.*y:.*z:.*rssi:.*lqi:.*/
|
||||||
}
|
}
|
||||||
|
|
||||||
//Note: Not using this method anymore
|
|
||||||
private String parseName(String description) {
|
private String parseName(String description) {
|
||||||
if (isSupportedDescription(description)) {
|
if (isSupportedDescription(description)) {
|
||||||
return "contact"
|
return "contact"
|
||||||
|
|||||||
@@ -13,7 +13,6 @@
|
|||||||
* for the specific language governing permissions and limitations under the License.
|
* for the specific language governing permissions and limitations under the License.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
//DEPRECATED - Using the smartsense-multi-sensor.groovy DTH for this device. Users need to be moved before deleting this DTH
|
|
||||||
|
|
||||||
metadata {
|
metadata {
|
||||||
definition (name: "SmartSense Open/Closed Accelerometer Sensor", namespace: "smartthings", author: "SmartThings") {
|
definition (name: "SmartSense Open/Closed Accelerometer Sensor", namespace: "smartthings", author: "SmartThings") {
|
||||||
@@ -24,7 +23,8 @@
|
|||||||
capability "Refresh"
|
capability "Refresh"
|
||||||
capability "Temperature Measurement"
|
capability "Temperature Measurement"
|
||||||
command "enrollResponse"
|
command "enrollResponse"
|
||||||
|
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05,FC02", outClusters: "0019", manufacturer: "CentraLite", model: "3320"
|
||||||
|
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05,FC02", outClusters: "0019", manufacturer: "CentraLite", model: "3321"
|
||||||
}
|
}
|
||||||
|
|
||||||
simulator {
|
simulator {
|
||||||
@@ -225,8 +225,7 @@ def getTemperature(value) {
|
|||||||
|
|
||||||
def volts = rawValue / 10
|
def volts = rawValue / 10
|
||||||
def descriptionText
|
def descriptionText
|
||||||
if (rawValue == 0 || rawValue == 255) {}
|
if (volts > 3.5) {
|
||||||
else if (volts > 3.5) {
|
|
||||||
result.descriptionText = "${linkText} battery has too much power (${volts} volts)."
|
result.descriptionText = "${linkText} battery has too much power (${volts} volts)."
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|||||||
@@ -16,18 +16,17 @@
|
|||||||
|
|
||||||
metadata {
|
metadata {
|
||||||
definition (name: "SmartSense Open/Closed Sensor", namespace: "smartthings", author: "SmartThings") {
|
definition (name: "SmartSense Open/Closed Sensor", namespace: "smartthings", author: "SmartThings") {
|
||||||
capability "Battery"
|
capability "Battery"
|
||||||
capability "Configuration"
|
capability "Configuration"
|
||||||
capability "Contact Sensor"
|
capability "Contact Sensor"
|
||||||
capability "Refresh"
|
capability "Refresh"
|
||||||
capability "Temperature Measurement"
|
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-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 {
|
simulator {
|
||||||
@@ -220,8 +219,7 @@ private Map getBatteryResult(rawValue) {
|
|||||||
|
|
||||||
def volts = rawValue / 10
|
def volts = rawValue / 10
|
||||||
def descriptionText
|
def descriptionText
|
||||||
if (rawValue == 0 || rawValue == 255) {}
|
if (volts > 3.5) {
|
||||||
else if (volts > 3.5) {
|
|
||||||
result.descriptionText = "${linkText} battery has too much power (${volts} volts)."
|
result.descriptionText = "${linkText} battery has too much power (${volts} volts)."
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|||||||
@@ -196,8 +196,7 @@ private Map getBatteryResult(rawValue) {
|
|||||||
|
|
||||||
def volts = rawValue / 10
|
def volts = rawValue / 10
|
||||||
def descriptionText
|
def descriptionText
|
||||||
if (rawValue == 0 || rawValue == 255) {}
|
if (volts > 3.5) {
|
||||||
else if (volts > 3.5) {
|
|
||||||
result.descriptionText = "${linkText} battery has too much power (${volts} volts)."
|
result.descriptionText = "${linkText} battery has too much power (${volts} volts)."
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|||||||
@@ -32,15 +32,14 @@ metadata {
|
|||||||
attributeState("default", label:'${currentValue}', unit:"dF")
|
attributeState("default", label:'${currentValue}', unit:"dF")
|
||||||
}
|
}
|
||||||
tileAttribute("device.temperature", key: "VALUE_CONTROL") {
|
tileAttribute("device.temperature", key: "VALUE_CONTROL") {
|
||||||
attributeState("VALUE_UP", action: "tempUp")
|
attributeState("default", action: "setTemperature")
|
||||||
attributeState("VALUE_DOWN", action: "tempDown")
|
|
||||||
}
|
}
|
||||||
tileAttribute("device.humidity", key: "SECONDARY_CONTROL") {
|
tileAttribute("device.humidity", key: "SECONDARY_CONTROL") {
|
||||||
attributeState("default", label:'${currentValue}%', unit:"%")
|
attributeState("default", label:'${currentValue}%', unit:"%")
|
||||||
}
|
}
|
||||||
tileAttribute("device.thermostatOperatingState", key: "OPERATING_STATE") {
|
tileAttribute("device.thermostatOperatingState", key: "OPERATING_STATE") {
|
||||||
attributeState("idle", backgroundColor:"#44b621")
|
attributeState("idle", backgroundColor:"#44b621")
|
||||||
attributeState("heating", backgroundColor:"#ea5462")
|
attributeState("heating", backgroundColor:"#ffa81e")
|
||||||
attributeState("cooling", backgroundColor:"#269bd2")
|
attributeState("cooling", backgroundColor:"#269bd2")
|
||||||
}
|
}
|
||||||
tileAttribute("device.thermostatMode", key: "THERMOSTAT_MODE") {
|
tileAttribute("device.thermostatMode", key: "THERMOSTAT_MODE") {
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ metadata {
|
|||||||
attributeState "power", label:'${currentValue} W'
|
attributeState "power", label:'${currentValue} W'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
||||||
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
|
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
|
||||||
}
|
}
|
||||||
main "switch"
|
main "switch"
|
||||||
@@ -56,17 +56,21 @@ metadata {
|
|||||||
def parse(String description) {
|
def parse(String description) {
|
||||||
log.debug "description is $description"
|
log.debug "description is $description"
|
||||||
|
|
||||||
def event = zigbee.getEvent(description)
|
def resultMap = zigbee.getKnownDescription(description)
|
||||||
if (event) {
|
if (resultMap) {
|
||||||
log.info event
|
log.info resultMap
|
||||||
if (event.name == "power") {
|
if (resultMap.type == "update") {
|
||||||
|
log.info "$device updates: ${resultMap.value}"
|
||||||
|
}
|
||||||
|
else if (resultMap.type == "power") {
|
||||||
|
def powerValue
|
||||||
if (device.getDataValue("manufacturer") != "OSRAM") { //OSRAM devices do not reliably update power
|
if (device.getDataValue("manufacturer") != "OSRAM") { //OSRAM devices do not reliably update power
|
||||||
event.value = (event.value as Integer) / 10 //TODO: The divisor value needs to be set as part of configuration
|
powerValue = (resultMap.value as Integer)/10 //TODO: The divisor value needs to be set as part of configuration
|
||||||
sendEvent(event)
|
sendEvent(name: "power", value: powerValue)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
sendEvent(event)
|
sendEvent(name: resultMap.type, value: resultMap.value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ metadata {
|
|||||||
attributeState "level", action:"switch level.setLevel"
|
attributeState "level", action:"switch level.setLevel"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
||||||
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
|
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
|
||||||
}
|
}
|
||||||
main "switch"
|
main "switch"
|
||||||
@@ -51,9 +51,15 @@ metadata {
|
|||||||
def parse(String description) {
|
def parse(String description) {
|
||||||
log.debug "description is $description"
|
log.debug "description is $description"
|
||||||
|
|
||||||
def event = zigbee.getEvent(description)
|
def resultMap = zigbee.getKnownDescription(description)
|
||||||
if (event) {
|
if (resultMap) {
|
||||||
sendEvent(event)
|
log.info resultMap
|
||||||
|
if (resultMap.type == "update") {
|
||||||
|
log.info "$device updates: ${resultMap.value}"
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
sendEvent(name: resultMap.type, value: resultMap.value)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
log.warn "DID NOT PARSE MESSAGE for description : $description"
|
log.warn "DID NOT PARSE MESSAGE for description : $description"
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ metadata {
|
|||||||
state "turningOn", label:'${name}', action:"switch.off", icon:"st.switches.switch.on", backgroundColor:"#79b821", nextState:"turningOff"
|
state "turningOn", label:'${name}', action:"switch.off", icon:"st.switches.switch.on", backgroundColor:"#79b821", nextState:"turningOff"
|
||||||
state "turningOff", label:'${name}', action:"switch.on", icon:"st.switches.switch.off", backgroundColor:"#ffffff", nextState:"turningOn"
|
state "turningOff", label:'${name}', action:"switch.on", icon:"st.switches.switch.off", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||||
}
|
}
|
||||||
standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat") {
|
standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat") {
|
||||||
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
|
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
|
||||||
}
|
}
|
||||||
controlTile("rgbSelector", "device.color", "color", height: 3, width: 3, inactiveLabel: false) {
|
controlTile("rgbSelector", "device.color", "color", height: 3, width: 3, inactiveLabel: false) {
|
||||||
|
|||||||
@@ -23,14 +23,22 @@
|
|||||||
capability "Battery"
|
capability "Battery"
|
||||||
capability "Configuration"
|
capability "Configuration"
|
||||||
|
|
||||||
fingerprint profileId: "0104", inClusters: "0000,0001,0003,0004,0005,0009,0020,0101,0402,0B05,FDBD", outClusters: "000A,0019", 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",
|
||||||
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"
|
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_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",
|
||||||
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"
|
manufacturer: "Kwikset", model: "SMARTCODE_LEVER_5", deviceJoinName: "Kwikset 5-Button Lever"
|
||||||
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,0004,0005,0009,0020,0101,0402,0B05,FDBD", outClusters: "000A,0019",
|
||||||
fingerprint profileId: "0104", inClusters: "0000,0001,0003,0009,000A,0101,0020", outClusters: "000A,0019", manufacturer: "Yale", model: "YRD210 PB DB", deviceJoinName: "Yale Push Button Deadbolt Lock"
|
manufacturer: "Kwikset", model: "SMARTCODE_DEADBOLT_10", deviceJoinName: "Kwikset 10-Button Deadbolt"
|
||||||
fingerprint profileId: "0104", inClusters: "0000,0001,0003,0009,000A,0101,0020", outClusters: "000A,0019", manufacturer: "Yale", model: "YRD220/240 TSDB", deviceJoinName: "Yale Touch Screen Deadbolt Lock"
|
fingerprint profileId: "0104", inClusters: "0000,0001,0003,0004,0005,0009,0020,0101,0402,0B05,FDBD", outClusters: "000A,0019",
|
||||||
fingerprint profileId: "0104", inClusters: "0000,0001,0003,0009,000A,0101,0020", outClusters: "000A,0019", manufacturer: "Yale", model: "YRL210 PB LL", deviceJoinName: "Yale Push Button Lever Lock"
|
manufacturer: "Kwikset", model: "SMARTCODE_DEADBOLT_10T", deviceJoinName: "Kwikset 10-Button Touch Deadbolt"
|
||||||
|
fingerprint profileId: "0104", inClusters: "0000,0001,0003,0009,000A,0101,0020", outClusters: "000A,0019",
|
||||||
|
manufacturer: "Yale", model: "YRL220 TS LL", deviceJoinName: "Yale Touch Screen Lever Lock"
|
||||||
|
fingerprint profileId: "0104", inClusters: "0000,0001,0003,0009,000A,0101,0020", outClusters: "000A,0019",
|
||||||
|
manufacturer: "Yale", model: "YRD210 PB DB", deviceJoinName: "Yale Push Button Deadbolt Lock"
|
||||||
|
fingerprint profileId: "0104", inClusters: "0000,0001,0003,0009,000A,0101,0020", outClusters: "000A,0019",
|
||||||
|
manufacturer: "Yale", model: "YRD220/240 TSDB", deviceJoinName: "Yale Touch Screen Deadbolt Lock"
|
||||||
|
fingerprint profileId: "0104", inClusters: "0000,0001,0003,0009,000A,0101,0020", outClusters: "000A,0019",
|
||||||
|
manufacturer: "Yale", model: "YRL210 PB LL", deviceJoinName: "Yale Push Button Lever Lock"
|
||||||
}
|
}
|
||||||
|
|
||||||
tiles(scale: 2) {
|
tiles(scale: 2) {
|
||||||
@@ -52,7 +60,7 @@
|
|||||||
valueTile("battery", "device.battery", inactiveLabel:false, decoration:"flat", width:2, height:2) {
|
valueTile("battery", "device.battery", inactiveLabel:false, decoration:"flat", width:2, height:2) {
|
||||||
state "battery", label:'${currentValue}% battery', unit:""
|
state "battery", label:'${currentValue}% battery', unit:""
|
||||||
}
|
}
|
||||||
standardTile("refresh", "device.refresh", inactiveLabel:false, decoration:"flat", width:2, height:2) {
|
standardTile("refresh", "device.lock", inactiveLabel:false, decoration:"flat", width:2, height:2) {
|
||||||
state "default", label:'', action:"refresh.refresh", icon:"st.secondary.refresh"
|
state "default", label:'', action:"refresh.refresh", icon:"st.secondary.refresh"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,19 +91,32 @@ def uninstalled() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
def configure() {
|
def configure() {
|
||||||
|
/*
|
||||||
def cmds =
|
def cmds =
|
||||||
zigbee.configureReporting(CLUSTER_DOORLOCK, DOORLOCK_ATTR_LOCKSTATE,
|
zigbee.configSetup("${CLUSTER_DOORLOCK}", "${DOORLOCK_ATTR_LOCKSTATE}",
|
||||||
TYPE_ENUM8, 0, 3600, null) +
|
"${TYPE_ENUM8}", 0, 3600, "{01}") +
|
||||||
zigbee.configureReporting(CLUSTER_POWER, POWER_ATTR_BATTERY_PERCENTAGE_REMAINING,
|
zigbee.configSetup("${CLUSTER_POWER}", "${POWER_ATTR_BATTERY_PERCENTAGE_REMAINING}",
|
||||||
TYPE_U8, 600, 21600, 0x01)
|
"${TYPE_U8}", 600, 21600, "{01}")
|
||||||
|
*/
|
||||||
|
def zigbeeId = device.zigbeeId
|
||||||
|
def cmds =
|
||||||
|
[
|
||||||
|
"zdo bind 0x${device.deviceNetworkId} 0x${device.endpointId} 1 ${CLUSTER_DOORLOCK} {$zigbeeId} {}", "delay 200",
|
||||||
|
"zcl global send-me-a-report ${CLUSTER_DOORLOCK} ${DOORLOCK_ATTR_LOCKSTATE} ${TYPE_ENUM8} 0 3600 {01}", "delay 200",
|
||||||
|
"send 0x${device.deviceNetworkId} 1 0x${device.endpointId}", "delay 200",
|
||||||
|
|
||||||
|
"zdo bind 0x${device.deviceNetworkId} 0x${device.endpointId} 1 ${CLUSTER_POWER} {$zigbeeId} {}", "delay 200",
|
||||||
|
"zcl global send-me-a-report ${CLUSTER_POWER} ${POWER_ATTR_BATTERY_PERCENTAGE_REMAINING} ${TYPE_U8} 600 21600 {01}", "delay 200",
|
||||||
|
"send 0x${device.deviceNetworkId} 1 0x${device.endpointId}", "delay 200",
|
||||||
|
]
|
||||||
log.info "configure() --- cmds: $cmds"
|
log.info "configure() --- cmds: $cmds"
|
||||||
return cmds + refresh() // send refresh cmds as part of config
|
return cmds + refresh() // send refresh cmds as part of config
|
||||||
}
|
}
|
||||||
|
|
||||||
def refresh() {
|
def refresh() {
|
||||||
def cmds =
|
def cmds =
|
||||||
zigbee.readAttribute(CLUSTER_DOORLOCK, DOORLOCK_ATTR_LOCKSTATE) +
|
zigbee.refreshData("${CLUSTER_DOORLOCK}", "${DOORLOCK_ATTR_LOCKSTATE}") +
|
||||||
zigbee.readAttribute(CLUSTER_POWER, POWER_ATTR_BATTERY_PERCENTAGE_REMAINING)
|
zigbee.refreshData("${CLUSTER_POWER}", "${POWER_ATTR_BATTERY_PERCENTAGE_REMAINING}")
|
||||||
log.info "refresh() --- cmds: $cmds"
|
log.info "refresh() --- cmds: $cmds"
|
||||||
return cmds
|
return cmds
|
||||||
}
|
}
|
||||||
@@ -108,27 +129,34 @@ def parse(String description) {
|
|||||||
map = parseReportAttributeMessage(description)
|
map = parseReportAttributeMessage(description)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.debug "parse() --- Parse returned $map"
|
||||||
def result = map ? createEvent(map) : null
|
def result = map ? createEvent(map) : null
|
||||||
log.debug "parse() --- returned: $result"
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
// Lock capability commands
|
// Lock capability commands
|
||||||
def lock() {
|
def lock() {
|
||||||
def cmds = zigbee.command(CLUSTER_DOORLOCK, DOORLOCK_CMD_LOCK_DOOR)
|
//def cmds = zigbee.zigbeeCommand("${CLUSTER_DOORLOCK}", "${DOORLOCK_CMD_LOCK_DOOR}", "{}")
|
||||||
log.info "lock() -- cmds: $cmds"
|
//log.info "lock() -- cmds: $cmds"
|
||||||
return cmds
|
//return cmds
|
||||||
|
"st cmd 0x${device.deviceNetworkId} 0x${device.endpointId} ${CLUSTER_DOORLOCK} ${DOORLOCK_CMD_LOCK_DOOR} {}"
|
||||||
}
|
}
|
||||||
|
|
||||||
def unlock() {
|
def unlock() {
|
||||||
def cmds = zigbee.command(CLUSTER_DOORLOCK, DOORLOCK_CMD_UNLOCK_DOOR)
|
//def cmds = zigbee.zigbeeCommand("${CLUSTER_DOORLOCK}", "${DOORLOCK_CMD_UNLOCK_DOOR}", "{}")
|
||||||
log.info "unlock() -- cmds: $cmds"
|
//log.info "unlock() -- cmds: $cmds"
|
||||||
return cmds
|
//return cmds
|
||||||
|
"st cmd 0x${device.deviceNetworkId} 0x${device.endpointId} ${CLUSTER_DOORLOCK} ${DOORLOCK_CMD_UNLOCK_DOOR} {}"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Private methods
|
// Private methods
|
||||||
private Map parseReportAttributeMessage(String description) {
|
private Map parseReportAttributeMessage(String description) {
|
||||||
|
log.trace "parseReportAttributeMessage() --- description: $description"
|
||||||
|
|
||||||
Map descMap = zigbee.parseDescriptionAsMap(description)
|
Map descMap = zigbee.parseDescriptionAsMap(description)
|
||||||
|
|
||||||
|
log.debug "parseReportAttributeMessage() --- descMap: $descMap"
|
||||||
|
|
||||||
Map resultMap = [:]
|
Map resultMap = [:]
|
||||||
if (descMap.clusterInt == CLUSTER_POWER && descMap.attrInt == POWER_ATTR_BATTERY_PERCENTAGE_REMAINING) {
|
if (descMap.clusterInt == CLUSTER_POWER && descMap.attrInt == POWER_ATTR_BATTERY_PERCENTAGE_REMAINING) {
|
||||||
resultMap.name = "battery"
|
resultMap.name = "battery"
|
||||||
@@ -136,24 +164,18 @@ private Map parseReportAttributeMessage(String description) {
|
|||||||
if (device.getDataValue("manufacturer") == "Yale") { //Handling issue with Yale locks incorrect battery reporting
|
if (device.getDataValue("manufacturer") == "Yale") { //Handling issue with Yale locks incorrect battery reporting
|
||||||
resultMap.value = Integer.parseInt(descMap.value, 16)
|
resultMap.value = Integer.parseInt(descMap.value, 16)
|
||||||
}
|
}
|
||||||
|
log.info "parseReportAttributeMessage() --- battery: ${resultMap.value}"
|
||||||
}
|
}
|
||||||
else if (descMap.clusterInt == CLUSTER_DOORLOCK && descMap.attrInt == DOORLOCK_ATTR_LOCKSTATE) {
|
else if (descMap.clusterInt == CLUSTER_DOORLOCK && descMap.attrInt == DOORLOCK_ATTR_LOCKSTATE) {
|
||||||
def value = Integer.parseInt(descMap.value, 16)
|
def value = Integer.parseInt(descMap.value, 16)
|
||||||
def linkText = getLinkText(device)
|
|
||||||
resultMap.name = "lock"
|
resultMap.name = "lock"
|
||||||
if (value == 0) {
|
resultMap.putAll([0:["value":"unknown",
|
||||||
resultMap.value = "unknown"
|
"descriptionText":"Not fully locked"],
|
||||||
resultMap.descriptionText = "${linkText} is not fully locked"
|
1:["value":"locked"],
|
||||||
} else if (value == 1) {
|
2:["value":"unlocked"]].get(value,
|
||||||
resultMap.value = "locked"
|
["value":"unknown",
|
||||||
resultMap.descriptionText = "${linkText} is locked"
|
"descriptionText":"Unknown lock state"]))
|
||||||
} else if (value == 2) {
|
log.info "parseReportAttributeMessage() --- lock: ${resultMap.value}"
|
||||||
resultMap.value = "unlocked"
|
|
||||||
resultMap.descriptionText = "${linkText} is unlocked"
|
|
||||||
} else {
|
|
||||||
resultMap.value = "unknown"
|
|
||||||
resultMap.descriptionText = "${linkText} is in unknown lock state"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
log.debug "parseReportAttributeMessage() --- ignoring attribute"
|
log.debug "parseReportAttributeMessage() --- ignoring attribute"
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ metadata {
|
|||||||
valueTile("colorTemp", "device.colorTemperature", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
valueTile("colorTemp", "device.colorTemperature", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
||||||
state "colorTemperature", label: '${currentValue} K'
|
state "colorTemperature", label: '${currentValue} K'
|
||||||
}
|
}
|
||||||
standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
||||||
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
|
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ metadata {
|
|||||||
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0702"
|
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: "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, 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) {
|
tiles(scale: 2) {
|
||||||
@@ -40,7 +39,7 @@ metadata {
|
|||||||
attributeState "power", label:'${currentValue} W'
|
attributeState "power", label:'${currentValue} W'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
||||||
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
|
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
|
||||||
}
|
}
|
||||||
main "switch"
|
main "switch"
|
||||||
@@ -51,15 +50,22 @@ metadata {
|
|||||||
// Parse incoming device messages to generate events
|
// Parse incoming device messages to generate events
|
||||||
def parse(String description) {
|
def parse(String description) {
|
||||||
log.debug "description is $description"
|
log.debug "description is $description"
|
||||||
def event = zigbee.getEvent(description)
|
|
||||||
if (event) {
|
def resultMap = zigbee.getKnownDescription(description)
|
||||||
if (event.name == "power") {
|
if (resultMap) {
|
||||||
|
log.info resultMap
|
||||||
|
if (resultMap.type == "update") {
|
||||||
|
log.info "$device updates: ${resultMap.value}"
|
||||||
|
}
|
||||||
|
else if (resultMap.type == "power") {
|
||||||
def powerValue
|
def powerValue
|
||||||
powerValue = (event.value as Integer)/10 //TODO: The divisor value needs to be set as part of configuration
|
if (device.getDataValue("manufacturer") != "OSRAM") { //OSRAM devices do not reliably update power
|
||||||
sendEvent(name: "power", value: powerValue)
|
powerValue = (resultMap.value as Integer)/10 //TODO: The divisor value needs to be set as part of configuration
|
||||||
|
sendEvent(name: "power", value: powerValue)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
sendEvent(event)
|
sendEvent(name: resultMap.type, value: resultMap.value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ metadata {
|
|||||||
attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.switches.light.off", backgroundColor:"#ffffff", nextState:"turningOn"
|
attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.switches.light.off", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
||||||
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
|
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
|
||||||
}
|
}
|
||||||
main "switch"
|
main "switch"
|
||||||
@@ -53,9 +53,16 @@ metadata {
|
|||||||
// Parse incoming device messages to generate events
|
// Parse incoming device messages to generate events
|
||||||
def parse(String description) {
|
def parse(String description) {
|
||||||
log.debug "description is $description"
|
log.debug "description is $description"
|
||||||
def event = zigbee.getEvent(description)
|
|
||||||
if (event) {
|
def resultMap = zigbee.getKnownDescription(description)
|
||||||
sendEvent(event)
|
if (resultMap) {
|
||||||
|
log.info resultMap
|
||||||
|
if (resultMap.type == "update") {
|
||||||
|
log.info "$device updates: ${resultMap.value}"
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
sendEvent(name: resultMap.type, value: resultMap.value)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
log.warn "DID NOT PARSE MESSAGE for description : $description"
|
log.warn "DID NOT PARSE MESSAGE for description : $description"
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ metadata {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
||||||
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
|
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,9 +73,16 @@ metadata {
|
|||||||
// Parse incoming device messages to generate events
|
// Parse incoming device messages to generate events
|
||||||
def parse(String description) {
|
def parse(String description) {
|
||||||
log.debug "description is $description"
|
log.debug "description is $description"
|
||||||
def event = zigbee.getEvent(description)
|
|
||||||
if (event) {
|
def finalResult = zigbee.getKnownDescription(description)
|
||||||
sendEvent(event)
|
if (finalResult) {
|
||||||
|
log.info finalResult
|
||||||
|
if (finalResult.type == "update") {
|
||||||
|
log.info "$device updates: ${finalResult.value}"
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
sendEvent(name: finalResult.type, value: finalResult.value)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
log.warn "DID NOT PARSE MESSAGE for description : $description"
|
log.warn "DID NOT PARSE MESSAGE for description : $description"
|
||||||
|
|||||||
@@ -41,7 +41,6 @@ metadata {
|
|||||||
attributeState "locked", label:'locked', action:"lock.unlock", icon:"st.locks.lock.locked", backgroundColor:"#79b821", nextState:"unlocking"
|
attributeState "locked", label:'locked', action:"lock.unlock", icon:"st.locks.lock.locked", backgroundColor:"#79b821", nextState:"unlocking"
|
||||||
attributeState "unlocked", label:'unlocked', action:"lock.lock", icon:"st.locks.lock.unlocked", backgroundColor:"#ffffff", nextState:"locking"
|
attributeState "unlocked", label:'unlocked', action:"lock.lock", icon:"st.locks.lock.unlocked", backgroundColor:"#ffffff", nextState:"locking"
|
||||||
attributeState "unknown", label:"unknown", action:"lock.lock", icon:"st.locks.lock.unknown", backgroundColor:"#ffffff", nextState:"locking"
|
attributeState "unknown", label:"unknown", action:"lock.lock", icon:"st.locks.lock.unknown", backgroundColor:"#ffffff", nextState:"locking"
|
||||||
attributeState "jammed", label:"jammed", action:"lock.lock", icon:"st.locks.lock.unknown", backgroundColor:"#ffffff", nextState:"locking"
|
|
||||||
attributeState "locking", label:'locking', icon:"st.locks.lock.locked", backgroundColor:"#79b821"
|
attributeState "locking", label:'locking', icon:"st.locks.lock.locked", backgroundColor:"#79b821"
|
||||||
attributeState "unlocking", label:'unlocking', icon:"st.locks.lock.unlocked", backgroundColor:"#ffffff"
|
attributeState "unlocking", label:'unlocking', icon:"st.locks.lock.unlocked", backgroundColor:"#ffffff"
|
||||||
}
|
}
|
||||||
@@ -131,7 +130,7 @@ def zwaveEvent(DoorLockOperationReport cmd) {
|
|||||||
if (cmd.doorLockMode == 0xFF) {
|
if (cmd.doorLockMode == 0xFF) {
|
||||||
map.value = "locked"
|
map.value = "locked"
|
||||||
} else if (cmd.doorLockMode >= 0x40) {
|
} else if (cmd.doorLockMode >= 0x40) {
|
||||||
map.value = "unknown" // XXX: Jammed?
|
map.value = "unknown"
|
||||||
} else if (cmd.doorLockMode & 1) {
|
} else if (cmd.doorLockMode & 1) {
|
||||||
map.value = "unlocked with timeout"
|
map.value = "unlocked with timeout"
|
||||||
} else {
|
} else {
|
||||||
@@ -181,7 +180,7 @@ def zwaveEvent(physicalgraph.zwave.commands.alarmv2.AlarmReport cmd) {
|
|||||||
map = [ name: "lock", value: "unknown", descriptionText: "$device.displayName was not locked fully" ]
|
map = [ name: "lock", value: "unknown", descriptionText: "$device.displayName was not locked fully" ]
|
||||||
break
|
break
|
||||||
case 0xB:
|
case 0xB:
|
||||||
map = [ name: "lock", value: "jammed", descriptionText: "$device.displayName is jammed", displayed: true, eventType: "ALERT" ]
|
map = [ name: "lock", value: "unknown", descriptionText: "$device.displayName is jammed" ]
|
||||||
break
|
break
|
||||||
case 0xC:
|
case 0xC:
|
||||||
map = [ name: "codeChanged", value: "all", descriptionText: "$device.displayName: all user codes deleted", isStateChange: true ]
|
map = [ name: "codeChanged", value: "all", descriptionText: "$device.displayName: all user codes deleted", isStateChange: true ]
|
||||||
@@ -267,7 +266,7 @@ def zwaveEvent(physicalgraph.zwave.commands.alarmv2.AlarmReport cmd) {
|
|||||||
case 17:
|
case 17:
|
||||||
case 23:
|
case 23:
|
||||||
case 26:
|
case 26:
|
||||||
map = [ name: "lock", value: "jammed", descriptionText: "$device.displayName bolt is jammed", displayed: true, eventType: "ALERT" ]
|
map = [ name: "lock", value: "unknown", descriptionText: "$device.displayName bolt is jammed" ]
|
||||||
break
|
break
|
||||||
case 13:
|
case 13:
|
||||||
map = [ name: "codeChanged", value: cmd.alarmLevel, descriptionText: "$device.displayName code $cmd.alarmLevel was added", isStateChange: true ]
|
map = [ name: "codeChanged", value: cmd.alarmLevel, descriptionText: "$device.displayName code $cmd.alarmLevel was added", isStateChange: true ]
|
||||||
@@ -276,7 +275,6 @@ def zwaveEvent(physicalgraph.zwave.commands.alarmv2.AlarmReport cmd) {
|
|||||||
case 32:
|
case 32:
|
||||||
map = [ name: "codeChanged", value: "all", descriptionText: "$device.displayName: all user codes deleted", isStateChange: true ]
|
map = [ name: "codeChanged", value: "all", descriptionText: "$device.displayName: all user codes deleted", isStateChange: true ]
|
||||||
allCodesDeleted()
|
allCodesDeleted()
|
||||||
break
|
|
||||||
case 33:
|
case 33:
|
||||||
map = [ name: "codeReport", value: cmd.alarmLevel, data: [ code: "" ], isStateChange: true ]
|
map = [ name: "codeReport", value: cmd.alarmLevel, data: [ code: "" ], isStateChange: true ]
|
||||||
map.descriptionText = "$device.displayName code $cmd.alarmLevel was deleted"
|
map.descriptionText = "$device.displayName code $cmd.alarmLevel was deleted"
|
||||||
@@ -343,14 +341,14 @@ def zwaveEvent(UserCodeReport cmd) {
|
|||||||
map = [ name: "codeReport", value: cmd.userIdentifier, data: [ code: code ] ]
|
map = [ name: "codeReport", value: cmd.userIdentifier, data: [ code: code ] ]
|
||||||
map.descriptionText = "$device.displayName code $cmd.userIdentifier is set"
|
map.descriptionText = "$device.displayName code $cmd.userIdentifier is set"
|
||||||
map.displayed = (cmd.userIdentifier != state.requestCode && cmd.userIdentifier != state.pollCode)
|
map.displayed = (cmd.userIdentifier != state.requestCode && cmd.userIdentifier != state.pollCode)
|
||||||
map.isStateChange = true
|
map.isStateChange = (code != decrypt(state[name]))
|
||||||
}
|
}
|
||||||
result << createEvent(map)
|
result << createEvent(map)
|
||||||
} else {
|
} else {
|
||||||
map = [ name: "codeReport", value: cmd.userIdentifier, data: [ code: "" ] ]
|
map = [ name: "codeReport", value: cmd.userIdentifier, data: [ code: "" ] ]
|
||||||
if (state.blankcodes && state["reset$name"]) { // we deleted this code so we can tell that our new code gets set
|
if (state.blankcodes && state["reset$name"]) { // we deleted this code so we can tell that our new code gets set
|
||||||
map.descriptionText = "$device.displayName code $cmd.userIdentifier was reset"
|
map.descriptionText = "$device.displayName code $cmd.userIdentifier was reset"
|
||||||
map.displayed = map.isStateChange = true
|
map.displayed = map.isStateChange = false
|
||||||
result << createEvent(map)
|
result << createEvent(map)
|
||||||
state["set$name"] = state["reset$name"]
|
state["set$name"] = state["reset$name"]
|
||||||
result << response(setCode(cmd.userIdentifier, state["reset$name"]))
|
result << response(setCode(cmd.userIdentifier, state["reset$name"]))
|
||||||
@@ -362,7 +360,7 @@ def zwaveEvent(UserCodeReport cmd) {
|
|||||||
map.descriptionText = "$device.displayName code $cmd.userIdentifier is not set"
|
map.descriptionText = "$device.displayName code $cmd.userIdentifier is not set"
|
||||||
}
|
}
|
||||||
map.displayed = (cmd.userIdentifier != state.requestCode && cmd.userIdentifier != state.pollCode)
|
map.displayed = (cmd.userIdentifier != state.requestCode && cmd.userIdentifier != state.pollCode)
|
||||||
map.isStateChange = true
|
map.isStateChange = state[name] as Boolean
|
||||||
result << createEvent(map)
|
result << createEvent(map)
|
||||||
}
|
}
|
||||||
code = ""
|
code = ""
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ def parse(String description) {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
def sensorValueEvent(value) {
|
def sensorValueEvent(Short value) {
|
||||||
if (value) {
|
if (value) {
|
||||||
createEvent(name: "motion", value: "active", descriptionText: "$device.displayName detected motion")
|
createEvent(name: "motion", value: "active", descriptionText: "$device.displayName detected motion")
|
||||||
} else {
|
} else {
|
||||||
@@ -94,24 +94,24 @@ def zwaveEvent(physicalgraph.zwave.commands.notificationv3.NotificationReport cm
|
|||||||
{
|
{
|
||||||
def result = []
|
def result = []
|
||||||
if (cmd.notificationType == 0x07) {
|
if (cmd.notificationType == 0x07) {
|
||||||
if (cmd.v1AlarmType == 0x07) { // special case for nonstandard messages from Monoprice ensors
|
if (cmd.event == 0x01 || cmd.event == 0x02) {
|
||||||
result << sensorValueEvent(cmd.v1AlarmLevel)
|
|
||||||
} else if (cmd.event == 0x01 || cmd.event == 0x02 || cmd.event == 0x07 || cmd.event == 0x08) {
|
|
||||||
result << sensorValueEvent(1)
|
result << sensorValueEvent(1)
|
||||||
} else if (cmd.event == 0x00) {
|
|
||||||
result << sensorValueEvent(0)
|
|
||||||
} else if (cmd.event == 0x03) {
|
} else if (cmd.event == 0x03) {
|
||||||
result << createEvent(name: "tamper", value: "detected", descriptionText: "$device.displayName covering was removed", isStateChange: true)
|
result << createEvent(descriptionText: "$device.displayName covering was removed", isStateChange: true)
|
||||||
result << response(zwave.batteryV1.batteryGet())
|
result << response(zwave.wakeUpV1.wakeUpIntervalSet(seconds:4*3600, nodeid:zwaveHubNodeId))
|
||||||
|
if(!state.MSR) result << response(zwave.manufacturerSpecificV2.manufacturerSpecificGet())
|
||||||
} else if (cmd.event == 0x05 || cmd.event == 0x06) {
|
} else if (cmd.event == 0x05 || cmd.event == 0x06) {
|
||||||
result << createEvent(descriptionText: "$device.displayName detected glass breakage", isStateChange: true)
|
result << createEvent(descriptionText: "$device.displayName detected glass breakage", isStateChange: true)
|
||||||
|
} else if (cmd.event == 0x07) {
|
||||||
|
if(!state.MSR) result << response(zwave.manufacturerSpecificV2.manufacturerSpecificGet())
|
||||||
|
result << sensorValueEvent(1)
|
||||||
}
|
}
|
||||||
} else if (cmd.notificationType) {
|
} else if (cmd.notificationType) {
|
||||||
def text = "Notification $cmd.notificationType: event ${([cmd.event] + cmd.eventParameter).join(", ")}"
|
def text = "Notification $cmd.notificationType: event ${([cmd.event] + cmd.eventParameter).join(", ")}"
|
||||||
result << createEvent(name: "notification$cmd.notificationType", value: "$cmd.event", descriptionText: text, isStateChange: true, displayed: false)
|
result << createEvent(name: "notification$cmd.notificationType", value: "$cmd.event", descriptionText: text, displayed: false)
|
||||||
} else {
|
} else {
|
||||||
def value = cmd.v1AlarmLevel == 255 ? "active" : cmd.v1AlarmLevel ?: "inactive"
|
def value = cmd.v1AlarmLevel == 255 ? "active" : cmd.v1AlarmLevel ?: "inactive"
|
||||||
result << createEvent(name: "alarm $cmd.v1AlarmType", value: value, isStateChange: true, displayed: false)
|
result << createEvent(name: "alarm $cmd.v1AlarmType", value: value, displayed: false)
|
||||||
}
|
}
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
@@ -119,10 +119,6 @@ def zwaveEvent(physicalgraph.zwave.commands.notificationv3.NotificationReport cm
|
|||||||
def zwaveEvent(physicalgraph.zwave.commands.wakeupv1.WakeUpNotification cmd)
|
def zwaveEvent(physicalgraph.zwave.commands.wakeupv1.WakeUpNotification cmd)
|
||||||
{
|
{
|
||||||
def result = [createEvent(descriptionText: "${device.displayName} woke up", isStateChange: false)]
|
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) {
|
if (!state.lastbat || (new Date().time) - state.lastbat > 53*60*60*1000) {
|
||||||
result << response(zwave.batteryV1.batteryGet())
|
result << response(zwave.batteryV1.batteryGet())
|
||||||
result << response("delay 1200")
|
result << response("delay 1200")
|
||||||
@@ -183,4 +179,4 @@ def zwaveEvent(physicalgraph.zwave.commands.manufacturerspecificv2.ManufacturerS
|
|||||||
|
|
||||||
result << createEvent(descriptionText: "$device.displayName MSR: $msr", isStateChange: false)
|
result << createEvent(descriptionText: "$device.displayName MSR: $msr", isStateChange: false)
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,44 +61,37 @@ def parse(String description) {
|
|||||||
zwaveEvent(cmd, results)
|
zwaveEvent(cmd, results)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
log.debug "'$description' parsed to ${results.inspect()}"
|
// log.debug "\"$description\" parsed to ${results.inspect()}"
|
||||||
return results
|
return results
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def createSmokeOrCOEvents(name, results) {
|
def createSmokeOrCOEvents(name, results) {
|
||||||
def text = null
|
def text = null
|
||||||
switch (name) {
|
if (name == "smoke") {
|
||||||
case "smoke":
|
text = "$device.displayName smoke was detected!"
|
||||||
text = "$device.displayName smoke was detected!"
|
// these are displayed:false because the composite event is the one we want to see in the app
|
||||||
// these are displayed:false because the composite event is the one we want to see in the app
|
results << createEvent(name: "smoke", value: "detected", descriptionText: text, displayed: false)
|
||||||
results << createEvent(name: "smoke", value: "detected", descriptionText: text, displayed: false)
|
} else if (name == "carbonMonoxide") {
|
||||||
break
|
text = "$device.displayName carbon monoxide was detected!"
|
||||||
case "carbonMonoxide":
|
results << createEvent(name: "carbonMonoxide", value: "detected", descriptionText: text, displayed: false)
|
||||||
text = "$device.displayName carbon monoxide was detected!"
|
} else if (name == "tested") {
|
||||||
results << createEvent(name: "carbonMonoxide", value: "detected", descriptionText: text, displayed: false)
|
text = "$device.displayName was tested"
|
||||||
break
|
results << createEvent(name: "smoke", value: "tested", descriptionText: text, displayed: false)
|
||||||
case "tested":
|
results << createEvent(name: "carbonMonoxide", value: "tested", descriptionText: text, displayed: false)
|
||||||
text = "$device.displayName was tested"
|
} else if (name == "smokeClear") {
|
||||||
results << createEvent(name: "smoke", value: "tested", descriptionText: text, displayed: false)
|
text = "$device.displayName smoke is clear"
|
||||||
results << createEvent(name: "carbonMonoxide", value: "tested", descriptionText: text, displayed: false)
|
results << createEvent(name: "smoke", value: "clear", descriptionText: text, displayed: false)
|
||||||
break
|
name = "clear"
|
||||||
case "smokeClear":
|
} else if (name == "carbonMonoxideClear") {
|
||||||
text = "$device.displayName smoke is clear"
|
text = "$device.displayName carbon monoxide is clear"
|
||||||
results << createEvent(name: "smoke", value: "clear", descriptionText: text, displayed: false)
|
results << createEvent(name: "carbonMonoxide", value: "clear", descriptionText: text, displayed: false)
|
||||||
name = "clear"
|
name = "clear"
|
||||||
break
|
} else if (name == "testClear") {
|
||||||
case "carbonMonoxideClear":
|
text = "$device.displayName smoke is clear"
|
||||||
text = "$device.displayName carbon monoxide is clear"
|
results << createEvent(name: "smoke", value: "clear", descriptionText: text, displayed: false)
|
||||||
results << createEvent(name: "carbonMonoxide", value: "clear", descriptionText: text, displayed: false)
|
results << createEvent(name: "carbonMonoxide", value: "clear", displayed: false)
|
||||||
name = "clear"
|
name = "clear"
|
||||||
break
|
|
||||||
case "testClear":
|
|
||||||
text = "$device.displayName test cleared"
|
|
||||||
results << createEvent(name: "smoke", value: "clear", descriptionText: text, displayed: false)
|
|
||||||
results << createEvent(name: "carbonMonoxide", value: "clear", displayed: false)
|
|
||||||
name = "clear"
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
// This composite event is used for updating the tile
|
// This composite event is used for updating the tile
|
||||||
results << createEvent(name: "alarmState", value: name, descriptionText: text)
|
results << createEvent(name: "alarmState", value: name, descriptionText: text)
|
||||||
@@ -124,10 +117,8 @@ def zwaveEvent(physicalgraph.zwave.commands.alarmv2.AlarmReport cmd, results) {
|
|||||||
createSmokeOrCOEvents(cmd.alarmLevel ? "tested" : "testClear", results)
|
createSmokeOrCOEvents(cmd.alarmLevel ? "tested" : "testClear", results)
|
||||||
break
|
break
|
||||||
case 13: // sent every hour -- not sure what this means, just a wake up notification?
|
case 13: // sent every hour -- not sure what this means, just a wake up notification?
|
||||||
if (cmd.alarmLevel == 255) {
|
if (cmd.alarmLevel != 255) {
|
||||||
results << createEvent(descriptionText: "$device.displayName checked in", isStateChange: false)
|
results << createEvent(descriptionText: "$device.displayName code 13 is $cmd.alarmLevel", displayed: true)
|
||||||
} else {
|
|
||||||
results << createEvent(descriptionText: "$device.displayName code 13 is $cmd.alarmLevel", isStateChange:true, displayed:false)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear smoke in case they pulled batteries and we missed the clear msg
|
// Clear smoke in case they pulled batteries and we missed the clear msg
|
||||||
@@ -136,8 +127,9 @@ def zwaveEvent(physicalgraph.zwave.commands.alarmv2.AlarmReport cmd, results) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check battery if we don't have a recent battery event
|
// Check battery if we don't have a recent battery event
|
||||||
if (!state.lastbatt || (now() - state.lastbatt) >= 48*60*60*1000) {
|
def prevBattery = device.currentState("battery")
|
||||||
results << response(zwave.batteryV1.batteryGet())
|
if (!prevBattery || (new Date().time - prevBattery.date.time)/60000 >= 60 * 53) {
|
||||||
|
results << new physicalgraph.device.HubAction(zwave.batteryV1.batteryGet().format())
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
@@ -166,17 +158,12 @@ def zwaveEvent(physicalgraph.zwave.commands.sensoralarmv1.SensorAlarmReport cmd,
|
|||||||
}
|
}
|
||||||
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.wakeupv1.WakeUpNotification cmd, results) {
|
def zwaveEvent(physicalgraph.zwave.commands.wakeupv1.WakeUpNotification cmd, results) {
|
||||||
|
results << new physicalgraph.device.HubAction(zwave.wakeUpV1.wakeUpNoMoreInformation().format())
|
||||||
results << createEvent(descriptionText: "$device.displayName woke up", isStateChange: false)
|
results << createEvent(descriptionText: "$device.displayName woke up", isStateChange: false)
|
||||||
if (!state.lastbatt || (now() - state.lastbatt) >= 56*60*60*1000) {
|
|
||||||
results << response(zwave.batteryV1.batteryGet(), "delay 2000", zwave.wakeUpV1.wakeUpNoMoreInformation())
|
|
||||||
} else {
|
|
||||||
results << response(zwave.wakeUpV1.wakeUpNoMoreInformation())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.batteryv1.BatteryReport cmd, results) {
|
def zwaveEvent(physicalgraph.zwave.commands.batteryv1.BatteryReport cmd, results) {
|
||||||
def map = [ name: "battery", unit: "%", isStateChange: true ]
|
def map = [ name: "battery", unit: "%" ]
|
||||||
state.lastbatt = now()
|
|
||||||
if (cmd.batteryLevel == 0xFF) {
|
if (cmd.batteryLevel == 0xFF) {
|
||||||
map.value = 1
|
map.value = 1
|
||||||
map.descriptionText = "$device.displayName battery is low!"
|
map.descriptionText = "$device.displayName battery is low!"
|
||||||
|
|||||||
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
6
gradle/wrapper/gradle-wrapper.properties
vendored
6
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,6 +0,0 @@
|
|||||||
#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
160
gradlew
vendored
@@ -1,160 +0,0 @@
|
|||||||
#!/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
90
gradlew.bat
vendored
@@ -1,90 +0,0 @@
|
|||||||
@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
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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'
|
|
||||||
@@ -1,147 +0,0 @@
|
|||||||
/**
|
|
||||||
* BeaconThing Manager
|
|
||||||
*
|
|
||||||
* Copyright 2015 obycode
|
|
||||||
*
|
|
||||||
* 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: "BeaconThings Manager",
|
|
||||||
namespace: "com.obycode",
|
|
||||||
author: "obycode",
|
|
||||||
description: "SmartApp to interact with the BeaconThings iOS app. Use this app to integrate iBeacons into your smart home.",
|
|
||||||
category: "Convenience",
|
|
||||||
iconUrl: "http://beaconthingsapp.com/images/Icon-60.png",
|
|
||||||
iconX2Url: "http://beaconthingsapp.com/images/Icon-60@2x.png",
|
|
||||||
iconX3Url: "http://beaconthingsapp.com/images/Icon-60@3x.png",
|
|
||||||
oauth: true)
|
|
||||||
|
|
||||||
|
|
||||||
preferences {
|
|
||||||
section("Allow BeaconThings to talk to your home") {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def installed() {
|
|
||||||
log.debug "Installed with settings: ${settings}"
|
|
||||||
|
|
||||||
initialize()
|
|
||||||
}
|
|
||||||
|
|
||||||
def initialize() {
|
|
||||||
}
|
|
||||||
|
|
||||||
def uninstalled() {
|
|
||||||
removeChildDevices(getChildDevices())
|
|
||||||
}
|
|
||||||
|
|
||||||
mappings {
|
|
||||||
path("/beacons") {
|
|
||||||
action: [
|
|
||||||
DELETE: "clearBeacons",
|
|
||||||
POST: "addBeacon"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
path("/beacons/:id") {
|
|
||||||
action: [
|
|
||||||
PUT: "updateBeacon",
|
|
||||||
DELETE: "deleteBeacon"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void clearBeacons() {
|
|
||||||
removeChildDevices(getChildDevices())
|
|
||||||
}
|
|
||||||
|
|
||||||
void addBeacon() {
|
|
||||||
def beacon = request.JSON?.beacon
|
|
||||||
if (beacon) {
|
|
||||||
def beaconId = "BeaconThings"
|
|
||||||
if (beacon.major) {
|
|
||||||
beaconId = "$beaconId-${beacon.major}"
|
|
||||||
if (beacon.minor) {
|
|
||||||
beaconId = "$beaconId-${beacon.minor}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
log.debug "adding beacon $beaconId"
|
|
||||||
def d = addChildDevice("com.obycode", "BeaconThing", beaconId, null, [label:beacon.name, name:"BeaconThing", completedSetup: true])
|
|
||||||
log.debug "addChildDevice returned $d"
|
|
||||||
|
|
||||||
if (beacon.present) {
|
|
||||||
d.arrive(beacon.present)
|
|
||||||
}
|
|
||||||
else if (beacon.presence) {
|
|
||||||
d.setPresence(beacon.presence)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void updateBeacon() {
|
|
||||||
log.debug "updating beacon ${params.id}"
|
|
||||||
def beaconDevice = getChildDevice(params.id)
|
|
||||||
// def children = getChildDevices()
|
|
||||||
// def beaconDevice = children.find{ d -> d.deviceNetworkId == "${params.id}" }
|
|
||||||
if (!beaconDevice) {
|
|
||||||
log.debug "Beacon not found directly"
|
|
||||||
def children = getChildDevices()
|
|
||||||
beaconDevice = children.find{ d -> d.deviceNetworkId == "${params.id}" }
|
|
||||||
if (!beaconDevice) {
|
|
||||||
log.debug "Beacon not found in list either"
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// This could be just updating the presence
|
|
||||||
def presence = request.JSON?.presence
|
|
||||||
if (presence) {
|
|
||||||
log.debug "Setting ${beaconDevice.label} to $presence"
|
|
||||||
beaconDevice.setPresence(presence)
|
|
||||||
}
|
|
||||||
|
|
||||||
// It could be someone arriving
|
|
||||||
def arrived = request.JSON?.arrived
|
|
||||||
if (arrived) {
|
|
||||||
log.debug "$arrived arrived at ${beaconDevice.label}"
|
|
||||||
beaconDevice.arrived(arrived)
|
|
||||||
}
|
|
||||||
|
|
||||||
// It could be someone left
|
|
||||||
def left = request.JSON?.left
|
|
||||||
if (left) {
|
|
||||||
log.debug "$left left ${beaconDevice.label}"
|
|
||||||
beaconDevice.left(left)
|
|
||||||
}
|
|
||||||
|
|
||||||
// or it could be updating the name
|
|
||||||
def beacon = request.JSON?.beacon
|
|
||||||
if (beacon) {
|
|
||||||
beaconDevice.label = beacon.name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void deleteBeacon() {
|
|
||||||
log.debug "deleting beacon ${params.id}"
|
|
||||||
deleteChildDevice(params.id)
|
|
||||||
// def children = getChildDevices()
|
|
||||||
// def beaconDevice = children.find{ d -> d.deviceNetworkId == "${params.id}" }
|
|
||||||
// if (beaconDevice) {
|
|
||||||
// deleteChildDevice(beaconDevice.deviceNetworkId)
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
private removeChildDevices(delete) {
|
|
||||||
delete.each {
|
|
||||||
deleteChildDevice(it.deviceNetworkId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -67,11 +67,11 @@ mappings {
|
|||||||
def listAllDevices() {
|
def listAllDevices() {
|
||||||
def resp = []
|
def resp = []
|
||||||
switches.each {
|
switches.each {
|
||||||
resp << [name: it.name, label: it.label, value: it.currentValue("switch"), type: "switch", id: it.id, hub: it.hub?.name]
|
resp << [name: it.name, label: it.label, value: it.currentValue("switch"), type: "switch", id: it.id, hub: it.hub.name]
|
||||||
}
|
}
|
||||||
|
|
||||||
locks.each {
|
locks.each {
|
||||||
resp << [name: it.name, label: it.label, value: it.currentValue("lock"), type: "lock", id: it.id, hub: it.hub?.name]
|
resp << [name: it.name, label: it.label, value: it.currentValue("lock"), type: "lock", id: it.id, hub: it.hub.name]
|
||||||
}
|
}
|
||||||
return resp
|
return resp
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,117 @@
|
|||||||
|
definition(
|
||||||
|
name: "EyXAr Notifications",
|
||||||
|
namespace: "eyxar",
|
||||||
|
author: "EyXAr",
|
||||||
|
description: "Phone and Voice notification of your door sensor status and phone presence sensor autonitification.",
|
||||||
|
category: "SmartThings Labs",
|
||||||
|
iconUrl: "https://s3.amazonaws.com/for-st/For_ST_60px.png",
|
||||||
|
iconX2Url: "https://s3.amazonaws.com/for-st/For_ST_120px.png",
|
||||||
|
iconX3Url: "https://s3.amazonaws.com/for-st/For_ST_256px.png"
|
||||||
|
)
|
||||||
|
|
||||||
|
/* For ST will only work if EyXAr Notification is installed and set-up first. */
|
||||||
|
|
||||||
|
preferences {
|
||||||
|
section("EyXAr Auto Notifications - For Voice Notification, Install the app 'FOR ST' in Google Play")
|
||||||
|
{
|
||||||
|
input "door", "capability.contactSensor", title: "Select Door/Contact", required: false, multiple: true
|
||||||
|
}
|
||||||
|
|
||||||
|
section("Send Notifications by Text or use below option?") {
|
||||||
|
input("recipients", "contact", title: "Send notifications to"){
|
||||||
|
input "phone", "phone", title: "Phone Number (optional, text charges may apply)",
|
||||||
|
description: "Phone Number", required: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
section("If combine with 'For ST' android app, this will add features of voice notifications.") {
|
||||||
|
input "sendPush", "bool", required: false,
|
||||||
|
title: "Phone/Tablet Auto Notification (Must be set to On =>>)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Presense */
|
||||||
|
|
||||||
|
section("Phone Presence Auto Notifications - For Voice notifications install 'FOR ST' in Google Play") {
|
||||||
|
input "presence", "capability.presenceSensor", title: "Select Phone/Tablet to Detect: (mandatory & rest below are optional)", required: false, multiple: true
|
||||||
|
}
|
||||||
|
|
||||||
|
def installed() {
|
||||||
|
initialize()
|
||||||
|
/* Presense */
|
||||||
|
subscribe(door, "contact.open", doorOpenHandler)
|
||||||
|
subscribe(door, "contact.closed", doorClosedHandler)
|
||||||
|
subscribe(presence, "presence", myHandler)
|
||||||
|
subscribe(presence, "presence", presenceHandler)
|
||||||
|
}
|
||||||
|
|
||||||
|
def updated() {
|
||||||
|
initialize()
|
||||||
|
}
|
||||||
|
|
||||||
|
def initialize() {
|
||||||
|
subscribe(door, "contact.open", doorOpenHandler)
|
||||||
|
subscribe(door, "contact.closed", doorClosedHandler)
|
||||||
|
subscribe(presence, "presence", myHandler)
|
||||||
|
subscribe(presence, "presence", presenceHandler)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
def doorOpenHandler(evt) {
|
||||||
|
def message = "EyXAr Detected the ${evt.displayName} is ${evt.value}!"
|
||||||
|
if (sendPush) {
|
||||||
|
sendPush(message)
|
||||||
|
}
|
||||||
|
if (phone) {
|
||||||
|
sendSms(phone, message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def doorClosedHandler(evt) {
|
||||||
|
def message = "EyXAr Detected the ${evt.displayName} is ${evt.value}!"
|
||||||
|
if (sendPush) {
|
||||||
|
sendPush(message)
|
||||||
|
}
|
||||||
|
if (phone) {
|
||||||
|
sendSms(phone, message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def contactHandler(evt) {
|
||||||
|
if("open" == evt.value)
|
||||||
|
|
||||||
|
// contact was opened, turn on a light maybe?
|
||||||
|
log.debug "Contact is in ${evt.value} state"
|
||||||
|
|
||||||
|
if("closed" == evt.value)
|
||||||
|
// contact was closed, turn off the light?
|
||||||
|
log.debug "Contact is in ${evt.value} state"
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Presense */
|
||||||
|
def myHandler(evt) {
|
||||||
|
if("present" == evt.value)
|
||||||
|
def message = "EyXAr Detected ${evt.displayName} is ${evt.value}!"
|
||||||
|
if (sendPush) {
|
||||||
|
sendPush(message)
|
||||||
|
}
|
||||||
|
if (phone) {
|
||||||
|
sendSms(phone, message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def presenceHandler(evt) {
|
||||||
|
|
||||||
|
if (evt.value == "present") {
|
||||||
|
log.debug "EyXAr ${evt.displayName} has arrived at the ${location}!"
|
||||||
|
sendPush("EyXAr ${evt.displayName} has arrived at the ${location}!")
|
||||||
|
} else if (evt.value == "not present") {
|
||||||
|
|
||||||
|
log.debug "EyXAr ${evt.displayName} has left the ${location}!"
|
||||||
|
|
||||||
|
sendPush("EyXAr ${evt.displayName} has left the ${location}!")
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,383 +0,0 @@
|
|||||||
/**
|
|
||||||
* Simple Sync Connect
|
|
||||||
*
|
|
||||||
* Copyright 2015 Roomie Remote, Inc.
|
|
||||||
*
|
|
||||||
* Date: 2015-09-22
|
|
||||||
*/
|
|
||||||
|
|
||||||
definition(
|
|
||||||
name: "Simple Sync Connect",
|
|
||||||
namespace: "roomieremote-raconnect",
|
|
||||||
author: "Roomie Remote, Inc.",
|
|
||||||
description: "Integrate SmartThings with your Simple Control activities via Simple Sync.",
|
|
||||||
category: "My Apps",
|
|
||||||
iconUrl: "https://s3.amazonaws.com/roomieuser/remotes/simplesync-60.png",
|
|
||||||
iconX2Url: "https://s3.amazonaws.com/roomieuser/remotes/simplesync-120.png",
|
|
||||||
iconX3Url: "https://s3.amazonaws.com/roomieuser/remotes/simplesync-120.png")
|
|
||||||
|
|
||||||
preferences()
|
|
||||||
{
|
|
||||||
page(name: "mainPage", title: "Simple Sync Setup", content: "mainPage", refreshTimeout: 5)
|
|
||||||
page(name:"agentDiscovery", title:"Simple Sync Discovery", content:"agentDiscovery", refreshTimeout:5)
|
|
||||||
page(name:"manualAgentEntry")
|
|
||||||
page(name:"verifyManualEntry")
|
|
||||||
}
|
|
||||||
|
|
||||||
def mainPage()
|
|
||||||
{
|
|
||||||
if (canInstallLabs())
|
|
||||||
{
|
|
||||||
return agentDiscovery()
|
|
||||||
}
|
|
||||||
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:"mainPage", title:"Upgrade needed!", nextPage:"", install:false, uninstall: true) {
|
|
||||||
section("Upgrade")
|
|
||||||
{
|
|
||||||
paragraph "$upgradeNeeded"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def agentDiscovery(params=[:])
|
|
||||||
{
|
|
||||||
int refreshCount = !state.refreshCount ? 0 : state.refreshCount as int
|
|
||||||
state.refreshCount = refreshCount + 1
|
|
||||||
def refreshInterval = refreshCount == 0 ? 2 : 5
|
|
||||||
|
|
||||||
if (!state.subscribe)
|
|
||||||
{
|
|
||||||
subscribe(location, null, locationHandler, [filterEvents:false])
|
|
||||||
state.subscribe = true
|
|
||||||
}
|
|
||||||
|
|
||||||
//ssdp request every fifth refresh
|
|
||||||
if ((refreshCount % 5) == 0)
|
|
||||||
{
|
|
||||||
discoverAgents()
|
|
||||||
}
|
|
||||||
|
|
||||||
def agentsDiscovered = agentsDiscovered()
|
|
||||||
|
|
||||||
return dynamicPage(name:"agentDiscovery", title:"Pair with Simple Sync", nextPage:"", refreshInterval: refreshInterval, install:true, uninstall: true) {
|
|
||||||
section("Pair with Simple Sync")
|
|
||||||
{
|
|
||||||
input "selectedAgent", "enum", required:true, title:"Select Simple Sync\n(${agentsDiscovered.size() ?: 0} found)", multiple:false, options:agentsDiscovered
|
|
||||||
href(name:"manualAgentEntry",
|
|
||||||
title:"Manually Configure Simple Sync",
|
|
||||||
required:false,
|
|
||||||
page:"manualAgentEntry")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def manualAgentEntry()
|
|
||||||
{
|
|
||||||
dynamicPage(name:"manualAgentEntry", title:"Manually Configure Simple Sync", nextPage:"verifyManualEntry", install:false, uninstall:true) {
|
|
||||||
section("Manually Configure Simple Sync")
|
|
||||||
{
|
|
||||||
paragraph "In the event that Simple Sync cannot be automatically discovered by your SmartThings hub, you may enter Simple Sync's IP address here."
|
|
||||||
input(name: "manualIPAddress", type: "text", title: "IP Address", required: true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def verifyManualEntry()
|
|
||||||
{
|
|
||||||
def hexIP = convertIPToHexString(manualIPAddress)
|
|
||||||
def hexPort = convertToHexString(47147)
|
|
||||||
def uuid = "593C03D2-1DA9-4CDB-A335-6C6DC98E56C3"
|
|
||||||
def hubId = ""
|
|
||||||
|
|
||||||
for (hub in location.hubs)
|
|
||||||
{
|
|
||||||
if (hub.localIP != null)
|
|
||||||
{
|
|
||||||
hubId = hub.id
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def manualAgent = [deviceType: "04",
|
|
||||||
mac: "unknown",
|
|
||||||
ip: hexIP,
|
|
||||||
port: hexPort,
|
|
||||||
ssdpPath: "/upnp/Roomie.xml",
|
|
||||||
ssdpUSN: "uuid:$uuid::urn:roomieremote-com:device:roomie:1",
|
|
||||||
hub: hubId,
|
|
||||||
verified: true,
|
|
||||||
name: "Simple Sync $manualIPAddress"]
|
|
||||||
|
|
||||||
state.agents[uuid] = manualAgent
|
|
||||||
|
|
||||||
addOrUpdateAgent(state.agents[uuid])
|
|
||||||
|
|
||||||
dynamicPage(name: "verifyManualEntry", title: "Manual Configuration Complete", nextPage: "", install:true, uninstall:true) {
|
|
||||||
section("")
|
|
||||||
{
|
|
||||||
paragraph("Tap Done to complete the installation process.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def discoverAgents()
|
|
||||||
{
|
|
||||||
def urn = getURN()
|
|
||||||
|
|
||||||
sendHubCommand(new physicalgraph.device.HubAction("lan discovery $urn", physicalgraph.device.Protocol.LAN))
|
|
||||||
}
|
|
||||||
|
|
||||||
def agentsDiscovered()
|
|
||||||
{
|
|
||||||
def gAgents = getAgents()
|
|
||||||
def agents = gAgents.findAll { it?.value?.verified == true }
|
|
||||||
def map = [:]
|
|
||||||
agents.each
|
|
||||||
{
|
|
||||||
map["${it.value.uuid}"] = it.value.name
|
|
||||||
}
|
|
||||||
map
|
|
||||||
}
|
|
||||||
|
|
||||||
def getAgents()
|
|
||||||
{
|
|
||||||
if (!state.agents)
|
|
||||||
{
|
|
||||||
state.agents = [:]
|
|
||||||
}
|
|
||||||
|
|
||||||
state.agents
|
|
||||||
}
|
|
||||||
|
|
||||||
def installed()
|
|
||||||
{
|
|
||||||
initialize()
|
|
||||||
}
|
|
||||||
|
|
||||||
def updated()
|
|
||||||
{
|
|
||||||
initialize()
|
|
||||||
}
|
|
||||||
|
|
||||||
def initialize()
|
|
||||||
{
|
|
||||||
if (state.subscribe)
|
|
||||||
{
|
|
||||||
unsubscribe()
|
|
||||||
state.subscribe = false
|
|
||||||
}
|
|
||||||
|
|
||||||
if (selectedAgent)
|
|
||||||
{
|
|
||||||
addOrUpdateAgent(state.agents[selectedAgent])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def addOrUpdateAgent(agent)
|
|
||||||
{
|
|
||||||
def children = getChildDevices()
|
|
||||||
def dni = agent.ip + ":" + agent.port
|
|
||||||
def found = false
|
|
||||||
|
|
||||||
children.each
|
|
||||||
{
|
|
||||||
if ((it.getDeviceDataByName("mac") == agent.mac))
|
|
||||||
{
|
|
||||||
found = true
|
|
||||||
|
|
||||||
if (it.getDeviceNetworkId() != dni)
|
|
||||||
{
|
|
||||||
it.setDeviceNetworkId(dni)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (it.getDeviceNetworkId() == dni)
|
|
||||||
{
|
|
||||||
found = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!found)
|
|
||||||
{
|
|
||||||
addChildDevice("roomieremote-agent", "Simple Sync", dni, agent.hub, [label: "Simple Sync"])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def locationHandler(evt)
|
|
||||||
{
|
|
||||||
def description = evt?.description
|
|
||||||
def urn = getURN()
|
|
||||||
def hub = evt?.hubId
|
|
||||||
def parsedEvent = parseEventMessage(description)
|
|
||||||
|
|
||||||
parsedEvent?.putAt("hub", hub)
|
|
||||||
|
|
||||||
//SSDP DISCOVERY EVENTS
|
|
||||||
if (parsedEvent?.ssdpTerm?.contains(urn))
|
|
||||||
{
|
|
||||||
def agent = parsedEvent
|
|
||||||
def ip = convertHexToIP(agent.ip)
|
|
||||||
def agents = getAgents()
|
|
||||||
|
|
||||||
agent.verified = true
|
|
||||||
agent.name = "Simple Sync $ip"
|
|
||||||
|
|
||||||
if (!agents[agent.uuid])
|
|
||||||
{
|
|
||||||
state.agents[agent.uuid] = agent
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private def parseEventMessage(String description)
|
|
||||||
{
|
|
||||||
def event = [:]
|
|
||||||
def parts = description.split(',')
|
|
||||||
|
|
||||||
parts.each
|
|
||||||
{ part ->
|
|
||||||
part = part.trim()
|
|
||||||
if (part.startsWith('devicetype:'))
|
|
||||||
{
|
|
||||||
def valueString = part.split(":")[1].trim()
|
|
||||||
event.devicetype = valueString
|
|
||||||
}
|
|
||||||
else if (part.startsWith('mac:'))
|
|
||||||
{
|
|
||||||
def valueString = part.split(":")[1].trim()
|
|
||||||
if (valueString)
|
|
||||||
{
|
|
||||||
event.mac = valueString
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (part.startsWith('networkAddress:'))
|
|
||||||
{
|
|
||||||
def valueString = part.split(":")[1].trim()
|
|
||||||
if (valueString)
|
|
||||||
{
|
|
||||||
event.ip = valueString
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (part.startsWith('deviceAddress:'))
|
|
||||||
{
|
|
||||||
def valueString = part.split(":")[1].trim()
|
|
||||||
if (valueString)
|
|
||||||
{
|
|
||||||
event.port = valueString
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (part.startsWith('ssdpPath:'))
|
|
||||||
{
|
|
||||||
def valueString = part.split(":")[1].trim()
|
|
||||||
if (valueString)
|
|
||||||
{
|
|
||||||
event.ssdpPath = valueString
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (part.startsWith('ssdpUSN:'))
|
|
||||||
{
|
|
||||||
part -= "ssdpUSN:"
|
|
||||||
def valueString = part.trim()
|
|
||||||
if (valueString)
|
|
||||||
{
|
|
||||||
event.ssdpUSN = valueString
|
|
||||||
|
|
||||||
def uuid = getUUIDFromUSN(valueString)
|
|
||||||
|
|
||||||
if (uuid)
|
|
||||||
{
|
|
||||||
event.uuid = uuid
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (part.startsWith('ssdpTerm:'))
|
|
||||||
{
|
|
||||||
part -= "ssdpTerm:"
|
|
||||||
def valueString = part.trim()
|
|
||||||
if (valueString)
|
|
||||||
{
|
|
||||||
event.ssdpTerm = valueString
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (part.startsWith('headers'))
|
|
||||||
{
|
|
||||||
part -= "headers:"
|
|
||||||
def valueString = part.trim()
|
|
||||||
if (valueString)
|
|
||||||
{
|
|
||||||
event.headers = valueString
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (part.startsWith('body'))
|
|
||||||
{
|
|
||||||
part -= "body:"
|
|
||||||
def valueString = part.trim()
|
|
||||||
if (valueString)
|
|
||||||
{
|
|
||||||
event.body = valueString
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
event
|
|
||||||
}
|
|
||||||
|
|
||||||
def getURN()
|
|
||||||
{
|
|
||||||
return "urn:roomieremote-com:device:roomie:1"
|
|
||||||
}
|
|
||||||
|
|
||||||
def getUUIDFromUSN(usn)
|
|
||||||
{
|
|
||||||
def parts = usn.split(":")
|
|
||||||
|
|
||||||
for (int i = 0; i < parts.size(); ++i)
|
|
||||||
{
|
|
||||||
if (parts[i] == "uuid")
|
|
||||||
{
|
|
||||||
return parts[i + 1]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def String convertHexToIP(hex)
|
|
||||||
{
|
|
||||||
[convertHexToInt(hex[0..1]),convertHexToInt(hex[2..3]),convertHexToInt(hex[4..5]),convertHexToInt(hex[6..7])].join(".")
|
|
||||||
}
|
|
||||||
|
|
||||||
def Integer convertHexToInt(hex)
|
|
||||||
{
|
|
||||||
Integer.parseInt(hex,16)
|
|
||||||
}
|
|
||||||
|
|
||||||
def String convertToHexString(n)
|
|
||||||
{
|
|
||||||
String hex = String.format("%X", n.toInteger())
|
|
||||||
}
|
|
||||||
|
|
||||||
def String convertIPToHexString(ipString)
|
|
||||||
{
|
|
||||||
String hex = ipString.tokenize(".").collect {
|
|
||||||
String.format("%02X", it.toInteger())
|
|
||||||
}.join()
|
|
||||||
}
|
|
||||||
|
|
||||||
def Boolean canInstallLabs()
|
|
||||||
{
|
|
||||||
return hasAllHubsOver("000.011.00603")
|
|
||||||
}
|
|
||||||
|
|
||||||
def Boolean hasAllHubsOver(String desiredFirmware)
|
|
||||||
{
|
|
||||||
return realHubFirmwareVersions.every { fw -> fw >= desiredFirmware }
|
|
||||||
}
|
|
||||||
|
|
||||||
def List getRealHubFirmwareVersions()
|
|
||||||
{
|
|
||||||
return location.hubs*.firmwareVersionString.findAll { it }
|
|
||||||
}
|
|
||||||
@@ -1,296 +0,0 @@
|
|||||||
/**
|
|
||||||
* Simple Sync Trigger
|
|
||||||
*
|
|
||||||
* Copyright 2015 Roomie Remote, Inc.
|
|
||||||
*
|
|
||||||
* Date: 2015-09-22
|
|
||||||
*/
|
|
||||||
definition(
|
|
||||||
name: "Simple Sync Trigger",
|
|
||||||
namespace: "roomieremote-ratrigger",
|
|
||||||
author: "Roomie Remote, Inc.",
|
|
||||||
description: "Trigger Simple Control activities when certain actions take place in your home.",
|
|
||||||
category: "My Apps",
|
|
||||||
iconUrl: "https://s3.amazonaws.com/roomieuser/remotes/simplesync-60.png",
|
|
||||||
iconX2Url: "https://s3.amazonaws.com/roomieuser/remotes/simplesync-120.png",
|
|
||||||
iconX3Url: "https://s3.amazonaws.com/roomieuser/remotes/simplesync-120.png")
|
|
||||||
|
|
||||||
|
|
||||||
preferences {
|
|
||||||
page(name: "agentSelection", title: "Select your Simple Sync")
|
|
||||||
page(name: "refreshActivities", title: "Updating list of Simple Sync activities")
|
|
||||||
page(name: "control", title: "Run a Simple Control activity when something happens")
|
|
||||||
page(name: "timeIntervalInput", title: "Only during a certain time", install: true, uninstall: true) {
|
|
||||||
section {
|
|
||||||
input "starting", "time", title: "Starting", required: false
|
|
||||||
input "ending", "time", title: "Ending", required: false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def agentSelection()
|
|
||||||
{
|
|
||||||
if (agent)
|
|
||||||
{
|
|
||||||
state.refreshCount = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
dynamicPage(name: "agentSelection", title: "Select your Simple Sync", nextPage: "control", install: false, uninstall: true) {
|
|
||||||
section {
|
|
||||||
input "agent", "capability.mediaController", title: "Simple Sync", required: true, multiple: false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def control()
|
|
||||||
{
|
|
||||||
def activities = agent.latestValue('activities')
|
|
||||||
|
|
||||||
if (!activities || !state.refreshCount)
|
|
||||||
{
|
|
||||||
int refreshCount = !state.refreshCount ? 0 : state.refreshCount as int
|
|
||||||
state.refreshCount = refreshCount + 1
|
|
||||||
def refreshInterval = refreshCount == 0 ? 2 : 4
|
|
||||||
|
|
||||||
// Request activities every 5th attempt
|
|
||||||
if((refreshCount % 5) == 0)
|
|
||||||
{
|
|
||||||
agent.getAllActivities()
|
|
||||||
}
|
|
||||||
|
|
||||||
dynamicPage(name: "control", title: "Updating list of Simple Control activities", nextPage: "", refreshInterval: refreshInterval, install: false, uninstall: true) {
|
|
||||||
section("") {
|
|
||||||
paragraph "Retrieving activities from Simple Sync"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
dynamicPage(name: "control", title: "Run a Simple Control activity when something happens", nextPage: "timeIntervalInput", install: false, uninstall: true) {
|
|
||||||
def anythingSet = anythingSet()
|
|
||||||
if (anythingSet) {
|
|
||||||
section("When..."){
|
|
||||||
ifSet "motion", "capability.motionSensor", title: "Motion Detected", required: false, multiple: true
|
|
||||||
ifSet "motionInactive", "capability.motionSensor", title: "Motion Stops", required: false, multiple: true
|
|
||||||
ifSet "contact", "capability.contactSensor", title: "Contact Opens", required: false, multiple: true
|
|
||||||
ifSet "contactClosed", "capability.contactSensor", title: "Contact Closes", required: false, multiple: true
|
|
||||||
ifSet "acceleration", "capability.accelerationSensor", title: "Acceleration Detected", required: false, multiple: true
|
|
||||||
ifSet "mySwitch", "capability.switch", title: "Switch Turned On", required: false, multiple: true
|
|
||||||
ifSet "mySwitchOff", "capability.switch", title: "Switch Turned Off", required: false, multiple: true
|
|
||||||
ifSet "arrivalPresence", "capability.presenceSensor", title: "Arrival Of", required: false, multiple: true
|
|
||||||
ifSet "departurePresence", "capability.presenceSensor", title: "Departure Of", required: false, multiple: true
|
|
||||||
ifSet "button1", "capability.button", title: "Button Press", required:false, multiple:true //remove from production
|
|
||||||
ifSet "triggerModes", "mode", title: "System Changes Mode", required: false, multiple: true
|
|
||||||
ifSet "timeOfDay", "time", title: "At a Scheduled Time", required: false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
section(anythingSet ? "Select additional triggers" : "When...", hideable: anythingSet, hidden: true){
|
|
||||||
ifUnset "motion", "capability.motionSensor", title: "Motion Here", required: false, multiple: true
|
|
||||||
ifUnset "motionInactive", "capability.motionSensor", title: "Motion Stops", required: false, multiple: true
|
|
||||||
ifUnset "contact", "capability.contactSensor", title: "Contact Opens", required: false, multiple: true
|
|
||||||
ifUnset "contactClosed", "capability.contactSensor", title: "Contact Closes", required: false, multiple: true
|
|
||||||
ifUnset "acceleration", "capability.accelerationSensor", title: "Acceleration Detected", required: false, multiple: true
|
|
||||||
ifUnset "mySwitch", "capability.switch", title: "Switch Turned On", required: false, multiple: true
|
|
||||||
ifUnset "mySwitchOff", "capability.switch", title: "Switch Turned Off", required: false, multiple: true
|
|
||||||
ifUnset "arrivalPresence", "capability.presenceSensor", title: "Arrival Of", required: false, multiple: true
|
|
||||||
ifUnset "departurePresence", "capability.presenceSensor", title: "Departure Of", required: false, multiple: true
|
|
||||||
ifUnset "button1", "capability.button", title: "Button Press", required:false, multiple:true //remove from production
|
|
||||||
ifUnset "triggerModes", "mode", title: "System Changes Mode", required: false, multiple: true
|
|
||||||
ifUnset "timeOfDay", "time", title: "At a Scheduled Time", required: false
|
|
||||||
}
|
|
||||||
section("Run this activity"){
|
|
||||||
input "activity", "enum", title: "Activity?", required: true, options: new groovy.json.JsonSlurper().parseText(activities ?: "[]").activities?.collect { ["${it.uuid}": it.name] }
|
|
||||||
}
|
|
||||||
|
|
||||||
section("More options", hideable: true, hidden: true) {
|
|
||||||
input "frequency", "decimal", title: "Minimum time between actions (defaults to every event)", description: "Minutes", required: false
|
|
||||||
href "timeIntervalInput", title: "Only during a certain time", description: timeLabel ?: "Tap to set", state: timeLabel ? "complete" : "incomplete"
|
|
||||||
input "days", "enum", title: "Only on certain days of the week", multiple: true, required: false,
|
|
||||||
options: ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
|
|
||||||
input "modes", "mode", title: "Only when mode is", multiple: true, required: false
|
|
||||||
input "oncePerDay", "bool", title: "Only once per day", required: false, defaultValue: false
|
|
||||||
}
|
|
||||||
section([mobileOnly:true]) {
|
|
||||||
label title: "Assign a name", required: false
|
|
||||||
mode title: "Set for specific mode(s)"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private anythingSet() {
|
|
||||||
for (name in ["motion","motionInactive","contact","contactClosed","acceleration","mySwitch","mySwitchOff","arrivalPresence","departurePresence","button1","triggerModes","timeOfDay"]) {
|
|
||||||
if (settings[name]) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
private ifUnset(Map options, String name, String capability) {
|
|
||||||
if (!settings[name]) {
|
|
||||||
input(options, name, capability)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private ifSet(Map options, String name, String capability) {
|
|
||||||
if (settings[name]) {
|
|
||||||
input(options, name, capability)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def installed() {
|
|
||||||
subscribeToEvents()
|
|
||||||
}
|
|
||||||
|
|
||||||
def updated() {
|
|
||||||
unsubscribe()
|
|
||||||
unschedule()
|
|
||||||
subscribeToEvents()
|
|
||||||
}
|
|
||||||
|
|
||||||
def subscribeToEvents() {
|
|
||||||
log.trace "subscribeToEvents()"
|
|
||||||
subscribe(app, appTouchHandler)
|
|
||||||
subscribe(contact, "contact.open", eventHandler)
|
|
||||||
subscribe(contactClosed, "contact.closed", eventHandler)
|
|
||||||
subscribe(acceleration, "acceleration.active", eventHandler)
|
|
||||||
subscribe(motion, "motion.active", eventHandler)
|
|
||||||
subscribe(motionInactive, "motion.inactive", eventHandler)
|
|
||||||
subscribe(mySwitch, "switch.on", eventHandler)
|
|
||||||
subscribe(mySwitchOff, "switch.off", eventHandler)
|
|
||||||
subscribe(arrivalPresence, "presence.present", eventHandler)
|
|
||||||
subscribe(departurePresence, "presence.not present", eventHandler)
|
|
||||||
subscribe(button1, "button.pushed", eventHandler)
|
|
||||||
|
|
||||||
if (triggerModes) {
|
|
||||||
subscribe(location, modeChangeHandler)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (timeOfDay) {
|
|
||||||
schedule(timeOfDay, scheduledTimeHandler)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def eventHandler(evt) {
|
|
||||||
if (allOk) {
|
|
||||||
def lastTime = state[frequencyKey(evt)]
|
|
||||||
if (oncePerDayOk(lastTime)) {
|
|
||||||
if (frequency) {
|
|
||||||
if (lastTime == null || now() - lastTime >= frequency * 60000) {
|
|
||||||
startActivity(evt)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
log.debug "Not taking action because $frequency minutes have not elapsed since last action"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
startActivity(evt)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
log.debug "Not taking action because it was already taken today"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def modeChangeHandler(evt) {
|
|
||||||
log.trace "modeChangeHandler $evt.name: $evt.value ($triggerModes)"
|
|
||||||
if (evt.value in triggerModes) {
|
|
||||||
eventHandler(evt)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def scheduledTimeHandler() {
|
|
||||||
eventHandler(null)
|
|
||||||
}
|
|
||||||
|
|
||||||
def appTouchHandler(evt) {
|
|
||||||
startActivity(evt)
|
|
||||||
}
|
|
||||||
|
|
||||||
private startActivity(evt) {
|
|
||||||
agent.startActivity(activity)
|
|
||||||
|
|
||||||
if (frequency) {
|
|
||||||
state.lastActionTimeStamp = now()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private frequencyKey(evt) {
|
|
||||||
//evt.deviceId ?: evt.value
|
|
||||||
"lastActionTimeStamp"
|
|
||||||
}
|
|
||||||
|
|
||||||
private dayString(Date date) {
|
|
||||||
def df = new java.text.SimpleDateFormat("yyyy-MM-dd")
|
|
||||||
if (location.timeZone) {
|
|
||||||
df.setTimeZone(location.timeZone)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
df.setTimeZone(TimeZone.getTimeZone("America/New_York"))
|
|
||||||
}
|
|
||||||
df.format(date)
|
|
||||||
}
|
|
||||||
|
|
||||||
private oncePerDayOk(Long lastTime) {
|
|
||||||
def result = true
|
|
||||||
if (oncePerDay) {
|
|
||||||
result = lastTime ? dayString(new Date()) != dayString(new Date(lastTime)) : true
|
|
||||||
log.trace "oncePerDayOk = $result"
|
|
||||||
}
|
|
||||||
result
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO - centralize somehow
|
|
||||||
private getAllOk() {
|
|
||||||
modeOk && daysOk && timeOk
|
|
||||||
}
|
|
||||||
|
|
||||||
private getModeOk() {
|
|
||||||
def result = !modes || modes.contains(location.mode)
|
|
||||||
log.trace "modeOk = $result"
|
|
||||||
result
|
|
||||||
}
|
|
||||||
|
|
||||||
private getDaysOk() {
|
|
||||||
def result = true
|
|
||||||
if (days) {
|
|
||||||
def df = new java.text.SimpleDateFormat("EEEE")
|
|
||||||
if (location.timeZone) {
|
|
||||||
df.setTimeZone(location.timeZone)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
df.setTimeZone(TimeZone.getTimeZone("America/New_York"))
|
|
||||||
}
|
|
||||||
def day = df.format(new Date())
|
|
||||||
result = days.contains(day)
|
|
||||||
}
|
|
||||||
log.trace "daysOk = $result"
|
|
||||||
result
|
|
||||||
}
|
|
||||||
|
|
||||||
private getTimeOk() {
|
|
||||||
def result = true
|
|
||||||
if (starting && ending) {
|
|
||||||
def currTime = now()
|
|
||||||
def start = timeToday(starting).time
|
|
||||||
def stop = timeToday(ending).time
|
|
||||||
result = start < stop ? currTime >= start && currTime <= stop : currTime <= stop || currTime >= start
|
|
||||||
}
|
|
||||||
log.trace "timeOk = $result"
|
|
||||||
result
|
|
||||||
}
|
|
||||||
|
|
||||||
private hhmm(time, fmt = "h:mm a")
|
|
||||||
{
|
|
||||||
def t = timeToday(time, location.timeZone)
|
|
||||||
def f = new java.text.SimpleDateFormat(fmt)
|
|
||||||
f.setTimeZone(location.timeZone ?: timeZone(time))
|
|
||||||
f.format(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
private timeIntervalLabel()
|
|
||||||
{
|
|
||||||
(starting && ending) ? hhmm(starting) + "-" + hhmm(ending, "h:mm a z") : ""
|
|
||||||
}
|
|
||||||
@@ -1,676 +0,0 @@
|
|||||||
/**
|
|
||||||
* Simple Control
|
|
||||||
*
|
|
||||||
* Copyright 2015 Roomie Remote, Inc.
|
|
||||||
*
|
|
||||||
* Date: 2015-09-22
|
|
||||||
*/
|
|
||||||
|
|
||||||
definition(
|
|
||||||
name: "Simple Control",
|
|
||||||
namespace: "roomieremote-roomieconnect",
|
|
||||||
author: "Roomie Remote, Inc.",
|
|
||||||
description: "Integrate SmartThings with your Simple Control activities.",
|
|
||||||
category: "My Apps",
|
|
||||||
iconUrl: "https://s3.amazonaws.com/roomieuser/remotes/simplesync-60.png",
|
|
||||||
iconX2Url: "https://s3.amazonaws.com/roomieuser/remotes/simplesync-120.png",
|
|
||||||
iconX3Url: "https://s3.amazonaws.com/roomieuser/remotes/simplesync-120.png")
|
|
||||||
|
|
||||||
preferences()
|
|
||||||
{
|
|
||||||
section("Allow Simple Control to Monitor and Control These Things...")
|
|
||||||
{
|
|
||||||
input "switches", "capability.switch", title: "Which Switches?", multiple: true, required: false
|
|
||||||
}
|
|
||||||
|
|
||||||
page(name: "mainPage", title: "Simple Control Setup", content: "mainPage", refreshTimeout: 5)
|
|
||||||
page(name:"agentDiscovery", title:"Simple Sync Discovery", content:"agentDiscovery", refreshTimeout:5)
|
|
||||||
page(name:"manualAgentEntry")
|
|
||||||
page(name:"verifyManualEntry")
|
|
||||||
}
|
|
||||||
|
|
||||||
mappings {
|
|
||||||
path("/devices") {
|
|
||||||
action: [
|
|
||||||
GET: "getDevices",
|
|
||||||
POST: "handleDevicesWithIDs"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
path("/device/:id") {
|
|
||||||
action: [
|
|
||||||
GET: "getDevice",
|
|
||||||
POST: "updateDevice"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
path("/subscriptions") {
|
|
||||||
action: [
|
|
||||||
GET: "listSubscriptions",
|
|
||||||
POST: "addSubscription", // {"deviceId":"xxx", "attributeName":"xxx","callbackUrl":"http://..."}
|
|
||||||
DELETE: "removeAllSubscriptions"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
path("/subscriptions/:id") {
|
|
||||||
action: [
|
|
||||||
DELETE: "removeSubscription"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private getAllDevices()
|
|
||||||
{
|
|
||||||
//log.debug("getAllDevices()")
|
|
||||||
([] + switches + locks + thermostats + imageCaptures + relaySwitches + doorControls + colorControls + musicPlayers + speechSynthesizers + switchLevels + indicators + mediaControllers + tones + tvs + alarms + valves + motionSensors + presenceSensors + beacons + pushButtons + smokeDetectors + coDetectors + contactSensors + accelerationSensors + energyMeters + powerMeters + lightSensors + humiditySensors + temperatureSensors + speechRecognizers + stepSensors + touchSensors)?.findAll()?.unique { it.id }
|
|
||||||
}
|
|
||||||
|
|
||||||
def getDevices()
|
|
||||||
{
|
|
||||||
//log.debug("getDevices, params: ${params}")
|
|
||||||
allDevices.collect {
|
|
||||||
//log.debug("device: ${it}")
|
|
||||||
deviceItem(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def getDevice()
|
|
||||||
{
|
|
||||||
//log.debug("getDevice, params: ${params}")
|
|
||||||
def device = allDevices.find { it.id == params.id }
|
|
||||||
if (!device)
|
|
||||||
{
|
|
||||||
render status: 404, data: '{"msg": "Device not found"}'
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
deviceItem(device)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def handleDevicesWithIDs()
|
|
||||||
{
|
|
||||||
//log.debug("handleDevicesWithIDs, params: ${params}")
|
|
||||||
def data = request.JSON
|
|
||||||
def ids = data?.ids?.findAll()?.unique()
|
|
||||||
//log.debug("ids: ${ids}")
|
|
||||||
def command = data?.command
|
|
||||||
def arguments = data?.arguments
|
|
||||||
if (command)
|
|
||||||
{
|
|
||||||
def success = false
|
|
||||||
//log.debug("command ${command}, arguments ${arguments}")
|
|
||||||
for (devId in ids)
|
|
||||||
{
|
|
||||||
def device = allDevices.find { it.id == devId }
|
|
||||||
if (device) {
|
|
||||||
if (arguments) {
|
|
||||||
device."$command"(*arguments)
|
|
||||||
} else {
|
|
||||||
device."$command"()
|
|
||||||
}
|
|
||||||
success = true
|
|
||||||
} else {
|
|
||||||
//log.debug("device not found ${devId}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (success)
|
|
||||||
{
|
|
||||||
render status: 200, data: "{}"
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
render status: 404, data: '{"msg": "Device not found"}'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ids.collect {
|
|
||||||
def currentId = it
|
|
||||||
def device = allDevices.find { it.id == currentId }
|
|
||||||
if (device)
|
|
||||||
{
|
|
||||||
deviceItem(device)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private deviceItem(device) {
|
|
||||||
[
|
|
||||||
id: device.id,
|
|
||||||
label: device.displayName,
|
|
||||||
currentState: device.currentStates,
|
|
||||||
capabilities: device.capabilities?.collect {[
|
|
||||||
name: it.name
|
|
||||||
]},
|
|
||||||
attributes: device.supportedAttributes?.collect {[
|
|
||||||
name: it.name,
|
|
||||||
dataType: it.dataType,
|
|
||||||
values: it.values
|
|
||||||
]},
|
|
||||||
commands: device.supportedCommands?.collect {[
|
|
||||||
name: it.name,
|
|
||||||
arguments: it.arguments
|
|
||||||
]},
|
|
||||||
type: [
|
|
||||||
name: device.typeName,
|
|
||||||
author: device.typeAuthor
|
|
||||||
]
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
def updateDevice()
|
|
||||||
{
|
|
||||||
//log.debug("updateDevice, params: ${params}")
|
|
||||||
def data = request.JSON
|
|
||||||
def command = data?.command
|
|
||||||
def arguments = data?.arguments
|
|
||||||
|
|
||||||
//log.debug("updateDevice, params: ${params}, request: ${data}")
|
|
||||||
if (!command) {
|
|
||||||
render status: 400, data: '{"msg": "command is required"}'
|
|
||||||
} else {
|
|
||||||
def device = allDevices.find { it.id == params.id }
|
|
||||||
if (device) {
|
|
||||||
if (arguments) {
|
|
||||||
device."$command"(*arguments)
|
|
||||||
} else {
|
|
||||||
device."$command"()
|
|
||||||
}
|
|
||||||
render status: 204, data: "{}"
|
|
||||||
} else {
|
|
||||||
render status: 404, data: '{"msg": "Device not found"}'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def listSubscriptions()
|
|
||||||
{
|
|
||||||
//log.debug "listSubscriptions()"
|
|
||||||
app.subscriptions?.findAll { it.deviceId }?.collect {
|
|
||||||
def deviceInfo = state[it.deviceId]
|
|
||||||
def response = [
|
|
||||||
id: it.id,
|
|
||||||
deviceId: it.deviceId,
|
|
||||||
attributeName: it.data,
|
|
||||||
handler: it.handler
|
|
||||||
]
|
|
||||||
//if (!selectedAgent) {
|
|
||||||
response.callbackUrl = deviceInfo?.callbackUrl
|
|
||||||
//}
|
|
||||||
response
|
|
||||||
} ?: []
|
|
||||||
}
|
|
||||||
|
|
||||||
def addSubscription() {
|
|
||||||
def data = request.JSON
|
|
||||||
def attribute = data.attributeName
|
|
||||||
def callbackUrl = data.callbackUrl
|
|
||||||
|
|
||||||
//log.debug "addSubscription, params: ${params}, request: ${data}"
|
|
||||||
if (!attribute) {
|
|
||||||
render status: 400, data: '{"msg": "attributeName is required"}'
|
|
||||||
} else {
|
|
||||||
def device = allDevices.find { it.id == data.deviceId }
|
|
||||||
if (device) {
|
|
||||||
//if (!selectedAgent) {
|
|
||||||
//log.debug "Adding callbackUrl: $callbackUrl"
|
|
||||||
state[device.id] = [callbackUrl: callbackUrl]
|
|
||||||
//}
|
|
||||||
//log.debug "Adding subscription"
|
|
||||||
def subscription = subscribe(device, attribute, deviceHandler)
|
|
||||||
if (!subscription || !subscription.eventSubscription) {
|
|
||||||
//log.debug("subscriptions: ${app.subscriptions}")
|
|
||||||
//for (sub in app.subscriptions)
|
|
||||||
//{
|
|
||||||
//log.debug("subscription.id ${sub.id} subscription.handler ${sub.handler} subscription.deviceId ${sub.deviceId}")
|
|
||||||
//log.debug(sub.properties.collect{it}.join('\n'))
|
|
||||||
//}
|
|
||||||
subscription = app.subscriptions?.find { it.device.id == data.deviceId && it.data == attribute && it.handler == 'deviceHandler' }
|
|
||||||
}
|
|
||||||
|
|
||||||
def response = [
|
|
||||||
id: subscription.id,
|
|
||||||
deviceId: subscription.device?.id,
|
|
||||||
attributeName: subscription.data,
|
|
||||||
handler: subscription.handler
|
|
||||||
]
|
|
||||||
//if (!selectedAgent) {
|
|
||||||
response.callbackUrl = callbackUrl
|
|
||||||
//}
|
|
||||||
response
|
|
||||||
} else {
|
|
||||||
render status: 400, data: '{"msg": "Device not found"}'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def removeSubscription()
|
|
||||||
{
|
|
||||||
def subscription = app.subscriptions?.find { it.id == params.id }
|
|
||||||
def device = subscription?.device
|
|
||||||
|
|
||||||
//log.debug "removeSubscription, params: ${params}, subscription: ${subscription}, device: ${device}"
|
|
||||||
if (device) {
|
|
||||||
//log.debug "Removing subscription for device: ${device.id}"
|
|
||||||
state.remove(device.id)
|
|
||||||
unsubscribe(device)
|
|
||||||
}
|
|
||||||
render status: 204, data: "{}"
|
|
||||||
}
|
|
||||||
|
|
||||||
def removeAllSubscriptions()
|
|
||||||
{
|
|
||||||
for (sub in app.subscriptions)
|
|
||||||
{
|
|
||||||
//log.debug("Subscription: ${sub}")
|
|
||||||
//log.debug(sub.properties.collect{it}.join('\n'))
|
|
||||||
def handler = sub.handler
|
|
||||||
def device = sub.device
|
|
||||||
|
|
||||||
if (device && handler == 'deviceHandler')
|
|
||||||
{
|
|
||||||
//log.debug(device.properties.collect{it}.join('\n'))
|
|
||||||
//log.debug("Removing subscription for device: ${device}")
|
|
||||||
state.remove(device.id)
|
|
||||||
unsubscribe(device)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def deviceHandler(evt) {
|
|
||||||
def deviceInfo = state[evt.deviceId]
|
|
||||||
//if (selectedAgent) {
|
|
||||||
// sendToRoomie(evt, agentCallbackUrl)
|
|
||||||
//} else if (deviceInfo) {
|
|
||||||
if (deviceInfo)
|
|
||||||
{
|
|
||||||
if (deviceInfo.callbackUrl) {
|
|
||||||
sendToRoomie(evt, deviceInfo.callbackUrl)
|
|
||||||
} else {
|
|
||||||
log.warn "No callbackUrl set for device: ${evt.deviceId}"
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
log.warn "No subscribed device found for device: ${evt.deviceId}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def sendToRoomie(evt, String callbackUrl) {
|
|
||||||
def callback = new URI(callbackUrl)
|
|
||||||
def host = callback.port != -1 ? "${callback.host}:${callback.port}" : callback.host
|
|
||||||
def path = callback.query ? "${callback.path}?${callback.query}".toString() : callback.path
|
|
||||||
sendHubCommand(new physicalgraph.device.HubAction(
|
|
||||||
method: "POST",
|
|
||||||
path: path,
|
|
||||||
headers: [
|
|
||||||
"Host": host,
|
|
||||||
"Content-Type": "application/json"
|
|
||||||
],
|
|
||||||
body: [evt: [deviceId: evt.deviceId, name: evt.name, value: evt.value]]
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
def mainPage()
|
|
||||||
{
|
|
||||||
if (canInstallLabs())
|
|
||||||
{
|
|
||||||
return agentDiscovery()
|
|
||||||
}
|
|
||||||
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:"mainPage", title:"Upgrade needed!", nextPage:"", install:false, uninstall: true) {
|
|
||||||
section("Upgrade")
|
|
||||||
{
|
|
||||||
paragraph "$upgradeNeeded"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def agentDiscovery(params=[:])
|
|
||||||
{
|
|
||||||
int refreshCount = !state.refreshCount ? 0 : state.refreshCount as int
|
|
||||||
state.refreshCount = refreshCount + 1
|
|
||||||
def refreshInterval = refreshCount == 0 ? 2 : 5
|
|
||||||
|
|
||||||
if (!state.subscribe)
|
|
||||||
{
|
|
||||||
subscribe(location, null, locationHandler, [filterEvents:false])
|
|
||||||
state.subscribe = true
|
|
||||||
}
|
|
||||||
|
|
||||||
//ssdp request every fifth refresh
|
|
||||||
if ((refreshCount % 5) == 0)
|
|
||||||
{
|
|
||||||
discoverAgents()
|
|
||||||
}
|
|
||||||
|
|
||||||
def agentsDiscovered = agentsDiscovered()
|
|
||||||
|
|
||||||
return dynamicPage(name:"agentDiscovery", title:"Pair with Simple Sync", nextPage:"", refreshInterval: refreshInterval, install:true, uninstall: true) {
|
|
||||||
section("Pair with Simple Sync")
|
|
||||||
{
|
|
||||||
input "selectedAgent", "enum", required:false, title:"Select Simple Sync\n(${agentsDiscovered.size() ?: 0} found)", multiple:false, options:agentsDiscovered
|
|
||||||
href(name:"manualAgentEntry",
|
|
||||||
title:"Manually Configure Simple Sync",
|
|
||||||
required:false,
|
|
||||||
page:"manualAgentEntry")
|
|
||||||
}
|
|
||||||
section("Allow Simple Control to Monitor and Control These Things...")
|
|
||||||
{
|
|
||||||
input "switches", "capability.switch", title: "Which Switches?", multiple: true, required: false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def manualAgentEntry()
|
|
||||||
{
|
|
||||||
dynamicPage(name:"manualAgentEntry", title:"Manually Configure Simple Sync", nextPage:"verifyManualEntry", install:false, uninstall:true) {
|
|
||||||
section("Manually Configure Simple Sync")
|
|
||||||
{
|
|
||||||
paragraph "In the event that Simple Sync cannot be automatically discovered by your SmartThings hub, you may enter Simple Sync's IP address here."
|
|
||||||
input(name: "manualIPAddress", type: "text", title: "IP Address", required: true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def verifyManualEntry()
|
|
||||||
{
|
|
||||||
def hexIP = convertIPToHexString(manualIPAddress)
|
|
||||||
def hexPort = convertToHexString(47147)
|
|
||||||
def uuid = "593C03D2-1DA9-4CDB-A335-6C6DC98E56C3"
|
|
||||||
def hubId = ""
|
|
||||||
|
|
||||||
for (hub in location.hubs)
|
|
||||||
{
|
|
||||||
if (hub.localIP != null)
|
|
||||||
{
|
|
||||||
hubId = hub.id
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def manualAgent = [deviceType: "04",
|
|
||||||
mac: "unknown",
|
|
||||||
ip: hexIP,
|
|
||||||
port: hexPort,
|
|
||||||
ssdpPath: "/upnp/Roomie.xml",
|
|
||||||
ssdpUSN: "uuid:$uuid::urn:roomieremote-com:device:roomie:1",
|
|
||||||
hub: hubId,
|
|
||||||
verified: true,
|
|
||||||
name: "Simple Sync $manualIPAddress"]
|
|
||||||
|
|
||||||
state.agents[uuid] = manualAgent
|
|
||||||
|
|
||||||
addOrUpdateAgent(state.agents[uuid])
|
|
||||||
|
|
||||||
dynamicPage(name: "verifyManualEntry", title: "Manual Configuration Complete", nextPage: "", install:true, uninstall:true) {
|
|
||||||
section("")
|
|
||||||
{
|
|
||||||
paragraph("Tap Done to complete the installation process.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def discoverAgents()
|
|
||||||
{
|
|
||||||
def urn = getURN()
|
|
||||||
|
|
||||||
sendHubCommand(new physicalgraph.device.HubAction("lan discovery $urn", physicalgraph.device.Protocol.LAN))
|
|
||||||
}
|
|
||||||
|
|
||||||
def agentsDiscovered()
|
|
||||||
{
|
|
||||||
def gAgents = getAgents()
|
|
||||||
def agents = gAgents.findAll { it?.value?.verified == true }
|
|
||||||
def map = [:]
|
|
||||||
agents.each
|
|
||||||
{
|
|
||||||
map["${it.value.uuid}"] = it.value.name
|
|
||||||
}
|
|
||||||
map
|
|
||||||
}
|
|
||||||
|
|
||||||
def getAgents()
|
|
||||||
{
|
|
||||||
if (!state.agents)
|
|
||||||
{
|
|
||||||
state.agents = [:]
|
|
||||||
}
|
|
||||||
|
|
||||||
state.agents
|
|
||||||
}
|
|
||||||
|
|
||||||
def installed()
|
|
||||||
{
|
|
||||||
initialize()
|
|
||||||
}
|
|
||||||
|
|
||||||
def updated()
|
|
||||||
{
|
|
||||||
initialize()
|
|
||||||
}
|
|
||||||
|
|
||||||
def initialize()
|
|
||||||
{
|
|
||||||
if (state.subscribe)
|
|
||||||
{
|
|
||||||
unsubscribe()
|
|
||||||
state.subscribe = false
|
|
||||||
}
|
|
||||||
|
|
||||||
if (selectedAgent)
|
|
||||||
{
|
|
||||||
addOrUpdateAgent(state.agents[selectedAgent])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def addOrUpdateAgent(agent)
|
|
||||||
{
|
|
||||||
def children = getChildDevices()
|
|
||||||
def dni = agent.ip + ":" + agent.port
|
|
||||||
def found = false
|
|
||||||
|
|
||||||
children.each
|
|
||||||
{
|
|
||||||
if ((it.getDeviceDataByName("mac") == agent.mac))
|
|
||||||
{
|
|
||||||
found = true
|
|
||||||
|
|
||||||
if (it.getDeviceNetworkId() != dni)
|
|
||||||
{
|
|
||||||
it.setDeviceNetworkId(dni)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (it.getDeviceNetworkId() == dni)
|
|
||||||
{
|
|
||||||
found = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!found)
|
|
||||||
{
|
|
||||||
addChildDevice("roomieremote-agent", "Simple Sync", dni, agent.hub, [label: "Simple Sync"])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def locationHandler(evt)
|
|
||||||
{
|
|
||||||
def description = evt?.description
|
|
||||||
def urn = getURN()
|
|
||||||
def hub = evt?.hubId
|
|
||||||
def parsedEvent = parseEventMessage(description)
|
|
||||||
|
|
||||||
parsedEvent?.putAt("hub", hub)
|
|
||||||
|
|
||||||
//SSDP DISCOVERY EVENTS
|
|
||||||
if (parsedEvent?.ssdpTerm?.contains(urn))
|
|
||||||
{
|
|
||||||
def agent = parsedEvent
|
|
||||||
def ip = convertHexToIP(agent.ip)
|
|
||||||
def agents = getAgents()
|
|
||||||
|
|
||||||
agent.verified = true
|
|
||||||
agent.name = "Simple Sync $ip"
|
|
||||||
|
|
||||||
if (!agents[agent.uuid])
|
|
||||||
{
|
|
||||||
state.agents[agent.uuid] = agent
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private def parseEventMessage(String description)
|
|
||||||
{
|
|
||||||
def event = [:]
|
|
||||||
def parts = description.split(',')
|
|
||||||
|
|
||||||
parts.each
|
|
||||||
{ part ->
|
|
||||||
part = part.trim()
|
|
||||||
if (part.startsWith('devicetype:'))
|
|
||||||
{
|
|
||||||
def valueString = part.split(":")[1].trim()
|
|
||||||
event.devicetype = valueString
|
|
||||||
}
|
|
||||||
else if (part.startsWith('mac:'))
|
|
||||||
{
|
|
||||||
def valueString = part.split(":")[1].trim()
|
|
||||||
if (valueString)
|
|
||||||
{
|
|
||||||
event.mac = valueString
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (part.startsWith('networkAddress:'))
|
|
||||||
{
|
|
||||||
def valueString = part.split(":")[1].trim()
|
|
||||||
if (valueString)
|
|
||||||
{
|
|
||||||
event.ip = valueString
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (part.startsWith('deviceAddress:'))
|
|
||||||
{
|
|
||||||
def valueString = part.split(":")[1].trim()
|
|
||||||
if (valueString)
|
|
||||||
{
|
|
||||||
event.port = valueString
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (part.startsWith('ssdpPath:'))
|
|
||||||
{
|
|
||||||
def valueString = part.split(":")[1].trim()
|
|
||||||
if (valueString)
|
|
||||||
{
|
|
||||||
event.ssdpPath = valueString
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (part.startsWith('ssdpUSN:'))
|
|
||||||
{
|
|
||||||
part -= "ssdpUSN:"
|
|
||||||
def valueString = part.trim()
|
|
||||||
if (valueString)
|
|
||||||
{
|
|
||||||
event.ssdpUSN = valueString
|
|
||||||
|
|
||||||
def uuid = getUUIDFromUSN(valueString)
|
|
||||||
|
|
||||||
if (uuid)
|
|
||||||
{
|
|
||||||
event.uuid = uuid
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (part.startsWith('ssdpTerm:'))
|
|
||||||
{
|
|
||||||
part -= "ssdpTerm:"
|
|
||||||
def valueString = part.trim()
|
|
||||||
if (valueString)
|
|
||||||
{
|
|
||||||
event.ssdpTerm = valueString
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (part.startsWith('headers'))
|
|
||||||
{
|
|
||||||
part -= "headers:"
|
|
||||||
def valueString = part.trim()
|
|
||||||
if (valueString)
|
|
||||||
{
|
|
||||||
event.headers = valueString
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (part.startsWith('body'))
|
|
||||||
{
|
|
||||||
part -= "body:"
|
|
||||||
def valueString = part.trim()
|
|
||||||
if (valueString)
|
|
||||||
{
|
|
||||||
event.body = valueString
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
event
|
|
||||||
}
|
|
||||||
|
|
||||||
def getURN()
|
|
||||||
{
|
|
||||||
return "urn:roomieremote-com:device:roomie:1"
|
|
||||||
}
|
|
||||||
|
|
||||||
def getUUIDFromUSN(usn)
|
|
||||||
{
|
|
||||||
def parts = usn.split(":")
|
|
||||||
|
|
||||||
for (int i = 0; i < parts.size(); ++i)
|
|
||||||
{
|
|
||||||
if (parts[i] == "uuid")
|
|
||||||
{
|
|
||||||
return parts[i + 1]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def String convertHexToIP(hex)
|
|
||||||
{
|
|
||||||
[convertHexToInt(hex[0..1]),convertHexToInt(hex[2..3]),convertHexToInt(hex[4..5]),convertHexToInt(hex[6..7])].join(".")
|
|
||||||
}
|
|
||||||
|
|
||||||
def Integer convertHexToInt(hex)
|
|
||||||
{
|
|
||||||
Integer.parseInt(hex,16)
|
|
||||||
}
|
|
||||||
|
|
||||||
def String convertToHexString(n)
|
|
||||||
{
|
|
||||||
String hex = String.format("%X", n.toInteger())
|
|
||||||
}
|
|
||||||
|
|
||||||
def String convertIPToHexString(ipString)
|
|
||||||
{
|
|
||||||
String hex = ipString.tokenize(".").collect {
|
|
||||||
String.format("%02X", it.toInteger())
|
|
||||||
}.join()
|
|
||||||
}
|
|
||||||
|
|
||||||
def Boolean canInstallLabs()
|
|
||||||
{
|
|
||||||
return hasAllHubsOver("000.011.00603")
|
|
||||||
}
|
|
||||||
|
|
||||||
def Boolean hasAllHubsOver(String desiredFirmware)
|
|
||||||
{
|
|
||||||
return realHubFirmwareVersions.every { fw -> fw >= desiredFirmware }
|
|
||||||
}
|
|
||||||
|
|
||||||
def List getRealHubFirmwareVersions()
|
|
||||||
{
|
|
||||||
return location.hubs*.firmwareVersionString.findAll { it }
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -353,7 +353,7 @@ def onLocation(evt) {
|
|||||||
}
|
}
|
||||||
else if (
|
else if (
|
||||||
lanEvent.headers && lanEvent.body &&
|
lanEvent.headers && lanEvent.body &&
|
||||||
lanEvent.headers."content-type"?.contains("xml")
|
lanEvent.headers."content-type".contains("xml")
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
def parsers = getParsers()
|
def parsers = getParsers()
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ definition(
|
|||||||
category: "SmartThings Labs",
|
category: "SmartThings Labs",
|
||||||
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Partner/ecobee.png",
|
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Partner/ecobee.png",
|
||||||
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Partner/ecobee@2x.png",
|
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Partner/ecobee@2x.png",
|
||||||
singleInstance: true
|
singleInstance: true
|
||||||
) {
|
) {
|
||||||
appSetting "clientId"
|
appSetting "clientId"
|
||||||
}
|
}
|
||||||
@@ -61,7 +61,7 @@ def authPage() {
|
|||||||
description = "Click to enter Ecobee Credentials"
|
description = "Click to enter Ecobee Credentials"
|
||||||
}
|
}
|
||||||
|
|
||||||
def redirectUrl = buildRedirectUrl
|
def redirectUrl = buildRedirectUrl //"${serverUrl}/oauth/initialize?appId=${app.id}&access_token=${atomicState.accessToken}"
|
||||||
log.debug "RedirectUrl = ${redirectUrl}"
|
log.debug "RedirectUrl = ${redirectUrl}"
|
||||||
// get rid of next button until the user is actually auth'd
|
// get rid of next button until the user is actually auth'd
|
||||||
if (!oauthTokenProvided) {
|
if (!oauthTokenProvided) {
|
||||||
@@ -103,7 +103,7 @@ def oauthInitUrl() {
|
|||||||
scope: "smartRead,smartWrite",
|
scope: "smartRead,smartWrite",
|
||||||
client_id: smartThingsClientId,
|
client_id: smartThingsClientId,
|
||||||
state: atomicState.oauthInitState,
|
state: atomicState.oauthInitState,
|
||||||
redirect_uri: callbackUrl
|
redirect_uri: callbackUrl //"https://graph.api.smartthings.com/oauth/callback"
|
||||||
]
|
]
|
||||||
|
|
||||||
redirect(location: "${apiEndpoint}/authorize?${toQueryString(oauthParams)}")
|
redirect(location: "${apiEndpoint}/authorize?${toQueryString(oauthParams)}")
|
||||||
@@ -115,13 +115,14 @@ def callback() {
|
|||||||
def code = params.code
|
def code = params.code
|
||||||
def oauthState = params.state
|
def oauthState = params.state
|
||||||
|
|
||||||
|
//verify oauthState == atomicState.oauthInitState, so the callback corresponds to the authentication request
|
||||||
if (oauthState == atomicState.oauthInitState){
|
if (oauthState == atomicState.oauthInitState){
|
||||||
|
|
||||||
def tokenParams = [
|
def tokenParams = [
|
||||||
grant_type: "authorization_code",
|
grant_type: "authorization_code",
|
||||||
code : code,
|
code : code,
|
||||||
client_id : smartThingsClientId,
|
client_id : smartThingsClientId,
|
||||||
redirect_uri: callbackUrl
|
redirect_uri: callbackUrl //"https://graph.api.smartthings.com/oauth/callback"
|
||||||
]
|
]
|
||||||
|
|
||||||
def tokenUrl = "https://www.ecobee.com/home/token?${toQueryString(tokenParams)}"
|
def tokenUrl = "https://www.ecobee.com/home/token?${toQueryString(tokenParams)}"
|
||||||
@@ -235,7 +236,6 @@ def connectionStatus(message, redirectUrl = null) {
|
|||||||
|
|
||||||
def getEcobeeThermostats() {
|
def getEcobeeThermostats() {
|
||||||
log.debug "getting device list"
|
log.debug "getting device list"
|
||||||
atomicState.remoteSensors = []
|
|
||||||
|
|
||||||
def requestBody = '{"selection":{"selectionType":"registered","selectionMatch":"","includeRuntime":true,"includeSensors":true}}'
|
def requestBody = '{"selection":{"selectionType":"registered","selectionMatch":"","includeRuntime":true,"includeSensors":true}}'
|
||||||
|
|
||||||
@@ -247,26 +247,31 @@ def getEcobeeThermostats() {
|
|||||||
]
|
]
|
||||||
|
|
||||||
def stats = [:]
|
def stats = [:]
|
||||||
try {
|
try {
|
||||||
httpGet(deviceListParams) { resp ->
|
httpGet(deviceListParams) { resp ->
|
||||||
|
|
||||||
if (resp.status == 200) {
|
if (resp.status == 200) {
|
||||||
resp.data.thermostatList.each { stat ->
|
resp.data.thermostatList.each { stat ->
|
||||||
atomicState.remoteSensors = atomicState.remoteSensors == null ? stat.remoteSensors : atomicState.remoteSensors << stat.remoteSensors
|
atomicState.remoteSensors = stat.remoteSensors
|
||||||
def dni = [app.id, stat.identifier].join('.')
|
def dni = [app.id, stat.identifier].join('.')
|
||||||
stats[dni] = getThermostatDisplayName(stat)
|
stats[dni] = getThermostatDisplayName(stat)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log.debug "http status: ${resp.status}"
|
log.debug "http status: ${resp.status}"
|
||||||
}
|
//refresh the auth token
|
||||||
}
|
if (resp.status == 500 && resp.data.status.code == 14) {
|
||||||
} catch (groovyx.net.http.HttpResponseException e) {
|
log.debug "Storing the failed action to try later"
|
||||||
log.trace "Exception polling children: " + e.response.data.status
|
atomicState.action = "getEcobeeThermostats"
|
||||||
if (e.response.data.status.code == 14) {
|
log.debug "Refreshing your auth_token!"
|
||||||
atomicState.action = "getEcobeeThermostats"
|
refreshAuthToken()
|
||||||
log.debug "Refreshing your auth_token!"
|
} else {
|
||||||
refreshAuthToken()
|
log.error "Authentication error, invalid authentication method, lack of credentials, etc."
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
} catch(Exception e) {
|
||||||
|
log.debug "___exception getEcobeeThermostats(): " + e
|
||||||
|
refreshAuthToken()
|
||||||
}
|
}
|
||||||
atomicState.thermostats = stats
|
atomicState.thermostats = stats
|
||||||
return stats
|
return stats
|
||||||
@@ -274,14 +279,11 @@ def getEcobeeThermostats() {
|
|||||||
|
|
||||||
Map sensorsDiscovered() {
|
Map sensorsDiscovered() {
|
||||||
def map = [:]
|
def map = [:]
|
||||||
log.info "list ${atomicState.remoteSensors}"
|
atomicState.remoteSensors.each {
|
||||||
atomicState.remoteSensors.each { sensors ->
|
if (it.type != "thermostat") {
|
||||||
sensors.each {
|
def value = "${it?.name}"
|
||||||
if (it.type != "thermostat") {
|
def key = "ecobee_sensor-"+ it?.id + "-" + it?.code
|
||||||
def value = "${it?.name}"
|
map["${key}"] = value
|
||||||
def key = "ecobee_sensor-"+ it?.id + "-" + it?.code
|
|
||||||
map["${key}"] = value
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
atomicState.sensors = map
|
atomicState.sensors = map
|
||||||
@@ -315,7 +317,7 @@ def initialize() {
|
|||||||
def devices = thermostats.collect { dni ->
|
def devices = thermostats.collect { dni ->
|
||||||
def d = getChildDevice(dni)
|
def d = getChildDevice(dni)
|
||||||
if(!d) {
|
if(!d) {
|
||||||
d = addChildDevice(app.namespace, getChildName(), dni, null, ["label":"${atomicState.thermostats[dni]}" ?: "Ecobee Thermostat"])
|
d = addChildDevice(app.namespace, getChildName(), dni, null, ["label":"Ecobee Thermostat:${atomicState.thermostats[dni]}"])
|
||||||
log.debug "created ${d.displayName} with id $dni"
|
log.debug "created ${d.displayName} with id $dni"
|
||||||
} else {
|
} else {
|
||||||
log.debug "found ${d.displayName} with id $dni already exists"
|
log.debug "found ${d.displayName} with id $dni already exists"
|
||||||
@@ -326,7 +328,7 @@ def initialize() {
|
|||||||
def sensors = ecobeesensors.collect { dni ->
|
def sensors = ecobeesensors.collect { dni ->
|
||||||
def d = getChildDevice(dni)
|
def d = getChildDevice(dni)
|
||||||
if(!d) {
|
if(!d) {
|
||||||
d = addChildDevice(app.namespace, getSensorChildName(), dni, null, ["label":"${atomicState.sensors[dni]}" ?:"Ecobee Sensor"])
|
d = addChildDevice(app.namespace, getSensorChildName(), dni, null, ["label":"Ecobee Sensor:${atomicState.sensors[dni]}"])
|
||||||
log.debug "created ${d.displayName} with id $dni"
|
log.debug "created ${d.displayName} with id $dni"
|
||||||
} else {
|
} else {
|
||||||
log.debug "found ${d.displayName} with id $dni already exists"
|
log.debug "found ${d.displayName} with id $dni already exists"
|
||||||
@@ -352,17 +354,21 @@ def initialize() {
|
|||||||
|
|
||||||
atomicState.thermostatData = [:] //reset Map to store thermostat data
|
atomicState.thermostatData = [:] //reset Map to store thermostat data
|
||||||
|
|
||||||
//send activity feeds to tell that device is connected
|
//send activity feeds to tell that device is connected
|
||||||
def notificationMessage = "is connected to SmartThings"
|
def notificationMessage = "is connected to SmartThings"
|
||||||
sendActivityFeeds(notificationMessage)
|
sendActivityFeeds(notificationMessage)
|
||||||
atomicState.timeSendPush = null
|
state.timeSendPush = null
|
||||||
atomicState.reAttempt = 0
|
|
||||||
|
|
||||||
pollHandler() //first time polling data data from thermostat
|
pollHandler() //first time polling data data from thermostat
|
||||||
|
|
||||||
//automatically update devices status every 5 mins
|
//automatically update devices status every 5 mins
|
||||||
runEvery5Minutes("poll")
|
runEvery5Minutes("poll")
|
||||||
|
|
||||||
|
//since access_token expires every 2 hours
|
||||||
|
runEvery1Hour("refreshAuthToken")
|
||||||
|
|
||||||
|
atomicState.reAttempt = 0
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def pollHandler() {
|
def pollHandler() {
|
||||||
@@ -383,10 +389,18 @@ def pollHandler() {
|
|||||||
def pollChildren(child = null) {
|
def pollChildren(child = null) {
|
||||||
def thermostatIdsString = getChildDeviceIdsString()
|
def thermostatIdsString = getChildDeviceIdsString()
|
||||||
log.debug "polling children: $thermostatIdsString"
|
log.debug "polling children: $thermostatIdsString"
|
||||||
def data = ""
|
|
||||||
|
|
||||||
def jsonRequestBody = '{"selection":{"selectionType":"thermostats","selectionMatch":"' + thermostatIdsString + '","includeExtendedRuntime":"true","includeSettings":"true","includeRuntime":"true","includeSensors":true}}'
|
def jsonRequestBody = '{"selection":{"selectionType":"thermostats","selectionMatch":"' + thermostatIdsString + '","includeExtendedRuntime":"true","includeSettings":"true","includeRuntime":"true","includeSensors":true}}'
|
||||||
def result = false
|
def result = false
|
||||||
|
// // TODO: test this:
|
||||||
|
//
|
||||||
|
// def jsonRequestBody = toJson([
|
||||||
|
// selection:[
|
||||||
|
// selectionType: "thermostats",
|
||||||
|
// selectionMatch: getChildDeviceIdsString(),
|
||||||
|
// includeRuntime: true
|
||||||
|
// ]
|
||||||
|
// ])
|
||||||
|
|
||||||
def pollParams = [
|
def pollParams = [
|
||||||
uri: apiEndpoint,
|
uri: apiEndpoint,
|
||||||
@@ -397,6 +411,11 @@ def pollChildren(child = null) {
|
|||||||
|
|
||||||
try{
|
try{
|
||||||
httpGet(pollParams) { resp ->
|
httpGet(pollParams) { resp ->
|
||||||
|
|
||||||
|
// if (resp.data) {
|
||||||
|
// debugEventFromParent(child, "pollChildren(child) >> resp.status = ${resp.status}, resp.data = ${resp.data}")
|
||||||
|
// }
|
||||||
|
|
||||||
if(resp.status == 200) {
|
if(resp.status == 200) {
|
||||||
log.debug "poll results returned resp.data ${resp.data}"
|
log.debug "poll results returned resp.data ${resp.data}"
|
||||||
atomicState.remoteSensors = resp.data.thermostatList.remoteSensors
|
atomicState.remoteSensors = resp.data.thermostatList.remoteSensors
|
||||||
@@ -407,88 +426,77 @@ def pollChildren(child = null) {
|
|||||||
|
|
||||||
log.debug "updating dni $dni"
|
log.debug "updating dni $dni"
|
||||||
|
|
||||||
data = [
|
def data = [
|
||||||
coolMode: (stat.settings.coolStages > 0),
|
coolMode: (stat.settings.coolStages > 0),
|
||||||
heatMode: (stat.settings.heatStages > 0),
|
heatMode: (stat.settings.heatStages > 0),
|
||||||
deviceTemperatureUnit: stat.settings.useCelsius,
|
|
||||||
minHeatingSetpoint: (stat.settings.heatRangeLow / 10),
|
|
||||||
maxHeatingSetpoint: (stat.settings.heatRangeHigh / 10),
|
|
||||||
minCoolingSetpoint: (stat.settings.coolRangeLow / 10),
|
|
||||||
maxCoolingSetpoint: (stat.settings.coolRangeHigh / 10),
|
|
||||||
autoMode: stat.settings.autoHeatCoolFeatureEnabled,
|
autoMode: stat.settings.autoHeatCoolFeatureEnabled,
|
||||||
auxHeatMode: (stat.settings.hasHeatPump) && (stat.settings.hasForcedAir || stat.settings.hasElectric || stat.settings.hasBoiler),
|
auxHeatMode: (stat.settings.hasHeatPump) && (stat.settings.hasForcedAir || stat.settings.hasElectric || stat.settings.hasBoiler),
|
||||||
temperature: (stat.runtime.actualTemperature / 10),
|
temperature: stat.runtime.actualTemperature / 10,
|
||||||
heatingSetpoint: stat.runtime.desiredHeat / 10,
|
heatingSetpoint: stat.runtime.desiredHeat / 10,
|
||||||
coolingSetpoint: stat.runtime.desiredCool / 10,
|
coolingSetpoint: stat.runtime.desiredCool / 10,
|
||||||
thermostatMode: stat.settings.hvacMode,
|
thermostatMode: stat.settings.hvacMode
|
||||||
humidity: stat.runtime.actualHumidity,
|
|
||||||
thermostatFanMode: stat.runtime.desiredFanMode
|
|
||||||
]
|
]
|
||||||
|
data["temperature"] = data["temperature"] ? data["temperature"].toDouble().toInteger() : data["temperature"]
|
||||||
if (location.temperatureScale == "F")
|
data["heatingSetpoint"] = data["heatingSetpoint"] ? data["heatingSetpoint"].toDouble().toInteger() : data["heatingSetpoint"]
|
||||||
{
|
data["coolingSetpoint"] = data["coolingSetpoint"] ? data["coolingSetpoint"].toDouble().toInteger() : data["coolingSetpoint"]
|
||||||
data["temperature"] = data["temperature"] ? Math.round(data["temperature"].toDouble()) : data["temperature"]
|
// debugEventFromParent(child, "Event Data = ${data}")
|
||||||
data["heatingSetpoint"] = data["heatingSetpoint"] ? Math.round(data["heatingSetpoint"].toDouble()) : data["heatingSetpoint"]
|
|
||||||
data["coolingSetpoint"] = data["coolingSetpoint"] ? Math.round(data["coolingSetpoint"].toDouble()) : data["coolingSetpoint"]
|
|
||||||
data["minHeatingSetpoint"] = data["minHeatingSetpoint"] ? Math.round(data["minHeatingSetpoint"].toDouble()) : data["minHeatingSetpoint"]
|
|
||||||
data["maxHeatingSetpoint"] = data["maxHeatingSetpoint"] ? Math.round(data["maxHeatingSetpoint"].toDouble()) : data["maxHeatingSetpoint"]
|
|
||||||
data["minCoolingSetpoint"] = data["minCoolingSetpoint"] ? Math.round(data["minCoolingSetpoint"].toDouble()) : data["minCoolingSetpoint"]
|
|
||||||
data["maxCoolingSetpoint"] = data["maxCoolingSetpoint"] ? Math.round(data["maxCoolingSetpoint"].toDouble()) : data["maxCoolingSetpoint"]
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data?.deviceTemperatureUnit == false && location.temperatureScale == "F") {
|
|
||||||
data["deviceTemperatureUnit"] = "F"
|
|
||||||
|
|
||||||
} else {
|
|
||||||
data["deviceTemperatureUnit"] = "C"
|
|
||||||
}
|
|
||||||
|
|
||||||
collector[dni] = [data:data]
|
collector[dni] = [data:data]
|
||||||
return collector
|
return collector
|
||||||
}
|
}
|
||||||
result = true
|
result = true
|
||||||
log.debug "updated ${atomicState.thermostats?.size()} stats: ${atomicState.thermostats}"
|
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.status == 500 && resp.data.status.code == 14) {
|
||||||
|
log.debug "Storing the failed action to try later"
|
||||||
|
atomicState.action = "pollChildren";
|
||||||
|
log.debug "Refreshing your auth_token!"
|
||||||
|
refreshAuthToken()
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
log.error "Authentication error, invalid authentication method, lack of credentials, etc."
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (groovyx.net.http.HttpResponseException e) {
|
} catch(Exception e) {
|
||||||
log.trace "Exception polling children: " + e.response.data.status
|
log.debug "___exception polling children: " + e
|
||||||
if (e.response.data.status.code == 14) {
|
// debugEventFromParent(child, "___exception polling children: " + e)
|
||||||
atomicState.action = "pollChildren"
|
refreshAuthToken()
|
||||||
log.debug "Refreshing your auth_token!"
|
|
||||||
refreshAuthToken()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
// Poll Child is invoked from the Child Device itself as part of the Poll Capability
|
// Poll Child is invoked from the Child Device itself as part of the Poll Capability
|
||||||
def pollChild(){
|
def pollChild(child){
|
||||||
|
|
||||||
def devices = getChildDevices()
|
if (pollChildren(child)){
|
||||||
|
if (!child.device.deviceNetworkId.startsWith("ecobee_sensor")){
|
||||||
if (pollChildren()){
|
if(atomicState.thermostats[child.device.deviceNetworkId] != null) {
|
||||||
devices.each { child ->
|
def tData = atomicState.thermostats[child.device.deviceNetworkId]
|
||||||
if (!child.device.deviceNetworkId.startsWith("ecobee_sensor")){
|
// debugEventFromParent(child, "pollChild(child)>> data for ${child.device.deviceNetworkId} : ${tData.data}") //TODO comment
|
||||||
if(atomicState.thermostats[child.device.deviceNetworkId] != null) {
|
log.info "pollChild(child)>> data for ${child.device.deviceNetworkId} : ${tData.data}"
|
||||||
def tData = atomicState.thermostats[child.device.deviceNetworkId]
|
child.generateEvent(tData.data) //parse received message from parent
|
||||||
log.info "pollChild(child)>> data for ${child.device.deviceNetworkId} : ${tData.data}"
|
// return tData.data
|
||||||
child.generateEvent(tData.data) //parse received message from parent
|
} else if(atomicState.thermostats[child.device.deviceNetworkId] == null) {
|
||||||
} else if(atomicState.thermostats[child.device.deviceNetworkId] == null) {
|
// debugEventFromParent(child, "ERROR: Device connection removed? no data for ${child.device.deviceNetworkId} after polling") //TODO comment
|
||||||
log.error "ERROR: Device connection removed? no data for ${child.device.deviceNetworkId}"
|
log.error "ERROR: Device connection removed? no data for ${child.device.deviceNetworkId}"
|
||||||
return null
|
return null
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log.info "ERROR: pollChildren()"
|
// debugEventFromParent(child, "ERROR: pollChildren(child) for ${child.device.deviceNetworkId} after polling") //TODO comment
|
||||||
|
log.info "ERROR: pollChildren(child) for ${child.device.deviceNetworkId} after polling"
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void poll() {
|
void poll() {
|
||||||
pollChild()
|
def devices = getChildDevices()
|
||||||
|
devices.each {pollChild(it)}
|
||||||
}
|
}
|
||||||
|
|
||||||
def availableModes(child) {
|
def availableModes(child) {
|
||||||
@@ -505,6 +513,9 @@ def availableModes(child) {
|
|||||||
{
|
{
|
||||||
log.error "ERROR: Device connection removed? no data for ${child.device.deviceNetworkId} after polling"
|
log.error "ERROR: Device connection removed? no data for ${child.device.deviceNetworkId} after polling"
|
||||||
|
|
||||||
|
// TODO: flag device as in error state
|
||||||
|
// child.errorState = true
|
||||||
|
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -531,6 +542,8 @@ def currentMode(child) {
|
|||||||
if(!tData) {
|
if(!tData) {
|
||||||
log.error "ERROR: Device connection removed? no data for ${child.device.deviceNetworkId} after polling"
|
log.error "ERROR: Device connection removed? no data for ${child.device.deviceNetworkId} after polling"
|
||||||
|
|
||||||
|
// TODO: flag device as in error state
|
||||||
|
// child.errorState = true
|
||||||
|
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
@@ -548,17 +561,8 @@ def updateSensorData() {
|
|||||||
def occupancy = ""
|
def occupancy = ""
|
||||||
it.capability.each {
|
it.capability.each {
|
||||||
if (it.type == "temperature") {
|
if (it.type == "temperature") {
|
||||||
if (it.value == "unknown") {
|
temperature = it.value as Double
|
||||||
temperature = "--"
|
temperature = (temperature / 10).toInteger()
|
||||||
} else {
|
|
||||||
if (location.temperatureScale == "F") {
|
|
||||||
temperature = Math.round(it.value.toDouble() / 10)
|
|
||||||
} else {
|
|
||||||
temperature = convertFtoC(it.value.toDouble() / 10)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
} else if (it.type == "occupancy") {
|
} else if (it.type == "occupancy") {
|
||||||
if(it.value == "true")
|
if(it.value == "true")
|
||||||
occupancy = "active"
|
occupancy = "active"
|
||||||
@@ -571,6 +575,7 @@ def updateSensorData() {
|
|||||||
if(d) {
|
if(d) {
|
||||||
d.sendEvent(name:"temperature", value: temperature)
|
d.sendEvent(name:"temperature", value: temperature)
|
||||||
d.sendEvent(name:"motion", value: occupancy)
|
d.sendEvent(name:"motion", value: occupancy)
|
||||||
|
// debugEventFromParent(d, "temperature : ${temperature}, motion:${occupancy}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -590,65 +595,68 @@ def toQueryString(Map m) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private refreshAuthToken() {
|
private refreshAuthToken() {
|
||||||
log.debug "refreshing auth token"
|
log.debug "refreshing auth token"
|
||||||
|
|
||||||
if(!atomicState.refreshToken) {
|
if(!atomicState.refreshToken) {
|
||||||
log.warn "Can not refresh OAuth token since there is no refreshToken stored"
|
log.warn "Can not refresh OAuth token since there is no refreshToken stored"
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
def refreshParams = [
|
def refreshParams = [
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
uri : apiEndpoint,
|
uri : apiEndpoint,
|
||||||
path : "/token",
|
path : "/token",
|
||||||
query : [grant_type: 'refresh_token', code: "${atomicState.refreshToken}", client_id: smartThingsClientId],
|
query : [grant_type: 'refresh_token', code: "${atomicState.refreshToken}", client_id: smartThingsClientId],
|
||||||
]
|
]
|
||||||
|
|
||||||
log.debug refreshParams
|
log.debug refreshParams
|
||||||
|
|
||||||
def notificationMessage = "is disconnected from SmartThings, because the access credential changed or was lost. Please go to the Ecobee (Connect) SmartApp and re-enter your account login credentials."
|
def notificationMessage = "is disconnected from SmartThings, because the access credential changed or was lost. Please go to the Ecobee (Connect) SmartApp and re-enter your account login credentials."
|
||||||
//changed to httpPost
|
//changed to httpPost
|
||||||
try {
|
try {
|
||||||
def jsonMap
|
def jsonMap
|
||||||
httpPost(refreshParams) { resp ->
|
httpPost(refreshParams) { resp ->
|
||||||
|
|
||||||
if(resp.status == 200) {
|
if(resp.status == 200) {
|
||||||
log.debug "Token refreshed...calling saved RestAction now!"
|
log.debug "Token refreshed...calling saved RestAction now!"
|
||||||
|
|
||||||
debugEvent("Token refreshed ... calling saved RestAction now!")
|
debugEvent("Token refreshed ... calling saved RestAction now!")
|
||||||
|
|
||||||
log.debug resp
|
log.debug resp
|
||||||
|
|
||||||
jsonMap = resp.data
|
jsonMap = resp.data
|
||||||
|
|
||||||
if(resp.data) {
|
if(resp.data) {
|
||||||
|
|
||||||
log.debug resp.data
|
log.debug resp.data
|
||||||
debugEvent("Response = ${resp.data}")
|
debugEvent("Response = ${resp.data}")
|
||||||
|
|
||||||
atomicState.refreshToken = resp?.data?.refresh_token
|
atomicState.refreshToken = resp?.data?.refresh_token
|
||||||
atomicState.authToken = resp?.data?.access_token
|
atomicState.authToken = resp?.data?.access_token
|
||||||
|
|
||||||
debugEvent("Refresh Token = ${atomicState.refreshToken}")
|
debugEvent("Refresh Token = ${atomicState.refreshToken}")
|
||||||
debugEvent("OAUTH Token = ${atomicState.authToken}")
|
debugEvent("OAUTH Token = ${atomicState.authToken}")
|
||||||
|
|
||||||
if(atomicState.action && atomicState.action != "") {
|
if(atomicState.action && atomicState.action != "") {
|
||||||
log.debug "Executing next action: ${atomicState.action}"
|
log.debug "Executing next action: ${atomicState.action}"
|
||||||
|
|
||||||
"${atomicState.action}"()
|
"${atomicState.action}"()
|
||||||
|
|
||||||
atomicState.action = ""
|
//remove saved action
|
||||||
}
|
atomicState.action = ""
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
atomicState.action = ""
|
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}"
|
}
|
||||||
|
} catch(Exception e) {
|
||||||
|
log.error "refreshAuthToken() >> Error: e.statusCode ${e.statusCode}"
|
||||||
def reAttemptPeriod = 300 // in sec
|
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")
|
runIn(reAttemptPeriod, "refreshAuthToken")
|
||||||
} else if (e.statusCode == 401) { // unauthorized
|
} else if (e.statusCode == 401) { //refresh token is expired
|
||||||
atomicState.reAttempt = atomicState.reAttempt + 1
|
atomicState.reAttempt = atomicState.reAttempt + 1
|
||||||
log.warn "reAttempt refreshAuthToken to try = ${atomicState.reAttempt}"
|
log.warn "reAttempt refreshAuthToken to try = ${atomicState.reAttempt}"
|
||||||
if (atomicState.reAttempt <= 3) {
|
if (atomicState.reAttempt <= 3) {
|
||||||
@@ -657,16 +665,20 @@ private refreshAuthToken() {
|
|||||||
sendPushAndFeeds(notificationMessage)
|
sendPushAndFeeds(notificationMessage)
|
||||||
atomicState.reAttempt = 0
|
atomicState.reAttempt = 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def resumeProgram(child, deviceId) {
|
def resumeProgram(child, deviceId) {
|
||||||
|
|
||||||
|
// def thermostatIdsString = getChildDeviceIdsString()
|
||||||
|
// log.debug "resumeProgram children: $thermostatIdsString"
|
||||||
|
|
||||||
def jsonRequestBody = '{"selection":{"selectionType":"thermostats","selectionMatch":"' + deviceId + '","includeRuntime":true},"functions": [{"type": "resumeProgram"}]}'
|
def jsonRequestBody = '{"selection":{"selectionType":"thermostats","selectionMatch":"' + deviceId + '","includeRuntime":true},"functions": [{"type": "resumeProgram"}]}'
|
||||||
|
//, { "type": "sendMessage", "params": { "text": "Setpoint Updated" } }
|
||||||
def result = sendJson(jsonRequestBody)
|
def result = sendJson(jsonRequestBody)
|
||||||
|
// debugEventFromParent(child, "resumeProgram(child) with result ${result}")
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -674,27 +686,28 @@ def setHold(child, heating, cooling, deviceId, sendHoldType) {
|
|||||||
|
|
||||||
int h = heating * 10
|
int h = heating * 10
|
||||||
int c = cooling * 10
|
int c = cooling * 10
|
||||||
|
|
||||||
|
// log.debug "setpoints____________ - h: $heating - $h, c: $cooling - $c"
|
||||||
|
// def thermostatIdsString = getChildDeviceIdsString()
|
||||||
|
|
||||||
def jsonRequestBody = '{"selection":{"selectionType":"thermostats","selectionMatch":"' + deviceId + '","includeRuntime":true},"functions": [{ "type": "setHold", "params": { "coolHoldTemp": '+c+',"heatHoldTemp": '+h+', "holdType": '+sendHoldType+' } } ]}'
|
def jsonRequestBody = '{"selection":{"selectionType":"thermostats","selectionMatch":"' + deviceId + '","includeRuntime":true},"functions": [{ "type": "setHold", "params": { "coolHoldTemp": '+c+',"heatHoldTemp": '+h+', "holdType": '+sendHoldType+' } } ]}'
|
||||||
|
// def jsonRequestBody = '{"selection":{"selectionType":"thermostats","selectionMatch":"' + thermostatIdsString + '","includeRuntime":true},"functions": [{"type": "resumeProgram"}, { "type": "setHold", "params": { "coolHoldTemp": '+c+',"heatHoldTemp": '+h+', "holdType": "indefinite" } } ]}'
|
||||||
def result = sendJson(child, jsonRequestBody)
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
def setFanMode(child, heating, cooling, deviceId, sendHoldType, fanMode) {
|
|
||||||
|
|
||||||
int h = heating * 10
|
|
||||||
int c = cooling * 10
|
|
||||||
|
|
||||||
|
|
||||||
def jsonRequestBody = '{"selection":{"selectionType":"thermostats","selectionMatch":"' + deviceId + '","includeRuntime":true},"functions": [{ "type": "setHold", "params": { "coolHoldTemp": '+c+',"heatHoldTemp": '+h+', "holdType": '+sendHoldType+', "fan": '+fanMode+' } } ]}'
|
|
||||||
def result = sendJson(child, jsonRequestBody)
|
def result = sendJson(child, jsonRequestBody)
|
||||||
|
// debugEventFromParent(child, "setHold: heating: ${h}, cooling: ${c} with result ${result}")
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
def setMode(child, mode, deviceId) {
|
def setMode(child, mode, deviceId) {
|
||||||
|
// def thermostatIdsString = getChildDeviceIdsString()
|
||||||
|
// log.debug "setCoolingSetpoint children: $thermostatIdsString"
|
||||||
|
|
||||||
def jsonRequestBody = '{"selection":{"selectionType":"thermostats","selectionMatch":"' + deviceId + '","includeRuntime":true},"thermostat": {"settings":{"hvacMode":"'+"${mode}"+'"}}}'
|
def jsonRequestBody = '{"selection":{"selectionType":"thermostats","selectionMatch":"' + deviceId + '","includeRuntime":true},"thermostat": {"settings":{"hvacMode":"'+"${mode}"+'"}}}'
|
||||||
|
|
||||||
|
// log.debug "Mode Request Body = ${jsonRequestBody}"
|
||||||
|
// debugEvent ("Mode Request Body = ${jsonRequestBody}")
|
||||||
|
|
||||||
def result = sendJson(jsonRequestBody)
|
def result = sendJson(jsonRequestBody)
|
||||||
|
// debugEventFromParent(child, "setMode to ${mode} with result ${result}")
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -711,6 +724,8 @@ def sendJson(child = null, String jsonBody) {
|
|||||||
try{
|
try{
|
||||||
httpPost(cmdParams) { resp ->
|
httpPost(cmdParams) { resp ->
|
||||||
|
|
||||||
|
// debugEventFromParent(child, "sendJson >> resp.status ${resp.status}, resp.data: ${resp.data}")
|
||||||
|
|
||||||
if(resp.status == 200) {
|
if(resp.status == 200) {
|
||||||
|
|
||||||
log.debug "updated ${resp.data}"
|
log.debug "updated ${resp.data}"
|
||||||
@@ -721,21 +736,30 @@ def sendJson(child = null, String jsonBody) {
|
|||||||
log.debug "Error return code = ${resp.data.status.code}"
|
log.debug "Error return code = ${resp.data.status.code}"
|
||||||
debugEvent("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 == 500 && resp.status.code == 14) {
|
||||||
|
//log.debug "Storing the failed action to try later"
|
||||||
|
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 (groovyx.net.http.HttpResponseException e) {
|
} catch(Exception e) {
|
||||||
log.trace "Exception Sending Json: " + e.response.data.status
|
log.debug "Exception Sending Json: " + e
|
||||||
debugEvent ("sent Json & got http status ${e.statusCode} - ${e.response.data.status.code}")
|
debugEvent ("Exception Sending JSON: " + e)
|
||||||
if (e.response.data.status.code == 14) {
|
refreshAuthToken()
|
||||||
atomicState.action = "pollChildren"
|
return false
|
||||||
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)
|
if (returnStatus == 0)
|
||||||
return true
|
return true
|
||||||
@@ -770,37 +794,25 @@ def debugEventFromParent(child, message) {
|
|||||||
|
|
||||||
//send both push notification and mobile activity feeds
|
//send both push notification and mobile activity feeds
|
||||||
def sendPushAndFeeds(notificationMessage){
|
def sendPushAndFeeds(notificationMessage){
|
||||||
log.warn "sendPushAndFeeds >> notificationMessage: ${notificationMessage}"
|
log.warn "sendPushAndFeeds >> notificationMessage: ${notificationMessage}"
|
||||||
log.warn "sendPushAndFeeds >> atomicState.timeSendPush: ${atomicState.timeSendPush}"
|
log.warn "sendPushAndFeeds >> atomicState.timeSendPush: ${atomicState.timeSendPush}"
|
||||||
if (atomicState.timeSendPush){
|
if (atomicState.timeSendPush){
|
||||||
if (now() - atomicState.timeSendPush > 86400000){ // notification is sent to remind user once a day
|
if (now() - atomicState.timeSendPush > 86400000){ // notification is sent to remind user once a day
|
||||||
sendPush("Your Ecobee thermostat " + notificationMessage)
|
sendPush("Your Ecobee thermostat " + notificationMessage)
|
||||||
sendActivityFeeds(notificationMessage)
|
sendActivityFeeds(notificationMessage)
|
||||||
atomicState.timeSendPush = now()
|
atomicState.timeSendPush = now()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
sendPush("Your Ecobee thermostat " + notificationMessage)
|
sendPush("Your Ecobee thermostat " + notificationMessage)
|
||||||
sendActivityFeeds(notificationMessage)
|
sendActivityFeeds(notificationMessage)
|
||||||
atomicState.timeSendPush = now()
|
atomicState.timeSendPush = now()
|
||||||
}
|
}
|
||||||
atomicState.authToken = null
|
atomicState.authToken = null
|
||||||
}
|
}
|
||||||
|
|
||||||
def sendActivityFeeds(notificationMessage) {
|
def sendActivityFeeds(notificationMessage) {
|
||||||
def devices = getChildDevices()
|
def devices = getChildDevices()
|
||||||
devices.each { child ->
|
devices.each { child ->
|
||||||
child.generateActivityFeedsEvent(notificationMessage) //parse received message from parent
|
child.generateActivityFeedsEvent(notificationMessage) //parse received message from parent
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
def roundC (tempC) {
|
|
||||||
return String.format("%.1f", (Math.round(tempC * 2))/2)
|
|
||||||
}
|
|
||||||
|
|
||||||
def convertFtoC (tempF) {
|
|
||||||
return String.format("%.1f", (Math.round(((tempF - 32)*(5/9)) * 2))/2)
|
|
||||||
}
|
|
||||||
|
|
||||||
def convertCtoF (tempC) {
|
|
||||||
return (Math.round(tempC * (9/5)) + 32).toInteger()
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -201,8 +201,8 @@ def completionPage() {
|
|||||||
|
|
||||||
section("Switches") {
|
section("Switches") {
|
||||||
input(name: "completionSwitches", type: "capability.switch", title: "Set these switches", description: null, required: false, multiple: true, submitOnChange: true)
|
input(name: "completionSwitches", type: "capability.switch", title: "Set these switches", description: null, required: false, multiple: true, submitOnChange: true)
|
||||||
if (completionSwitches) {
|
if (completionSwitches || androidClient()) {
|
||||||
input(name: "completionSwitchesState", type: "enum", title: "To", description: null, required: false, multiple: false, options: ["on", "off"], defaultValue: "on")
|
input(name: "completionSwitchesState", type: "enum", title: "To", description: null, required: false, multiple: false, options: ["on", "off"], style: "segmented", defaultValue: "on")
|
||||||
input(name: "completionSwitchesLevel", type: "number", title: "Optionally, Set Dimmer Levels To", description: null, required: false, multiple: false, range: "(0..99)")
|
input(name: "completionSwitchesLevel", type: "number", title: "Optionally, Set Dimmer Levels To", description: null, required: false, multiple: false, range: "(0..99)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
* for the specific language governing permissions and limitations under the License.
|
* for the specific language governing permissions and limitations under the License.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
definition(
|
definition(
|
||||||
name: "Hue (Connect)",
|
name: "Hue (Connect)",
|
||||||
namespace: "smartthings",
|
namespace: "smartthings",
|
||||||
@@ -35,11 +35,23 @@ preferences {
|
|||||||
}
|
}
|
||||||
|
|
||||||
def mainPage() {
|
def mainPage() {
|
||||||
def bridges = bridgesDiscovered()
|
if(canInstallLabs()) {
|
||||||
if (state.username && bridges) {
|
def bridges = bridgesDiscovered()
|
||||||
return bulbDiscovery()
|
if (state.username && bridges) {
|
||||||
|
return bulbDiscovery()
|
||||||
|
} else {
|
||||||
|
return bridgeDiscovery()
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
return bridgeDiscovery()
|
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:"bridgeDiscovery", title:"Upgrade needed!", nextPage:"", install:false, uninstall: true) {
|
||||||
|
section("Upgrade") {
|
||||||
|
paragraph "$upgradeNeeded"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,7 +70,7 @@ def bridgeDiscovery(params=[:])
|
|||||||
state.bridges = [:]
|
state.bridges = [:]
|
||||||
state.bridgeRefreshCount = 0
|
state.bridgeRefreshCount = 0
|
||||||
app.updateSetting("selectedHue", "")
|
app.updateSetting("selectedHue", "")
|
||||||
}
|
}
|
||||||
|
|
||||||
subscribe(location, null, locationHandler, [filterEvents:false])
|
subscribe(location, null, locationHandler, [filterEvents:false])
|
||||||
|
|
||||||
@@ -68,7 +80,7 @@ def bridgeDiscovery(params=[:])
|
|||||||
}
|
}
|
||||||
|
|
||||||
//setup.xml request every 3 seconds except on discoveries
|
//setup.xml request every 3 seconds except on discoveries
|
||||||
if(((bridgeRefreshCount % 3) == 0) && ((bridgeRefreshCount % 5) != 0)) {
|
if(((bridgeRefreshCount % 1) == 0) && ((bridgeRefreshCount % 5) != 0)) {
|
||||||
verifyHueBridges()
|
verifyHueBridges()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -130,8 +142,8 @@ def bulbDiscovery() {
|
|||||||
def bulboptions = bulbsDiscovered() ?: [:]
|
def bulboptions = bulbsDiscovered() ?: [:]
|
||||||
def numFound = bulboptions.size() ?: 0
|
def numFound = bulboptions.size() ?: 0
|
||||||
if (numFound == 0)
|
if (numFound == 0)
|
||||||
app.updateSetting("selectedBulbs", "")
|
app.updateSetting("selectedBulbs", "")
|
||||||
|
|
||||||
if((bulbRefreshCount % 5) == 0) {
|
if((bulbRefreshCount % 5) == 0) {
|
||||||
discoverHueBulbs()
|
discoverHueBulbs()
|
||||||
}
|
}
|
||||||
@@ -140,7 +152,7 @@ def bulbDiscovery() {
|
|||||||
section("Please wait while we discover your Hue Bulbs. Discovery can take five minutes or more, so sit back and relax! Select your device below once discovered.") {
|
section("Please wait while we discover your Hue Bulbs. Discovery can take five minutes or more, so sit back and relax! Select your device below once discovered.") {
|
||||||
input "selectedBulbs", "enum", required:false, title:"Select Hue Bulbs (${numFound} found)", multiple:true, options:bulboptions
|
input "selectedBulbs", "enum", required:false, title:"Select Hue Bulbs (${numFound} found)", multiple:true, options:bulboptions
|
||||||
}
|
}
|
||||||
section {
|
section {
|
||||||
def title = getBridgeIP() ? "Hue bridge (${getBridgeIP()})" : "Find bridges"
|
def title = getBridgeIP() ? "Hue bridge (${getBridgeIP()})" : "Find bridges"
|
||||||
href "bridgeDiscovery", title: title, description: "", state: selectedHue ? "complete" : "incomplete", params: [override: true]
|
href "bridgeDiscovery", title: title, description: "", state: selectedHue ? "complete" : "incomplete", params: [override: true]
|
||||||
|
|
||||||
@@ -175,7 +187,6 @@ private discoverHueBulbs() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private verifyHueBridge(String deviceNetworkId, String host) {
|
private verifyHueBridge(String deviceNetworkId, String host) {
|
||||||
log.trace "Verify Hue Bridge $deviceNetworkId"
|
|
||||||
sendHubCommand(new physicalgraph.device.HubAction([
|
sendHubCommand(new physicalgraph.device.HubAction([
|
||||||
method: "GET",
|
method: "GET",
|
||||||
path: "/description.xml",
|
path: "/description.xml",
|
||||||
@@ -247,13 +258,13 @@ def installed() {
|
|||||||
|
|
||||||
def updated() {
|
def updated() {
|
||||||
log.trace "Updated with settings: ${settings}"
|
log.trace "Updated with settings: ${settings}"
|
||||||
unsubscribe()
|
unsubscribe()
|
||||||
unschedule()
|
unschedule()
|
||||||
initialize()
|
initialize()
|
||||||
}
|
}
|
||||||
|
|
||||||
def initialize() {
|
def initialize() {
|
||||||
log.debug "Initializing"
|
log.debug "Initializing"
|
||||||
unsubscribe(bridge)
|
unsubscribe(bridge)
|
||||||
state.inBulbDiscovery = false
|
state.inBulbDiscovery = false
|
||||||
state.bridgeRefreshCount = 0
|
state.bridgeRefreshCount = 0
|
||||||
@@ -282,18 +293,18 @@ def uninstalled(){
|
|||||||
def bulbListHandler(hub, data = "") {
|
def bulbListHandler(hub, data = "") {
|
||||||
def msg = "Bulbs list not processed. Only while in settings menu."
|
def msg = "Bulbs list not processed. Only while in settings menu."
|
||||||
def bulbs = [:]
|
def bulbs = [:]
|
||||||
if (state.inBulbDiscovery) {
|
if (state.inBulbDiscovery) {
|
||||||
def logg = ""
|
def logg = ""
|
||||||
log.trace "Adding bulbs to state..."
|
log.trace "Adding bulbs to state..."
|
||||||
state.bridgeProcessedLightList = true
|
state.bridgeProcessedLightList = true
|
||||||
def object = new groovy.json.JsonSlurper().parseText(data)
|
def object = new groovy.json.JsonSlurper().parseText(data)
|
||||||
object.each { k,v ->
|
object.each { k,v ->
|
||||||
if (v instanceof Map)
|
if (v instanceof Map)
|
||||||
bulbs[k] = [id: k, name: v.name, type: v.type, hub:hub]
|
bulbs[k] = [id: k, name: v.name, type: v.type, hub:hub]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
def bridge = null
|
def bridge = null
|
||||||
if (selectedHue)
|
if (selectedHue)
|
||||||
bridge = getChildDevice(selectedHue)
|
bridge = getChildDevice(selectedHue)
|
||||||
bridge.sendEvent(name: "bulbList", value: hub, data: bulbs, isStateChange: true, displayed: false)
|
bridge.sendEvent(name: "bulbList", value: hub, data: bulbs, isStateChange: true, displayed: false)
|
||||||
msg = "${bulbs.size()} bulbs found. ${bulbs}"
|
msg = "${bulbs.size()} bulbs found. ${bulbs}"
|
||||||
@@ -315,16 +326,15 @@ def addBulbs() {
|
|||||||
d = addChildDevice("smartthings", "Hue Bulb", dni, newHueBulb?.value.hub, ["label":newHueBulb?.value.name])
|
d = addChildDevice("smartthings", "Hue Bulb", dni, newHueBulb?.value.hub, ["label":newHueBulb?.value.name])
|
||||||
}
|
}
|
||||||
log.debug "created ${d.displayName} with id $dni"
|
log.debug "created ${d.displayName} with id $dni"
|
||||||
d.refresh()
|
|
||||||
} else {
|
} else {
|
||||||
log.debug "$dni in not longer paired to the Hue Bridge or ID changed"
|
log.debug "$dni in not longer paired to the Hue Bridge or ID changed"
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
//backwards compatable
|
//backwards compatable
|
||||||
newHueBulb = bulbs.find { (app.id + "/" + it.id) == dni }
|
newHueBulb = bulbs.find { (app.id + "/" + it.id) == dni }
|
||||||
d = addChildDevice("smartthings", "Hue Bulb", dni, newHueBulb?.hub, ["label":newHueBulb?.name])
|
d = addChildDevice("smartthings", "Hue Bulb", dni, newHueBulb?.hub, ["label":newHueBulb?.name])
|
||||||
d.refresh()
|
|
||||||
}
|
}
|
||||||
|
d.refresh()
|
||||||
} else {
|
} else {
|
||||||
log.debug "found ${d.displayName} with id $dni already exists, type: '$d.typeName'"
|
log.debug "found ${d.displayName} with id $dni already exists, type: '$d.typeName'"
|
||||||
if (bulbs instanceof java.util.Map) {
|
if (bulbs instanceof java.util.Map) {
|
||||||
@@ -345,7 +355,7 @@ def addBridge() {
|
|||||||
def d = getChildDevice(selectedHue)
|
def d = getChildDevice(selectedHue)
|
||||||
if(!d) {
|
if(!d) {
|
||||||
// compatibility with old devices
|
// compatibility with old devices
|
||||||
def newbridge = true
|
def newbridge = true
|
||||||
childDevices.each {
|
childDevices.each {
|
||||||
if (it.getDeviceDataByName("mac")) {
|
if (it.getDeviceDataByName("mac")) {
|
||||||
def newDNI = "${it.getDeviceDataByName("mac")}"
|
def newDNI = "${it.getDeviceDataByName("mac")}"
|
||||||
@@ -355,10 +365,10 @@ def addBridge() {
|
|||||||
it.setDeviceNetworkId("${newDNI}")
|
it.setDeviceNetworkId("${newDNI}")
|
||||||
if (oldDNI == selectedHue)
|
if (oldDNI == selectedHue)
|
||||||
app.updateSetting("selectedHue", newDNI)
|
app.updateSetting("selectedHue", newDNI)
|
||||||
newbridge = false
|
newbridge = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (newbridge) {
|
if (newbridge) {
|
||||||
d = addChildDevice("smartthings", "Hue Bridge", selectedHue, vbridge.value.hub)
|
d = addChildDevice("smartthings", "Hue Bridge", selectedHue, vbridge.value.hub)
|
||||||
log.debug "created ${d.displayName} with id ${d.deviceNetworkId}"
|
log.debug "created ${d.displayName} with id ${d.deviceNetworkId}"
|
||||||
@@ -369,13 +379,13 @@ def addBridge() {
|
|||||||
childDevice.sendEvent(name: "networkAddress", value: vbridge.value.ip + ":" + vbridge.value.port)
|
childDevice.sendEvent(name: "networkAddress", value: vbridge.value.ip + ":" + vbridge.value.port)
|
||||||
childDevice.updateDataValue("networkAddress", vbridge.value.ip + ":" + vbridge.value.port)
|
childDevice.updateDataValue("networkAddress", vbridge.value.ip + ":" + vbridge.value.port)
|
||||||
} else {
|
} else {
|
||||||
childDevice.sendEvent(name: "networkAddress", value: convertHexToIP(vbridge.value.ip) + ":" + convertHexToInt(vbridge.value.port))
|
childDevice.sendEvent(name: "networkAddress", value: convertHexToIP(vbridge.value.ip) + ":" + convertHexToInt(vbridge.value.port))
|
||||||
childDevice.updateDataValue("networkAddress", convertHexToIP(vbridge.value.ip) + ":" + convertHexToInt(vbridge.value.port))
|
childDevice.updateDataValue("networkAddress", convertHexToIP(vbridge.value.ip) + ":" + convertHexToInt(vbridge.value.port))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
childDevice.sendEvent(name: "networkAddress", value: convertHexToIP(vbridge.value.networkAddress) + ":" + convertHexToInt(vbridge.value.deviceAddress))
|
childDevice.sendEvent(name: "networkAddress", value: convertHexToIP(vbridge.value.networkAddress) + ":" + convertHexToInt(vbridge.value.deviceAddress))
|
||||||
childDevice.updateDataValue("networkAddress", convertHexToIP(vbridge.value.networkAddress) + ":" + convertHexToInt(vbridge.value.deviceAddress))
|
childDevice.updateDataValue("networkAddress", convertHexToIP(vbridge.value.networkAddress) + ":" + convertHexToInt(vbridge.value.deviceAddress))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log.debug "found ${d.displayName} with id $selectedHue already exists"
|
log.debug "found ${d.displayName} with id $selectedHue already exists"
|
||||||
@@ -437,7 +447,7 @@ def locationHandler(evt) {
|
|||||||
dstate.name = "Philips hue ($ip)"
|
dstate.name = "Philips hue ($ip)"
|
||||||
d.sendEvent(name:"networkAddress", value: host)
|
d.sendEvent(name:"networkAddress", value: host)
|
||||||
d.updateDataValue("networkAddress", host)
|
d.updateDataValue("networkAddress", host)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -456,7 +466,7 @@ def locationHandler(evt) {
|
|||||||
log.error "/description.xml returned a bridge that didn't exist"
|
log.error "/description.xml returned a bridge that didn't exist"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if(headerString?.contains("json") && isValidSource(parsedEvent.mac)) {
|
} else if(headerString?.contains("json")) {
|
||||||
log.trace "description.xml response (application/json)"
|
log.trace "description.xml response (application/json)"
|
||||||
def body = new groovy.json.JsonSlurper().parseText(parsedEvent.body)
|
def body = new groovy.json.JsonSlurper().parseText(parsedEvent.body)
|
||||||
if (body.success != null) {
|
if (body.success != null) {
|
||||||
@@ -495,21 +505,16 @@ def doDeviceSync(){
|
|||||||
discoverBridges()
|
discoverBridges()
|
||||||
}
|
}
|
||||||
|
|
||||||
def isValidSource(macAddress) {
|
|
||||||
def vbridges = getVerifiedHueBridges()
|
|
||||||
return (vbridges?.find {"${it.value.mac}" == macAddress}) != null
|
|
||||||
}
|
|
||||||
|
|
||||||
/////////////////////////////////////
|
/////////////////////////////////////
|
||||||
//CHILD DEVICE METHODS
|
//CHILD DEVICE METHODS
|
||||||
/////////////////////////////////////
|
/////////////////////////////////////
|
||||||
|
|
||||||
def parse(childDevice, description) {
|
def parse(childDevice, description) {
|
||||||
def parsedEvent = parseLanMessage(description)
|
def parsedEvent = parseLanMessage(description)
|
||||||
if (parsedEvent.headers && parsedEvent.body) {
|
if (parsedEvent.headers && parsedEvent.body) {
|
||||||
def headerString = parsedEvent.headers.toString()
|
def headerString = parsedEvent.headers.toString()
|
||||||
def bodyString = parsedEvent.body.toString()
|
def bodyString = parsedEvent.body.toString()
|
||||||
if (headerString?.contains("json")) {
|
if (headerString?.contains("json")) {
|
||||||
def body
|
def body
|
||||||
try {
|
try {
|
||||||
body = new groovy.json.JsonSlurper().parseText(bodyString)
|
body = new groovy.json.JsonSlurper().parseText(bodyString)
|
||||||
@@ -517,11 +522,11 @@ def parse(childDevice, description) {
|
|||||||
log.warn "Parsing Body failed - trying again..."
|
log.warn "Parsing Body failed - trying again..."
|
||||||
poll()
|
poll()
|
||||||
}
|
}
|
||||||
if (body instanceof java.util.HashMap) {
|
if (body instanceof java.util.HashMap) {
|
||||||
//poll response
|
//poll response
|
||||||
def bulbs = getChildDevices()
|
def bulbs = getChildDevices()
|
||||||
for (bulb in body) {
|
for (bulb in body) {
|
||||||
def d = bulbs.find{it.deviceNetworkId == "${app.id}/${bulb.key}"}
|
def d = bulbs.find{it.deviceNetworkId == "${app.id}/${bulb.key}"}
|
||||||
if (d) {
|
if (d) {
|
||||||
if (bulb.value.state?.reachable) {
|
if (bulb.value.state?.reachable) {
|
||||||
sendEvent(d.deviceNetworkId, [name: "switch", value: bulb.value?.state?.on ? "on" : "off"])
|
sendEvent(d.deviceNetworkId, [name: "switch", value: bulb.value?.state?.on ? "on" : "off"])
|
||||||
@@ -536,18 +541,18 @@ def parse(childDevice, description) {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
sendEvent(d.deviceNetworkId, [name: "switch", value: "off"])
|
sendEvent(d.deviceNetworkId, [name: "switch", value: "off"])
|
||||||
sendEvent(d.deviceNetworkId, [name: "level", value: 100])
|
sendEvent(d.deviceNetworkId, [name: "level", value: 100])
|
||||||
if (bulb.value.state.sat) {
|
if (bulb.value.state.sat) {
|
||||||
def hue = 23
|
def hue = 23
|
||||||
def sat = 56
|
def sat = 56
|
||||||
def hex = colorUtil.hslToHex(23, 56)
|
def hex = colorUtil.hslToHex(23, 56)
|
||||||
sendEvent(d.deviceNetworkId, [name: "color", value: hex])
|
sendEvent(d.deviceNetworkId, [name: "color", value: hex])
|
||||||
sendEvent(d.deviceNetworkId, [name: "hue", value: hue])
|
sendEvent(d.deviceNetworkId, [name: "hue", value: hue])
|
||||||
sendEvent(d.deviceNetworkId, [name: "saturation", value: sat])
|
sendEvent(d.deviceNetworkId, [name: "saturation", value: sat])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{ //put response
|
{ //put response
|
||||||
@@ -596,27 +601,13 @@ def parse(childDevice, description) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log.debug "parse - got something other than headers,body..."
|
log.debug "parse - got something other than headers,body..."
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def hubVerification(bodytext) {
|
|
||||||
log.trace "Bridge sent back description.xml for verification"
|
|
||||||
def body = new XmlSlurper().parseText(bodytext)
|
|
||||||
if (body?.device?.modelName?.text().startsWith("Philips hue bridge")) {
|
|
||||||
def bridges = getHueBridges()
|
|
||||||
def bridge = bridges.find {it?.key?.contains(body?.device?.UDN?.text())}
|
|
||||||
if (bridge) {
|
|
||||||
bridge.value << [name:body?.device?.friendlyName?.text(), serialNumber:body?.device?.serialNumber?.text(), verified: true]
|
|
||||||
} else {
|
|
||||||
log.error "/description.xml returned a bridge that didn't exist"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def on(childDevice) {
|
def on(childDevice) {
|
||||||
log.debug "Executing 'on'"
|
log.debug "Executing 'on'"
|
||||||
put("lights/${getId(childDevice)}/state", [on: true])
|
put("lights/${getId(childDevice)}/state", [on: true])
|
||||||
@@ -631,7 +622,7 @@ def off(childDevice) {
|
|||||||
|
|
||||||
def setLevel(childDevice, percent) {
|
def setLevel(childDevice, percent) {
|
||||||
log.debug "Executing 'setLevel'"
|
log.debug "Executing 'setLevel'"
|
||||||
def level
|
def level
|
||||||
if (percent == 1) level = 1 else level = Math.min(Math.round(percent * 255 / 100), 255)
|
if (percent == 1) level = 1 else level = Math.min(Math.round(percent * 255 / 100), 255)
|
||||||
put("lights/${getId(childDevice)}/state", [bri: level, on: percent > 0])
|
put("lights/${getId(childDevice)}/state", [bri: level, on: percent > 0])
|
||||||
}
|
}
|
||||||
@@ -648,62 +639,36 @@ def setHue(childDevice, percent) {
|
|||||||
put("lights/${getId(childDevice)}/state", [hue: level])
|
put("lights/${getId(childDevice)}/state", [hue: level])
|
||||||
}
|
}
|
||||||
|
|
||||||
def setColorTemperature(childDevice, huesettings) {
|
def setColor(childDevice, huesettings) {
|
||||||
log.debug "Executing 'setColorTemperature($huesettings)'"
|
log.debug "Executing 'setColor($huesettings)'"
|
||||||
def ct = Math.round(Math.abs((huesettings / 12.96829971181556) - 654))
|
def hue = Math.min(Math.round(huesettings.hue * 65535 / 100), 65535)
|
||||||
def value = [ct: ct, on: true]
|
def sat = Math.min(Math.round(huesettings.saturation * 255 / 100), 255)
|
||||||
log.trace "sending command $value"
|
def alert = huesettings.alert ? huesettings.alert : "none"
|
||||||
|
def transition = huesettings.transition ? huesettings.transition : 4
|
||||||
|
|
||||||
|
def value = [sat: sat, hue: hue, alert: alert, transitiontime: transition]
|
||||||
|
if (huesettings.level != null) {
|
||||||
|
if (huesettings.level == 1) value.bri = 1 else value.bri = Math.min(Math.round(huesettings.level * 255 / 100), 255)
|
||||||
|
value.on = value.bri > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if (huesettings.switch) {
|
||||||
|
value.on = huesettings.switch == "on"
|
||||||
|
}
|
||||||
|
|
||||||
|
log.debug "sending command $value"
|
||||||
put("lights/${getId(childDevice)}/state", value)
|
put("lights/${getId(childDevice)}/state", value)
|
||||||
}
|
}
|
||||||
|
|
||||||
def setColor(childDevice, huesettings) {
|
|
||||||
log.debug "Executing 'setColor($huesettings)'"
|
|
||||||
|
|
||||||
def value = [:]
|
|
||||||
def hue = null
|
|
||||||
def sat = null
|
|
||||||
def xy = null
|
|
||||||
|
|
||||||
if (huesettings.hex != null) {
|
|
||||||
value.xy = getHextoXY(huesettings.hex)
|
|
||||||
} else {
|
|
||||||
if (huesettings.hue != null)
|
|
||||||
value.hue = Math.min(Math.round(huesettings.hue * 65535 / 100), 65535)
|
|
||||||
if (huesettings.saturation != null)
|
|
||||||
value.sat = Math.min(Math.round(huesettings.saturation * 255 / 100), 255)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Default behavior is to turn light on
|
|
||||||
value.on = true
|
|
||||||
|
|
||||||
if (huesettings.level != null) {
|
|
||||||
if (huesettings.level <= 0)
|
|
||||||
value.on = false
|
|
||||||
else if (huesettings.level == 1)
|
|
||||||
value.bri = 1
|
|
||||||
else
|
|
||||||
value.bri = Math.min(Math.round(huesettings.level * 255 / 100), 255)
|
|
||||||
}
|
|
||||||
value.alert = huesettings.alert ? huesettings.alert : "none"
|
|
||||||
value.transition = huesettings.transition ? huesettings.transition : 4
|
|
||||||
|
|
||||||
// Make sure to turn off light if requested
|
|
||||||
if (huesettings.switch == "off")
|
|
||||||
value.on = false
|
|
||||||
|
|
||||||
log.debug "sending command $value"
|
|
||||||
put("lights/${getId(childDevice)}/state", value)
|
|
||||||
return "Color set to $value"
|
|
||||||
}
|
|
||||||
|
|
||||||
def nextLevel(childDevice) {
|
def nextLevel(childDevice) {
|
||||||
def level = device.latestValue("level") as Integer ?: 0
|
def level = device.latestValue("level") as Integer ?: 0
|
||||||
if (level < 100) {
|
if (level < 100) {
|
||||||
level = Math.min(25 * (Math.round(level / 25) + 1), 100) as Integer
|
level = Math.min(25 * (Math.round(level / 25) + 1), 100) as Integer
|
||||||
} else {
|
}
|
||||||
level = 25
|
else {
|
||||||
}
|
level = 25
|
||||||
setLevel(childDevice,level)
|
}
|
||||||
|
setLevel(childDevice,level)
|
||||||
}
|
}
|
||||||
|
|
||||||
private getId(childDevice) {
|
private getId(childDevice) {
|
||||||
@@ -730,7 +695,7 @@ HOST: ${host}
|
|||||||
}
|
}
|
||||||
|
|
||||||
private put(path, body) {
|
private put(path, body) {
|
||||||
def host = getBridgeIP()
|
def host = getBridgeIP()
|
||||||
def uri = "/api/${state.username}/$path"
|
def uri = "/api/${state.username}/$path"
|
||||||
def bodyJSON = new groovy.json.JsonBuilder(body).toString()
|
def bodyJSON = new groovy.json.JsonBuilder(body).toString()
|
||||||
def length = bodyJSON.getBytes().size().toString()
|
def length = bodyJSON.getBytes().size().toString()
|
||||||
@@ -756,11 +721,11 @@ private getBridgeIP() {
|
|||||||
host = d.getDeviceDataByName("networkAddress")
|
host = d.getDeviceDataByName("networkAddress")
|
||||||
else
|
else
|
||||||
host = d.latestState('networkAddress').stringValue
|
host = d.latestState('networkAddress').stringValue
|
||||||
}
|
}
|
||||||
if (host == null || host == "") {
|
if (host == null || host == "") {
|
||||||
def serialNumber = selectedHue
|
def serialNumber = selectedHue
|
||||||
def bridge = getHueBridges().find { it?.value?.serialNumber?.equalsIgnoreCase(serialNumber) }?.value
|
def bridge = getHueBridges().find { it?.value?.serialNumber?.equalsIgnoreCase(serialNumber) }?.value
|
||||||
if (!bridge) {
|
if (!bridge) {
|
||||||
bridge = getHueBridges().find { it?.value?.mac?.equalsIgnoreCase(serialNumber) }?.value
|
bridge = getHueBridges().find { it?.value?.mac?.equalsIgnoreCase(serialNumber) }?.value
|
||||||
}
|
}
|
||||||
if (bridge?.ip && bridge?.port) {
|
if (bridge?.ip && bridge?.port) {
|
||||||
@@ -770,63 +735,12 @@ private getBridgeIP() {
|
|||||||
host = "${convertHexToIP(bridge?.ip)}:${convertHexToInt(bridge?.port)}"
|
host = "${convertHexToIP(bridge?.ip)}:${convertHexToInt(bridge?.port)}"
|
||||||
} else if (bridge?.networkAddress && bridge?.deviceAddress)
|
} else if (bridge?.networkAddress && bridge?.deviceAddress)
|
||||||
host = "${convertHexToIP(bridge?.networkAddress)}:${convertHexToInt(bridge?.deviceAddress)}"
|
host = "${convertHexToIP(bridge?.networkAddress)}:${convertHexToInt(bridge?.deviceAddress)}"
|
||||||
}
|
}
|
||||||
log.trace "Bridge: $selectedHue - Host: $host"
|
log.trace "Bridge: $selectedHue - Host: $host"
|
||||||
}
|
}
|
||||||
return host
|
return host
|
||||||
}
|
}
|
||||||
|
|
||||||
private getHextoXY(String colorStr) {
|
|
||||||
// For the hue bulb the corners of the triangle are:
|
|
||||||
// -Red: 0.675, 0.322
|
|
||||||
// -Green: 0.4091, 0.518
|
|
||||||
// -Blue: 0.167, 0.04
|
|
||||||
|
|
||||||
def cred = Integer.valueOf( colorStr.substring( 1, 3 ), 16 )
|
|
||||||
def cgreen = Integer.valueOf( colorStr.substring( 3, 5 ), 16 )
|
|
||||||
def cblue = Integer.valueOf( colorStr.substring( 5, 7 ), 16 )
|
|
||||||
|
|
||||||
double[] normalizedToOne = new double[3];
|
|
||||||
normalizedToOne[0] = (cred / 255);
|
|
||||||
normalizedToOne[1] = (cgreen / 255);
|
|
||||||
normalizedToOne[2] = (cblue / 255);
|
|
||||||
float red, green, blue;
|
|
||||||
|
|
||||||
// Make red more vivid
|
|
||||||
if (normalizedToOne[0] > 0.04045) {
|
|
||||||
red = (float) Math.pow(
|
|
||||||
(normalizedToOne[0] + 0.055) / (1.0 + 0.055), 2.4);
|
|
||||||
} else {
|
|
||||||
red = (float) (normalizedToOne[0] / 12.92);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make green more vivid
|
|
||||||
if (normalizedToOne[1] > 0.04045) {
|
|
||||||
green = (float) Math.pow((normalizedToOne[1] + 0.055) / (1.0 + 0.055), 2.4);
|
|
||||||
} else {
|
|
||||||
green = (float) (normalizedToOne[1] / 12.92);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make blue more vivid
|
|
||||||
if (normalizedToOne[2] > 0.04045) {
|
|
||||||
blue = (float) Math.pow((normalizedToOne[2] + 0.055) / (1.0 + 0.055), 2.4);
|
|
||||||
} else {
|
|
||||||
blue = (float) (normalizedToOne[2] / 12.92);
|
|
||||||
}
|
|
||||||
|
|
||||||
float X = (float) (red * 0.649926 + green * 0.103455 + blue * 0.197109);
|
|
||||||
float Y = (float) (red * 0.234327 + green * 0.743075 + blue * 0.022598);
|
|
||||||
float Z = (float) (red * 0.0000000 + green * 0.053077 + blue * 1.035763);
|
|
||||||
|
|
||||||
float x = (X != 0 ? X / (X + Y + Z) : 0);
|
|
||||||
float y = (Y != 0 ? Y / (X + Y + Z) : 0);
|
|
||||||
|
|
||||||
double[] xy = new double[2];
|
|
||||||
xy[0] = x;
|
|
||||||
xy[1] = y;
|
|
||||||
return xy;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Integer convertHexToInt(hex) {
|
private Integer convertHexToInt(hex) {
|
||||||
Integer.parseInt(hex,16)
|
Integer.parseInt(hex,16)
|
||||||
}
|
}
|
||||||
@@ -850,10 +764,14 @@ private String convertHexToIP(hex) {
|
|||||||
[convertHexToInt(hex[0..1]),convertHexToInt(hex[2..3]),convertHexToInt(hex[4..5]),convertHexToInt(hex[6..7])].join(".")
|
[convertHexToInt(hex[0..1]),convertHexToInt(hex[2..3]),convertHexToInt(hex[4..5]),convertHexToInt(hex[6..7])].join(".")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Boolean canInstallLabs() {
|
||||||
|
return hasAllHubsOver("000.011.00603")
|
||||||
|
}
|
||||||
|
|
||||||
private Boolean hasAllHubsOver(String desiredFirmware) {
|
private Boolean hasAllHubsOver(String desiredFirmware) {
|
||||||
return realHubFirmwareVersions.every { fw -> fw >= desiredFirmware }
|
return realHubFirmwareVersions.every { fw -> fw >= desiredFirmware }
|
||||||
}
|
}
|
||||||
|
|
||||||
private List getRealHubFirmwareVersions() {
|
private List getRealHubFirmwareVersions() {
|
||||||
return location.hubs*.firmwareVersionString.findAll { it }
|
return location.hubs*.firmwareVersionString.findAll { it }
|
||||||
}
|
}
|
||||||
@@ -89,7 +89,7 @@ mappings {
|
|||||||
}
|
}
|
||||||
|
|
||||||
def getServerUrl() { return "https://graph.api.smartthings.com" }
|
def getServerUrl() { return "https://graph.api.smartthings.com" }
|
||||||
def getServercallbackUrl() { "https://graph.api.smartthings.com/oauth/callback" }
|
def getCallbackUrl() { "https://graph.api.smartthings.com/oauth/callback" }
|
||||||
def getBuildRedirectUrl() { "${serverUrl}/oauth/initialize?appId=${app.id}&access_token=${state.accessToken}&apiServerUrl=${apiServerUrl}" }
|
def getBuildRedirectUrl() { "${serverUrl}/oauth/initialize?appId=${app.id}&access_token=${state.accessToken}&apiServerUrl=${apiServerUrl}" }
|
||||||
|
|
||||||
def authPage() {
|
def authPage() {
|
||||||
@@ -166,7 +166,7 @@ def callback() {
|
|||||||
|
|
||||||
def init() {
|
def init() {
|
||||||
log.debug "Requesting Code"
|
log.debug "Requesting Code"
|
||||||
def oauthParams = [client_id: "${appSettings.clientId}", scope: "remote", response_type: "code", redirect_uri: "${servercallbackUrl}" ]
|
def oauthParams = [client_id: "${appSettings.clientId}", scope: "remote", response_type: "code", redirect_uri: "${callbackUrl}" ]
|
||||||
redirect(location: "https://home.myharmony.com/oauth2/authorize?${toQueryString(oauthParams)}")
|
redirect(location: "https://home.myharmony.com/oauth2/authorize?${toQueryString(oauthParams)}")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -419,11 +419,9 @@ def addDevice() {
|
|||||||
def d = getChildDevice(dni)
|
def d = getChildDevice(dni)
|
||||||
if(!d) {
|
if(!d) {
|
||||||
def newAction = state.HarmonyActivities.find { it.key == dni }
|
def newAction = state.HarmonyActivities.find { it.key == dni }
|
||||||
if (newAction) {
|
d = addChildDevice("smartthings", "Harmony Activity", dni, null, [label:"${newAction.value} [Harmony Activity]"])
|
||||||
d = addChildDevice("smartthings", "Harmony Activity", dni, null, [label:"${newAction.value} [Harmony Activity]"])
|
log.trace "created ${d.displayName} with id $dni"
|
||||||
log.trace "created ${d.displayName} with id $dni"
|
poll()
|
||||||
poll()
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
log.trace "found ${d.displayName} with id $dni already exists"
|
log.trace "found ${d.displayName} with id $dni already exists"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,31 +0,0 @@
|
|||||||
'''Acceleration Detected'''.ko=가속이 감지되었을 때
|
|
||||||
'''Arrival Of'''.ko=도착했을 때
|
|
||||||
'''Both Push and SMS?'''.ko=푸시 알람과 SMS 모두 사용
|
|
||||||
'''Button Pushed'''.ko=버튼이 눌렸을 때
|
|
||||||
'''Contact Closes'''.ko=닫힘이 감지되었을 때
|
|
||||||
'''Contact Opens'''.ko=열림이 감지되었을 때
|
|
||||||
'''Departure Of'''.ko=출발할 때
|
|
||||||
'''Message Text'''.ko=문자 메시지
|
|
||||||
'''Minutes'''.ko=메시지 전송 간격(분)
|
|
||||||
'''Motion Here'''.ko=움직임이 감지되었을 때
|
|
||||||
'''Phone Number (for SMS, optional)'''.ko=전화번호 (옵션)
|
|
||||||
'''Receive notifications when anything happens in your home.'''.ko=집 안에 무슨 일이 일어나면 알림이 전송됩니다.
|
|
||||||
'''Smoke Detected'''.ko=연기가 감지되었을 때
|
|
||||||
'''Switch Turned Off'''.ko=스위치가 꺼졌을 때
|
|
||||||
'''Switch Turned On'''.ko=스위치가 켜졌을 때
|
|
||||||
'''Choose one or more, when...'''.ko=다음 상황 중 하나 이상 선택
|
|
||||||
'''Yes'''.ko=예
|
|
||||||
'''No'''.ko=아니요
|
|
||||||
'''Send this message (optional, sends standard status message if not specified)'''.ko=메시지 작성 (작성하지 않을 경우 디폴트 메시지 전송)
|
|
||||||
'''Via a push notification and/or an SMS message'''.ko=푸시 알람 및 SMS 설정
|
|
||||||
'''Set for specific mode(s)'''.ko=특정 상태 설정
|
|
||||||
'''Tap to set'''.ko=눌러서 설정
|
|
||||||
'''Minimum time between messages (optional, defaults to every message)'''.ko=메시지 전송 간격 설정
|
|
||||||
'''If outside the US please make sure to enter the proper country code'''.ko=미국 이외 국가에 거주한다면 국가 코드와 함께 입력하여 주세요.
|
|
||||||
'''Water Sensor Wet'''.ko=누수가 감지되었을 때
|
|
||||||
'''{{ triggerEvent.linkText }} has arrived at the {{ location.name }}'''.ko={{ triggerEvent.linkText }}님이 {{ location.name }}에 도착했습니다
|
|
||||||
'''{{ triggerEvent.linkText }} has arrived at {{ location.name }}'''.ko={{ triggerEvent.linkText }}님이 {{ location.name }}에 도착했습니다
|
|
||||||
'''{{ triggerEvent.linkText }} has left the {{ location.name }}'''.ko={{ triggerEvent.linkText }}님이 {{ location.name }}을(를) 떠났습니다
|
|
||||||
'''{{ triggerEvent.linkText }} has left {{ location.name }}'''.ko={{ triggerEvent.linkText }}님이 {{ location.name }}을(를) 떠났습니다
|
|
||||||
'''Assign a name'''.ko=이름 설정
|
|
||||||
'''Choose Modes'''.ko=상태 선택
|
|
||||||
@@ -20,19 +20,19 @@
|
|||||||
* 2014-10-03: Added capability.button device picker and button.pushed event subscription. For Doorbell.
|
* 2014-10-03: Added capability.button device picker and button.pushed event subscription. For Doorbell.
|
||||||
*/
|
*/
|
||||||
definition(
|
definition(
|
||||||
name: "Notify Me When",
|
name: "Notify Me When",
|
||||||
namespace: "smartthings",
|
namespace: "smartthings",
|
||||||
author: "SmartThings",
|
author: "SmartThings",
|
||||||
description: "Receive notifications when anything happens in your home.",
|
description: "Receive notifications when anything happens in your home.",
|
||||||
category: "Convenience",
|
category: "Convenience",
|
||||||
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Meta/window_contact.png",
|
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Meta/window_contact.png",
|
||||||
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Meta/window_contact@2x.png"
|
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Meta/window_contact@2x.png"
|
||||||
)
|
)
|
||||||
|
|
||||||
preferences {
|
preferences {
|
||||||
section("Choose one or more, when..."){
|
section("Choose one or more, when..."){
|
||||||
input "button", "capability.button", title: "Button Pushed", required: false, multiple: true //tw
|
input "button", "capability.button", title: "Button Pushed", required: false, multiple: true //tw
|
||||||
input "motion", "capability.motionSensor", title: "Motion Here", required: false, multiple: true
|
input "motion", "capability.motionSensor", title: "Motion Here", required: false, multiple: true
|
||||||
input "contact", "capability.contactSensor", title: "Contact Opens", required: false, multiple: true
|
input "contact", "capability.contactSensor", title: "Contact Opens", required: false, multiple: true
|
||||||
input "contactClosed", "capability.contactSensor", title: "Contact Closes", required: false, multiple: true
|
input "contactClosed", "capability.contactSensor", title: "Contact Closes", required: false, multiple: true
|
||||||
input "acceleration", "capability.accelerationSensor", title: "Acceleration Detected", required: false, multiple: true
|
input "acceleration", "capability.accelerationSensor", title: "Acceleration Detected", required: false, multiple: true
|
||||||
@@ -47,11 +47,11 @@ preferences {
|
|||||||
input "messageText", "text", title: "Message Text", required: false
|
input "messageText", "text", title: "Message Text", required: false
|
||||||
}
|
}
|
||||||
section("Via a push notification and/or an SMS message"){
|
section("Via a push notification and/or an SMS message"){
|
||||||
input("recipients", "contact", title: "Send notifications to") {
|
input("recipients", "contact", title: "Send notifications to") {
|
||||||
input "phone", "phone", title: "Phone Number (for SMS, optional)", required: false
|
input "phone", "phone", title: "Phone Number (for SMS, optional)", required: false
|
||||||
paragraph "If outside the US please make sure to enter the proper country code"
|
paragraph "If outside the US please make sure to enter the proper country code"
|
||||||
input "pushAndPhone", "enum", title: "Both Push and SMS?", required: false, options: ["Yes", "No"]
|
input "pushAndPhone", "enum", title: "Both Push and SMS?", required: false, options: ["Yes", "No"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
section("Minimum time between messages (optional, defaults to every message)") {
|
section("Minimum time between messages (optional, defaults to every message)") {
|
||||||
input "frequency", "decimal", title: "Minutes", required: false
|
input "frequency", "decimal", title: "Minutes", required: false
|
||||||
@@ -71,7 +71,7 @@ def updated() {
|
|||||||
|
|
||||||
def subscribeToEvents() {
|
def subscribeToEvents() {
|
||||||
subscribe(button, "button.pushed", eventHandler) //tw
|
subscribe(button, "button.pushed", eventHandler) //tw
|
||||||
subscribe(contact, "contact.open", eventHandler)
|
subscribe(contact, "contact.open", eventHandler)
|
||||||
subscribe(contactClosed, "contact.closed", eventHandler)
|
subscribe(contactClosed, "contact.closed", eventHandler)
|
||||||
subscribe(acceleration, "acceleration.active", eventHandler)
|
subscribe(acceleration, "acceleration.active", eventHandler)
|
||||||
subscribe(motion, "motion.active", eventHandler)
|
subscribe(motion, "motion.active", eventHandler)
|
||||||
@@ -99,55 +99,49 @@ def eventHandler(evt) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private sendMessage(evt) {
|
private sendMessage(evt) {
|
||||||
String msg = messageText
|
def msg = messageText ?: defaultText(evt)
|
||||||
Map options = [:]
|
|
||||||
|
|
||||||
if (!messageText) {
|
|
||||||
msg = defaultText(evt)
|
|
||||||
options = [translatable: true, triggerEvent: evt]
|
|
||||||
}
|
|
||||||
log.debug "$evt.name:$evt.value, pushAndPhone:$pushAndPhone, '$msg'"
|
log.debug "$evt.name:$evt.value, pushAndPhone:$pushAndPhone, '$msg'"
|
||||||
|
|
||||||
if (location.contactBookEnabled) {
|
if (location.contactBookEnabled) {
|
||||||
sendNotificationToContacts(msg, recipients, options)
|
sendNotificationToContacts(msg, recipients)
|
||||||
} else {
|
}
|
||||||
if (!phone || pushAndPhone != 'No') {
|
else {
|
||||||
log.debug 'sending push'
|
|
||||||
options.method = 'push'
|
|
||||||
//sendPush(msg)
|
|
||||||
}
|
|
||||||
if (phone) {
|
|
||||||
options.phone = phone
|
|
||||||
log.debug 'sending SMS'
|
|
||||||
//sendSms(phone, msg)
|
|
||||||
}
|
|
||||||
sendNotification(msg, options)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if (!phone || pushAndPhone != "No") {
|
||||||
|
log.debug "sending push"
|
||||||
|
sendPush(msg)
|
||||||
|
}
|
||||||
|
if (phone) {
|
||||||
|
log.debug "sending SMS"
|
||||||
|
sendSms(phone, msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
if (frequency) {
|
if (frequency) {
|
||||||
state[evt.deviceId] = now()
|
state[evt.deviceId] = now()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private defaultText(evt) {
|
private defaultText(evt) {
|
||||||
if (evt.name == 'presence') {
|
if (evt.name == "presence") {
|
||||||
if (evt.value == 'present') {
|
if (evt.value == "present") {
|
||||||
if (includeArticle) {
|
if (includeArticle) {
|
||||||
'{{ triggerEvent.linkText }} has arrived at the {{ location.name }}'
|
"$evt.linkText has arrived at the $location.name"
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
'{{ triggerEvent.linkText }} has arrived at {{ location.name }}'
|
"$evt.linkText has arrived at $location.name"
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (includeArticle) {
|
|
||||||
'{{ triggerEvent.linkText }} has left the {{ location.name }}'
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
'{{ triggerEvent.linkText }} has left {{ location.name }}'
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
else {
|
||||||
'{{ triggerEvent.descriptionText }}'
|
if (includeArticle) {
|
||||||
|
"$evt.linkText has left the $location.name"
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
"$evt.linkText has left $location.name"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
evt.descriptionText
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,421 @@
|
|||||||
|
/**
|
||||||
|
* 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:')) {
|
||||||
|
def valueString = part.split(":")[1].trim()
|
||||||
|
event.devicetype = valueString
|
||||||
|
}
|
||||||
|
else if (part.startsWith('mac:')) {
|
||||||
|
def valueString = part.split(":")[1].trim()
|
||||||
|
if (valueString) {
|
||||||
|
event.mac = valueString
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (part.startsWith('networkAddress:')) {
|
||||||
|
def valueString = part.split(":")[1].trim()
|
||||||
|
if (valueString) {
|
||||||
|
event.ip = valueString
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (part.startsWith('deviceAddress:')) {
|
||||||
|
def valueString = part.split(":")[1].trim()
|
||||||
|
if (valueString) {
|
||||||
|
event.port = valueString
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (part.startsWith('ssdpPath:')) {
|
||||||
|
def valueString = part.split(":")[1].trim()
|
||||||
|
if (valueString) {
|
||||||
|
event.ssdpPath = valueString
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (part.startsWith('ssdpUSN:')) {
|
||||||
|
part -= "ssdpUSN:"
|
||||||
|
def valueString = part.trim()
|
||||||
|
if (valueString) {
|
||||||
|
event.ssdpUSN = valueString
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (part.startsWith('ssdpTerm:')) {
|
||||||
|
part -= "ssdpTerm:"
|
||||||
|
def valueString = part.trim()
|
||||||
|
if (valueString) {
|
||||||
|
event.ssdpTerm = valueString
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (part.startsWith('headers')) {
|
||||||
|
part -= "headers:"
|
||||||
|
def valueString = part.trim()
|
||||||
|
if (valueString) {
|
||||||
|
event.headers = valueString
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (part.startsWith('body')) {
|
||||||
|
part -= "body:"
|
||||||
|
def valueString = part.trim()
|
||||||
|
if (valueString) {
|
||||||
|
event.body = valueString
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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 }
|
||||||
|
}
|
||||||
@@ -76,7 +76,7 @@ def mainPage() {
|
|||||||
}
|
}
|
||||||
section{
|
section{
|
||||||
input "actionType", "enum", title: "Action?", required: true, defaultValue: "Bell 1", options: [
|
input "actionType", "enum", title: "Action?", required: true, defaultValue: "Bell 1", options: [
|
||||||
"Custom Message",
|
//"Custom Message",
|
||||||
"Bell 1",
|
"Bell 1",
|
||||||
"Bell 2",
|
"Bell 2",
|
||||||
"Dogs Barking",
|
"Dogs Barking",
|
||||||
@@ -89,7 +89,7 @@ def mainPage() {
|
|||||||
"Someone is arriving",
|
"Someone is arriving",
|
||||||
"Piano",
|
"Piano",
|
||||||
"Lightsaber"]
|
"Lightsaber"]
|
||||||
input "message","text",title:"Play this message", required:false, multiple: false
|
//input "message","text",title:"Play this message", required:false, multiple: false
|
||||||
}
|
}
|
||||||
section {
|
section {
|
||||||
input "sonos", "capability.musicPlayer", title: "On this Speaker player", required: true
|
input "sonos", "capability.musicPlayer", title: "On this Speaker player", required: true
|
||||||
@@ -408,15 +408,13 @@ private loadText() {
|
|||||||
case "Lightsaber":
|
case "Lightsaber":
|
||||||
state.sound = [uri: "http://s3.amazonaws.com/smartapp-media/sonos/lightsaber.mp3", duration: "10"]
|
state.sound = [uri: "http://s3.amazonaws.com/smartapp-media/sonos/lightsaber.mp3", duration: "10"]
|
||||||
break;
|
break;
|
||||||
case "Custom Message":
|
default:
|
||||||
if (message) {
|
/*if (message) {
|
||||||
state.sound = textToSpeech(message instanceof List ? message[0] : message) // not sure why this is (sometimes) needed)
|
state.sound = textToSpeech(message instanceof List ? message[0] : message) // not sure why this is (sometimes) needed)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
state.sound = textToSpeech("You selected the custom message option but did not enter a message in the $app.label Smart App")
|
state.sound = textToSpeech("You selected the custom message option but did not enter a message in the $app.label Smart App")
|
||||||
}
|
}*/
|
||||||
break;
|
|
||||||
default:
|
|
||||||
state.sound = [uri: "http://s3.amazonaws.com/smartapp-media/sonos/bell1.mp3", duration: "10"]
|
state.sound = [uri: "http://s3.amazonaws.com/smartapp-media/sonos/bell1.mp3", duration: "10"]
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,14 +16,14 @@
|
|||||||
* Date: 2013-09-06
|
* Date: 2013-09-06
|
||||||
*/
|
*/
|
||||||
definition(
|
definition(
|
||||||
name: "Wemo (Connect)",
|
name: "Wemo (Connect)",
|
||||||
namespace: "smartthings",
|
namespace: "smartthings",
|
||||||
author: "SmartThings",
|
author: "SmartThings",
|
||||||
description: "Allows you to integrate your WeMo Switch and Wemo Motion sensor with SmartThings.",
|
description: "Allows you to integrate your WeMo Switch and Wemo Motion sensor with SmartThings.",
|
||||||
category: "SmartThings Labs",
|
category: "SmartThings Labs",
|
||||||
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Partner/wemo.png",
|
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Partner/wemo.png",
|
||||||
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Partner/wemo@2x.png",
|
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Partner/wemo@2x.png",
|
||||||
singleInstance: true
|
singleInstance: true
|
||||||
)
|
)
|
||||||
|
|
||||||
preferences {
|
preferences {
|
||||||
@@ -39,7 +39,7 @@ private getFriendlyName(String deviceNetworkId) {
|
|||||||
sendHubCommand(new physicalgraph.device.HubAction("""GET /setup.xml HTTP/1.1
|
sendHubCommand(new physicalgraph.device.HubAction("""GET /setup.xml HTTP/1.1
|
||||||
HOST: ${deviceNetworkId}
|
HOST: ${deviceNetworkId}
|
||||||
|
|
||||||
""", physicalgraph.device.Protocol.LAN, "${deviceNetworkId}", [callback: "setupHandler"]))
|
""", physicalgraph.device.Protocol.LAN, "${deviceNetworkId}"))
|
||||||
}
|
}
|
||||||
|
|
||||||
private verifyDevices() {
|
private verifyDevices() {
|
||||||
@@ -52,13 +52,6 @@ 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()
|
def firstPage()
|
||||||
{
|
{
|
||||||
if(canInstallLabs())
|
if(canInstallLabs())
|
||||||
@@ -69,7 +62,7 @@ def firstPage()
|
|||||||
|
|
||||||
log.debug "REFRESH COUNT :: ${refreshCount}"
|
log.debug "REFRESH COUNT :: ${refreshCount}"
|
||||||
|
|
||||||
ssdpSubscribe()
|
subscribe(location, null, locationHandler, [filterEvents:false])
|
||||||
|
|
||||||
//ssdp request every 25 seconds
|
//ssdp request every 25 seconds
|
||||||
if((refreshCount % 5) == 0) {
|
if((refreshCount % 5) == 0) {
|
||||||
@@ -112,7 +105,9 @@ def devicesDiscovered() {
|
|||||||
def motions = getWemoMotions()
|
def motions = getWemoMotions()
|
||||||
def lightSwitches = getWemoLightSwitches()
|
def lightSwitches = getWemoLightSwitches()
|
||||||
def devices = switches + motions + lightSwitches
|
def devices = switches + motions + lightSwitches
|
||||||
devices?.collect{ [app.id, it.ssdpUSN].join('.') }
|
def list = []
|
||||||
|
|
||||||
|
list = devices?.collect{ [app.id, it.ssdpUSN].join('.') }
|
||||||
}
|
}
|
||||||
|
|
||||||
def switchesDiscovered() {
|
def switchesDiscovered() {
|
||||||
@@ -180,9 +175,8 @@ def updated() {
|
|||||||
|
|
||||||
def initialize() {
|
def initialize() {
|
||||||
unsubscribe()
|
unsubscribe()
|
||||||
unschedule()
|
unschedule()
|
||||||
|
subscribe(location, null, locationHandler, [filterEvents:false])
|
||||||
ssdpSubscribe()
|
|
||||||
|
|
||||||
if (selectedSwitches)
|
if (selectedSwitches)
|
||||||
addSwitches()
|
addSwitches()
|
||||||
@@ -195,7 +189,7 @@ def initialize() {
|
|||||||
|
|
||||||
runIn(5, "subscribeToDevices") //initial subscriptions delayed by 5 seconds
|
runIn(5, "subscribeToDevices") //initial subscriptions delayed by 5 seconds
|
||||||
runIn(10, "refreshDevices") //refresh devices, delayed by 10 seconds
|
runIn(10, "refreshDevices") //refresh devices, delayed by 10 seconds
|
||||||
runEvery5Minutes("refresh")
|
runEvery5Minutes("refresh")
|
||||||
}
|
}
|
||||||
|
|
||||||
def resubscribe() {
|
def resubscribe() {
|
||||||
@@ -205,7 +199,7 @@ def resubscribe() {
|
|||||||
|
|
||||||
def refresh() {
|
def refresh() {
|
||||||
log.debug "refresh() called"
|
log.debug "refresh() called"
|
||||||
doDeviceSync()
|
doDeviceSync()
|
||||||
refreshDevices()
|
refreshDevices()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -234,21 +228,21 @@ def addSwitches() {
|
|||||||
def d
|
def d
|
||||||
if (selectedSwitch) {
|
if (selectedSwitch) {
|
||||||
d = getChildDevices()?.find {
|
d = getChildDevices()?.find {
|
||||||
it.deviceNetworkId == selectedSwitch.value.mac || it.device.getDataValue("mac") == selectedSwitch.value.mac
|
it.dni == selectedSwitch.value.mac || it.device.getDataValue("mac") == selectedSwitch.value.mac
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!d) {
|
if (!d) {
|
||||||
log.debug "Creating WeMo Switch with dni: ${selectedSwitch.value.mac}"
|
log.debug "Creating WeMo Switch with dni: ${selectedSwitch.value.mac}"
|
||||||
d = addChildDevice("smartthings", "Wemo Switch", selectedSwitch.value.mac, selectedSwitch?.value.hub, [
|
d = addChildDevice("smartthings", "Wemo Switch", selectedSwitch.value.mac, selectedSwitch?.value.hub, [
|
||||||
"label": selectedSwitch?.value?.name ?: "Wemo Switch",
|
"label": selectedSwitch?.value?.name ?: "Wemo Switch",
|
||||||
"data": [
|
"data": [
|
||||||
"mac": selectedSwitch.value.mac,
|
"mac": selectedSwitch.value.mac,
|
||||||
"ip": selectedSwitch.value.ip,
|
"ip": selectedSwitch.value.ip,
|
||||||
"port": selectedSwitch.value.port
|
"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}")
|
d.sendEvent(name: "currentIP", value: ipvalue, descriptionText: "IP is ${ipvalue}")
|
||||||
log.debug "Created ${d.displayName} with id: ${d.id}, dni: ${d.deviceNetworkId}"
|
log.debug "Created ${d.displayName} with id: ${d.id}, dni: ${d.deviceNetworkId}"
|
||||||
} else {
|
} else {
|
||||||
@@ -265,7 +259,7 @@ def addMotions() {
|
|||||||
def d
|
def d
|
||||||
if (selectedMotion) {
|
if (selectedMotion) {
|
||||||
d = getChildDevices()?.find {
|
d = getChildDevices()?.find {
|
||||||
it.deviceNetworkId == selectedMotion.value.mac || it.device.getDataValue("mac") == selectedMotion.value.mac
|
it.dni == selectedMotion.value.mac || it.device.getDataValue("mac") == selectedMotion.value.mac
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -279,9 +273,9 @@ def addMotions() {
|
|||||||
"port": selectedMotion.value.port
|
"port": selectedMotion.value.port
|
||||||
]
|
]
|
||||||
])
|
])
|
||||||
def ipvalue = convertHexToIP(selectedMotion.value.ip)
|
def ipvalue = convertHexToIP(selectedMotion.value.ip)
|
||||||
d.sendEvent(name: "currentIP", value: ipvalue, descriptionText: "IP is ${ipvalue}")
|
d.sendEvent(name: "currentIP", value: ipvalue, descriptionText: "IP is ${ipvalue}")
|
||||||
log.debug "Created ${d.displayName} with id: ${d.id}, dni: ${d.deviceNetworkId}"
|
log.debug "Created ${d.displayName} with id: ${d.id}, dni: ${d.deviceNetworkId}"
|
||||||
} else {
|
} else {
|
||||||
log.debug "found ${d.displayName} with id $dni already exists"
|
log.debug "found ${d.displayName} with id $dni already exists"
|
||||||
}
|
}
|
||||||
@@ -296,7 +290,7 @@ def addLightSwitches() {
|
|||||||
def d
|
def d
|
||||||
if (selectedLightSwitch) {
|
if (selectedLightSwitch) {
|
||||||
d = getChildDevices()?.find {
|
d = getChildDevices()?.find {
|
||||||
it.deviceNetworkId == selectedLightSwitch.value.mac || it.device.getDataValue("mac") == selectedLightSwitch.value.mac
|
it.dni == selectedLightSwitch.value.mac || it.device.getDataValue("mac") == selectedLightSwitch.value.mac
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -310,155 +304,26 @@ def addLightSwitches() {
|
|||||||
"port": selectedLightSwitch.value.port
|
"port": selectedLightSwitch.value.port
|
||||||
]
|
]
|
||||||
])
|
])
|
||||||
def ipvalue = convertHexToIP(selectedLightSwitch.value.ip)
|
def ipvalue = convertHexToIP(selectedLightSwitch.value.ip)
|
||||||
d.sendEvent(name: "currentIP", value: ipvalue, descriptionText: "IP is ${ipvalue}")
|
d.sendEvent(name: "currentIP", value: ipvalue, descriptionText: "IP is ${ipvalue}")
|
||||||
log.debug "created ${d.displayName} with id $dni"
|
log.debug "created ${d.displayName} with id $dni"
|
||||||
} else {
|
} 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 locationHandler(evt) {
|
||||||
def description = evt.description
|
def description = evt.description
|
||||||
def hub = evt?.hubId
|
def hub = evt?.hubId
|
||||||
def parsedEvent = parseDiscoveryMessage(description)
|
def parsedEvent = parseDiscoveryMessage(description)
|
||||||
parsedEvent << ["hub":hub]
|
parsedEvent << ["hub":hub]
|
||||||
log.debug parsedEvent
|
log.debug parsedEvent
|
||||||
|
|
||||||
if (parsedEvent?.ssdpTerm?.contains("Belkin:device:controllee") || parsedEvent?.ssdpTerm?.contains("Belkin:device:insight")) {
|
if (parsedEvent?.ssdpTerm?.contains("Belkin:device:controllee") || parsedEvent?.ssdpTerm?.contains("Belkin:device:insight")) {
|
||||||
def switches = getWemoSwitches()
|
def switches = getWemoSwitches()
|
||||||
if (!(switches."${parsedEvent.ssdpUSN.toString()}")) {
|
if (!(switches."${parsedEvent.ssdpUSN.toString()}")) {
|
||||||
//if it doesn't already exist
|
//if it doesn't already exist
|
||||||
switches << ["${parsedEvent.ssdpUSN.toString()}":parsedEvent]
|
switches << ["${parsedEvent.ssdpUSN.toString()}":parsedEvent]
|
||||||
} else {
|
} else {
|
||||||
log.debug "Device was already found in state..."
|
log.debug "Device was already found in state..."
|
||||||
@@ -470,20 +335,16 @@ def locationHandler(evt) {
|
|||||||
d.port = parsedEvent.port
|
d.port = parsedEvent.port
|
||||||
deviceChangedValues = true
|
deviceChangedValues = true
|
||||||
log.debug "Device's port or ip changed..."
|
log.debug "Device's port or ip changed..."
|
||||||
def child = getChildDevice(parsedEvent.mac)
|
def child = getChildDevice(parsedEvent.mac)
|
||||||
if (child) {
|
child.subscribe(parsedEvent.ip, parsedEvent.port)
|
||||||
child.subscribe(parsedEvent.ip, parsedEvent.port)
|
child.poll()
|
||||||
child.poll()
|
|
||||||
} else {
|
|
||||||
log.debug "Device with mac $parsedEvent.mac not found"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (parsedEvent?.ssdpTerm?.contains("Belkin:device:sensor")) {
|
else if (parsedEvent?.ssdpTerm?.contains("Belkin:device:sensor")) {
|
||||||
def motions = getWemoMotions()
|
def motions = getWemoMotions()
|
||||||
if (!(motions."${parsedEvent.ssdpUSN.toString()}")) {
|
if (!(motions."${parsedEvent.ssdpUSN.toString()}")) {
|
||||||
//if it doesn't already exist
|
//if it doesn't already exist
|
||||||
motions << ["${parsedEvent.ssdpUSN.toString()}":parsedEvent]
|
motions << ["${parsedEvent.ssdpUSN.toString()}":parsedEvent]
|
||||||
} else { // just update the values
|
} else { // just update the values
|
||||||
log.debug "Device was already found in state..."
|
log.debug "Device was already found in state..."
|
||||||
@@ -530,12 +391,8 @@ def locationHandler(evt) {
|
|||||||
deviceChangedValues = true
|
deviceChangedValues = true
|
||||||
log.debug "Device's port or ip changed..."
|
log.debug "Device's port or ip changed..."
|
||||||
def child = getChildDevice(parsedEvent.mac)
|
def child = getChildDevice(parsedEvent.mac)
|
||||||
if (child) {
|
log.debug "updating ip and port, and resubscribing, for device with mac ${parsedEvent.mac}"
|
||||||
log.debug "updating ip and port, and resubscribing, for device with mac ${parsedEvent.mac}"
|
child.subscribe(parsedEvent.ip, parsedEvent.port)
|
||||||
child.subscribe(parsedEvent.ip, parsedEvent.port)
|
|
||||||
} else {
|
|
||||||
log.debug "Device with mac $parsedEvent.mac not found"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -602,7 +459,6 @@ def locationHandler(evt) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Deprecated
|
|
||||||
private def parseXmlBody(def body) {
|
private def parseXmlBody(def body) {
|
||||||
def decodedBytes = body.decodeBase64()
|
def decodedBytes = body.decodeBase64()
|
||||||
def bodyString
|
def bodyString
|
||||||
@@ -617,48 +473,68 @@ private def parseXmlBody(def body) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private def parseDiscoveryMessage(String description) {
|
private def parseDiscoveryMessage(String description) {
|
||||||
def event = [:]
|
def device = [:]
|
||||||
def parts = description.split(',')
|
def parts = description.split(',')
|
||||||
parts.each { part ->
|
parts.each { part ->
|
||||||
part = part.trim()
|
part = part.trim()
|
||||||
if (part.startsWith('devicetype:')) {
|
if (part.startsWith('devicetype:')) {
|
||||||
part -= "devicetype:"
|
def valueString = part.split(":")[1].trim()
|
||||||
event.devicetype = part.trim()
|
device.devicetype = valueString
|
||||||
}
|
}
|
||||||
else if (part.startsWith('mac:')) {
|
else if (part.startsWith('mac:')) {
|
||||||
part -= "mac:"
|
def valueString = part.split(":")[1].trim()
|
||||||
event.mac = part.trim()
|
if (valueString) {
|
||||||
|
device.mac = valueString
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (part.startsWith('networkAddress:')) {
|
else if (part.startsWith('networkAddress:')) {
|
||||||
part -= "networkAddress:"
|
def valueString = part.split(":")[1].trim()
|
||||||
event.ip = part.trim()
|
if (valueString) {
|
||||||
|
device.ip = valueString
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (part.startsWith('deviceAddress:')) {
|
else if (part.startsWith('deviceAddress:')) {
|
||||||
part -= "deviceAddress:"
|
def valueString = part.split(":")[1].trim()
|
||||||
event.port = part.trim()
|
if (valueString) {
|
||||||
|
device.port = valueString
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (part.startsWith('ssdpPath:')) {
|
else if (part.startsWith('ssdpPath:')) {
|
||||||
part -= "ssdpPath:"
|
def valueString = part.split(":")[1].trim()
|
||||||
event.ssdpPath = part.trim()
|
if (valueString) {
|
||||||
|
device.ssdpPath = valueString
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (part.startsWith('ssdpUSN:')) {
|
else if (part.startsWith('ssdpUSN:')) {
|
||||||
part -= "ssdpUSN:"
|
part -= "ssdpUSN:"
|
||||||
event.ssdpUSN = part.trim()
|
def valueString = part.trim()
|
||||||
|
if (valueString) {
|
||||||
|
device.ssdpUSN = valueString
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (part.startsWith('ssdpTerm:')) {
|
else if (part.startsWith('ssdpTerm:')) {
|
||||||
part -= "ssdpTerm:"
|
part -= "ssdpTerm:"
|
||||||
event.ssdpTerm = part.trim()
|
def valueString = part.trim()
|
||||||
|
if (valueString) {
|
||||||
|
device.ssdpTerm = valueString
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (part.startsWith('headers')) {
|
else if (part.startsWith('headers')) {
|
||||||
part -= "headers:"
|
part -= "headers:"
|
||||||
event.headers = part.trim()
|
def valueString = part.trim()
|
||||||
|
if (valueString) {
|
||||||
|
device.headers = valueString
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (part.startsWith('body')) {
|
else if (part.startsWith('body')) {
|
||||||
part -= "body:"
|
part -= "body:"
|
||||||
event.body = part.trim()
|
def valueString = part.trim()
|
||||||
|
if (valueString) {
|
||||||
|
device.body = valueString
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
event
|
device
|
||||||
}
|
}
|
||||||
|
|
||||||
def doDeviceSync(){
|
def doDeviceSync(){
|
||||||
@@ -684,4 +560,4 @@ private Boolean hasAllHubsOver(String desiredFirmware) {
|
|||||||
|
|
||||||
private List getRealHubFirmwareVersions() {
|
private List getRealHubFirmwareVersions() {
|
||||||
return location.hubs*.firmwareVersionString.findAll { it }
|
return location.hubs*.firmwareVersionString.findAll { it }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,11 @@
|
|||||||
/**
|
/**
|
||||||
* Vacation Lighting Director
|
* Vacation Lighting Director
|
||||||
*
|
*
|
||||||
* Version 2.5 - Moved scheduling over to Cron and added time as a trigger.
|
* Version 2.4 - Added information paragraphs
|
||||||
* Cleaned up formatting and some typos.
|
|
||||||
* Updated license.
|
|
||||||
* Made people option optional
|
|
||||||
* Added sttement to unschedule on mode change if people option is not selected
|
|
||||||
*
|
|
||||||
* Version 2.4 - Added information paragraphs
|
|
||||||
*
|
*
|
||||||
* Source code can be found here: https://github.com/tslagle13/SmartThings/blob/master/smartapps/tslagle13/vacation-lighting-director.groovy
|
* Source code can be found here: https://github.com/tslagle13/SmartThings/blob/master/smartapps/tslagle13/vacation-lighting-director.groovy
|
||||||
*
|
*
|
||||||
* Copyright 2016 Tim Slagle
|
* Copyright 2015 Tim Slagle
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||||
* in compliance with the License. You may obtain a copy of the License at:
|
* in compliance with the License. You may obtain a copy of the License at:
|
||||||
@@ -57,7 +51,8 @@ def pageSetup() {
|
|||||||
return dynamicPage(pageProperties) {
|
return dynamicPage(pageProperties) {
|
||||||
section(""){
|
section(""){
|
||||||
paragraph "This app can be used to make your home seem occupied anytime you are away from your home. " +
|
paragraph "This app can be used to make your home seem occupied anytime you are away from your home. " +
|
||||||
"Please use each of the the sections below to setup the different preferences to your liking. "
|
"Please use each othe the sections below to setup the different preferences to your liking. " +
|
||||||
|
"I recommend this app be used with at least two away modes. An example would be 'Away Day' 'and Away Night'. "
|
||||||
}
|
}
|
||||||
section("Setup Menu") {
|
section("Setup Menu") {
|
||||||
href "Setup", title: "Setup", description: "", state:greyedOut()
|
href "Setup", title: "Setup", description: "", state:greyedOut()
|
||||||
@@ -75,7 +70,7 @@ def Setup() {
|
|||||||
def newMode = [
|
def newMode = [
|
||||||
name: "newMode",
|
name: "newMode",
|
||||||
type: "mode",
|
type: "mode",
|
||||||
title: "Modes",
|
title: "Which?",
|
||||||
multiple: true,
|
multiple: true,
|
||||||
required: true
|
required: true
|
||||||
]
|
]
|
||||||
@@ -101,6 +96,14 @@ def Setup() {
|
|||||||
required: true,
|
required: true,
|
||||||
]
|
]
|
||||||
|
|
||||||
|
def people = [
|
||||||
|
name: "people",
|
||||||
|
type: "capability.presenceSensor",
|
||||||
|
title: "If these people are home do not change light status",
|
||||||
|
required: true,
|
||||||
|
multiple: true
|
||||||
|
]
|
||||||
|
|
||||||
def pageName = "Setup"
|
def pageName = "Setup"
|
||||||
|
|
||||||
def pageProperties = [
|
def pageProperties = [
|
||||||
@@ -113,11 +116,10 @@ def Setup() {
|
|||||||
|
|
||||||
section(""){
|
section(""){
|
||||||
paragraph "In this section you need to setup the deatils of how you want your lighting to be affected while " +
|
paragraph "In this section you need to setup the deatils of how you want your lighting to be affected while " +
|
||||||
"you are away. All of these settings are required in order for the simulator to run correctly."
|
paragraph "you are away. All of these settings are required in order for the simulator to run correctly."
|
||||||
}
|
}
|
||||||
section("Simulator Triggers") {
|
section("Which mode change triggers the simulator? (This app will only run in selected mode(s))") {
|
||||||
input newMode
|
input newMode
|
||||||
href "timeIntervalInput", title: "Times", description: timeIntervalLabel(), refreshAfterSelection:true
|
|
||||||
}
|
}
|
||||||
section("Light switches to turn on/off") {
|
section("Light switches to turn on/off") {
|
||||||
input switches
|
input switches
|
||||||
@@ -128,6 +130,9 @@ def Setup() {
|
|||||||
section("Number of active lights at any given time") {
|
section("Number of active lights at any given time") {
|
||||||
input number_of_active_lights
|
input number_of_active_lights
|
||||||
}
|
}
|
||||||
|
section("People") {
|
||||||
|
input people
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -157,29 +162,18 @@ def Settings() {
|
|||||||
title: "Settings",
|
title: "Settings",
|
||||||
nextPage: "pageSetup"
|
nextPage: "pageSetup"
|
||||||
]
|
]
|
||||||
|
|
||||||
def people = [
|
|
||||||
name: "people",
|
|
||||||
type: "capability.presenceSensor",
|
|
||||||
title: "If these people are home do not change light status",
|
|
||||||
required: false,
|
|
||||||
multiple: true
|
|
||||||
]
|
|
||||||
|
|
||||||
return dynamicPage(pageProperties) {
|
return dynamicPage(pageProperties) {
|
||||||
|
|
||||||
section(""){
|
section(""){
|
||||||
paragraph "In this section you can restrict how your simulator runs. For instance you can restrict on which days it will run " +
|
paragraph "In this section you can restrict how your simulator runs. For instance you can restrict on which days it will run " +
|
||||||
"as well as a delay for the simulator to start after it is in the correct mode. Delaying the simulator helps with false starts based on a incorrect mode change."
|
paragraph "as well as a delay for the simulator to start after it is in the correct mode. Delaying the simulator helps with false starts based on a incorrect mode change."
|
||||||
}
|
}
|
||||||
section("Delay to start simulator") {
|
section("Delay to start simulator") {
|
||||||
input falseAlarmThreshold
|
input falseAlarmThreshold
|
||||||
}
|
}
|
||||||
section("People") {
|
|
||||||
paragraph "Not using this setting may cause some lights to remain on when you arrive home"
|
|
||||||
input people
|
|
||||||
}
|
|
||||||
section("More options") {
|
section("More options") {
|
||||||
|
href "timeIntervalInput", title: "Only during a certain time", description: getTimeLabel(starting, ending), state: greyedOutTime(starting, ending), refreshAfterSelection:true
|
||||||
input days
|
input days
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -187,24 +181,9 @@ def Settings() {
|
|||||||
|
|
||||||
page(name: "timeIntervalInput", title: "Only during a certain time", refreshAfterSelection:true) {
|
page(name: "timeIntervalInput", title: "Only during a certain time", refreshAfterSelection:true) {
|
||||||
section {
|
section {
|
||||||
input "startTimeType", "enum", title: "Starting at", options: [["time": "A specific time"], ["sunrise": "Sunrise"], ["sunset": "Sunset"]], defaultValue: "time", submitOnChange: true
|
input "starting", "time", title: "Starting", required: false
|
||||||
if (startTimeType in ["sunrise","sunset"]) {
|
input "ending", "time", title: "Ending", required: false
|
||||||
input "startTimeOffset", "number", title: "Offset in minutes (+/-)", range: "*..*", required: false
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
input "starting", "time", title: "Start time", required: false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
section {
|
|
||||||
input "endTimeType", "enum", title: "Ending at", options: [["time": "A specific time"], ["sunrise": "Sunrise"], ["sunset": "Sunset"]], defaultValue: "time", submitOnChange: true
|
|
||||||
if (endTimeType in ["sunrise","sunset"]) {
|
|
||||||
input "endTimeOffset", "number", title: "Offset in minutes (+/-)", range: "*..*", required: false
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
input "ending", "time", title: "End time", required: false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def installed() {
|
def installed() {
|
||||||
@@ -222,13 +201,10 @@ def initialize(){
|
|||||||
if (newMode != null) {
|
if (newMode != null) {
|
||||||
subscribe(location, modeChangeHandler)
|
subscribe(location, modeChangeHandler)
|
||||||
}
|
}
|
||||||
if (starting != null) {
|
|
||||||
schedule(starting, modeChangeHandler)
|
|
||||||
}
|
|
||||||
log.debug "Installed with settings: ${settings}"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def modeChangeHandler(evt) {
|
def modeChangeHandler(evt) {
|
||||||
|
log.debug "Mode change to: ${evt.value}"
|
||||||
def delay = (falseAlarmThreshold != null && falseAlarmThreshold != "") ? falseAlarmThreshold * 60 : 2 * 60
|
def delay = (falseAlarmThreshold != null && falseAlarmThreshold != "") ? falseAlarmThreshold * 60 : 2 * 60
|
||||||
runIn(delay, scheduleCheck)
|
runIn(delay, scheduleCheck)
|
||||||
}
|
}
|
||||||
@@ -236,54 +212,48 @@ def modeChangeHandler(evt) {
|
|||||||
|
|
||||||
//Main logic to pick a random set of lights from the large set of lights to turn on and then turn the rest off
|
//Main logic to pick a random set of lights from the large set of lights to turn on and then turn the rest off
|
||||||
def scheduleCheck(evt) {
|
def scheduleCheck(evt) {
|
||||||
if(allOk){
|
if(allOk){
|
||||||
log.debug("Running")
|
log.debug("Running")
|
||||||
// turn off all the switches
|
// turn off all the switches
|
||||||
switches.off()
|
switches.off()
|
||||||
|
|
||||||
// grab a random switch
|
// grab a random switch
|
||||||
def random = new Random()
|
def random = new Random()
|
||||||
def inactive_switches = switches
|
def inactive_switches = switches
|
||||||
for (int i = 0 ; i < number_of_active_lights ; i++) {
|
for (int i = 0 ; i < number_of_active_lights ; i++) {
|
||||||
// if there are no inactive switches to turn on then let's break
|
// if there are no inactive switches to turn on then let's break
|
||||||
if (inactive_switches.size() == 0){
|
if (inactive_switches.size() == 0){
|
||||||
break
|
break
|
||||||
}
|
|
||||||
|
|
||||||
// grab a random switch and turn it on
|
|
||||||
def random_int = random.nextInt(inactive_switches.size())
|
|
||||||
inactive_switches[random_int].on()
|
|
||||||
|
|
||||||
// then remove that switch from the pool off switches that can be turned on
|
|
||||||
inactive_switches.remove(random_int)
|
|
||||||
}
|
|
||||||
|
|
||||||
// re-run again when the frequency demands it
|
|
||||||
schedule("0 0/${frequency_minutes} * 1/1 * ? *", scheduleCheck)
|
|
||||||
}
|
}
|
||||||
//Check to see if mode is ok but not time/day. If mode is still ok, check again after frequency period.
|
|
||||||
else if (modeOk) {
|
// grab a random switch and turn it on
|
||||||
log.debug("mode OK. Running again")
|
def random_int = random.nextInt(inactive_switches.size())
|
||||||
switches.off()
|
inactive_switches[random_int].on()
|
||||||
}
|
|
||||||
//if none is ok turn off frequency check and turn off lights.
|
// then remove that switch from the pool off switches that can be turned on
|
||||||
else {
|
inactive_switches.remove(random_int)
|
||||||
if(people){
|
}
|
||||||
//don't turn off lights if anyone is home
|
|
||||||
if(someoneIsHome()){
|
// re-run again when the frequency demands it
|
||||||
log.debug("Stopping Check for Light")
|
runIn(frequency_minutes * 60, scheduleCheck)
|
||||||
unschedule()
|
}
|
||||||
}
|
//Check to see if mode is ok but not time/day. If mode is still ok, check again after frequency period.
|
||||||
else{
|
else if (modeOk) {
|
||||||
log.debug("Stopping Check for Light and turning off all lights")
|
log.debug("mode OK. Running again")
|
||||||
switches.off()
|
runIn(frequency_minutes * 60, scheduleCheck)
|
||||||
unschedule()
|
switches.off()
|
||||||
}
|
}
|
||||||
|
//if none is ok turn off frequency check and turn off lights.
|
||||||
|
else if(people){
|
||||||
|
//don't turn off lights if anyone is home
|
||||||
|
if(someoneIsHome()){
|
||||||
|
log.debug("Stopping Check for Light")
|
||||||
}
|
}
|
||||||
else if (!modeOk) {
|
else{
|
||||||
unschedule()
|
log.debug("Stopping Check for Light and turning off all lights")
|
||||||
}
|
switches.off()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -316,6 +286,26 @@ private getDaysOk() {
|
|||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private getTimeOk() {
|
||||||
|
def result = true
|
||||||
|
if (starting && ending) {
|
||||||
|
def currTime = now()
|
||||||
|
def start = timeToday(starting).time
|
||||||
|
def stop = timeToday(ending).time
|
||||||
|
result = start < stop ? currTime >= start && currTime <= stop : currTime <= stop || currTime >= start
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (starting){
|
||||||
|
result = currTime >= start
|
||||||
|
}
|
||||||
|
else if (ending){
|
||||||
|
result = currTime <= stop
|
||||||
|
}
|
||||||
|
|
||||||
|
log.trace "timeOk = $result"
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
private getHomeIsEmpty() {
|
private getHomeIsEmpty() {
|
||||||
def result = true
|
def result = true
|
||||||
|
|
||||||
@@ -340,59 +330,25 @@ private getSomeoneIsHome() {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
private getTimeOk() {
|
|
||||||
def result = true
|
//gets the label for time restriction. Label phrasing changes depending on if there is both start and stop times or just one start/stop time.
|
||||||
def start = timeWindowStart()
|
def getTimeLabel(starting, ending){
|
||||||
def stop = timeWindowStop()
|
|
||||||
if (start && stop && location.timeZone) {
|
def timeLabel = "Tap to set"
|
||||||
result = timeOfDayIsBetween(start, stop, new Date(), location.timeZone)
|
|
||||||
}
|
if(starting && ending){
|
||||||
log.trace "timeOk = $result"
|
timeLabel = "Between" + " " + hhmm(starting) + " " + "and" + " " + hhmm(ending)
|
||||||
result
|
}
|
||||||
}
|
else if (starting) {
|
||||||
|
timeLabel = "Start at" + " " + hhmm(starting)
|
||||||
private timeWindowStart() {
|
}
|
||||||
def result = null
|
else if(ending){
|
||||||
if (startTimeType == "sunrise") {
|
timeLabel = "End at" + hhmm(ending)
|
||||||
result = location.currentState("sunriseTime")?.dateValue
|
}
|
||||||
if (result && startTimeOffset) {
|
timeLabel
|
||||||
result = new Date(result.time + Math.round(startTimeOffset * 60000))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (startTimeType == "sunset") {
|
|
||||||
result = location.currentState("sunsetTime")?.dateValue
|
|
||||||
if (result && startTimeOffset) {
|
|
||||||
result = new Date(result.time + Math.round(startTimeOffset * 60000))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (starting && location.timeZone) {
|
|
||||||
result = timeToday(starting, location.timeZone)
|
|
||||||
}
|
|
||||||
log.trace "timeWindowStart = ${result}"
|
|
||||||
result
|
|
||||||
}
|
|
||||||
|
|
||||||
private timeWindowStop() {
|
|
||||||
def result = null
|
|
||||||
if (endTimeType == "sunrise") {
|
|
||||||
result = location.currentState("sunriseTime")?.dateValue
|
|
||||||
if (result && endTimeOffset) {
|
|
||||||
result = new Date(result.time + Math.round(endTimeOffset * 60000))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (endTimeType == "sunset") {
|
|
||||||
result = location.currentState("sunsetTime")?.dateValue
|
|
||||||
if (result && endTimeOffset) {
|
|
||||||
result = new Date(result.time + Math.round(endTimeOffset * 60000))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (ending && location.timeZone) {
|
|
||||||
result = timeToday(ending, location.timeZone)
|
|
||||||
}
|
|
||||||
log.trace "timeWindowStop = ${result}"
|
|
||||||
result
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//fomrats time to readable format for time label
|
||||||
private hhmm(time, fmt = "h:mm a")
|
private hhmm(time, fmt = "h:mm a")
|
||||||
{
|
{
|
||||||
def t = timeToday(time, location.timeZone)
|
def t = timeToday(time, location.timeZone)
|
||||||
@@ -401,41 +357,6 @@ private hhmm(time, fmt = "h:mm a")
|
|||||||
f.format(t)
|
f.format(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
private timeIntervalLabel() {
|
|
||||||
def start = ""
|
|
||||||
switch (startTimeType) {
|
|
||||||
case "time":
|
|
||||||
if (ending) {
|
|
||||||
start += hhmm(starting)
|
|
||||||
}
|
|
||||||
break
|
|
||||||
case "sunrise":
|
|
||||||
case "sunset":
|
|
||||||
start += startTimeType[0].toUpperCase() + startTimeType[1..-1]
|
|
||||||
if (startTimeOffset) {
|
|
||||||
start += startTimeOffset > 0 ? "+${startTimeOffset} min" : "${startTimeOffset} min"
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
def finish = ""
|
|
||||||
switch (endTimeType) {
|
|
||||||
case "time":
|
|
||||||
if (ending) {
|
|
||||||
finish += hhmm(ending)
|
|
||||||
}
|
|
||||||
break
|
|
||||||
case "sunrise":
|
|
||||||
case "sunset":
|
|
||||||
finish += endTimeType[0].toUpperCase() + endTimeType[1..-1]
|
|
||||||
if (endTimeOffset) {
|
|
||||||
finish += endTimeOffset > 0 ? "+${endTimeOffset} min" : "${endTimeOffset} min"
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
start && finish ? "${start} to ${finish}" : ""
|
|
||||||
}
|
|
||||||
|
|
||||||
//sets complete/not complete for the setup section on the main dynamic page
|
//sets complete/not complete for the setup section on the main dynamic page
|
||||||
def greyedOut(){
|
def greyedOut(){
|
||||||
def result = ""
|
def result = ""
|
||||||
@@ -448,7 +369,16 @@ def greyedOut(){
|
|||||||
//sets complete/not complete for the settings section on the main dynamic page
|
//sets complete/not complete for the settings section on the main dynamic page
|
||||||
def greyedOutSettings(){
|
def greyedOutSettings(){
|
||||||
def result = ""
|
def result = ""
|
||||||
if (people || days || falseAlarmThreshold ) {
|
if (starting || ending || days || falseAlarmThreshold) {
|
||||||
|
result = "complete"
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
//sets complete/not complete for time restriction section in settings
|
||||||
|
def greyedOutTime(starting, ending){
|
||||||
|
def result = ""
|
||||||
|
if (starting || ending) {
|
||||||
result = "complete"
|
result = "complete"
|
||||||
}
|
}
|
||||||
result
|
result
|
||||||
|
|||||||
Reference in New Issue
Block a user