mirror of
https://github.com/mtan93/SmartThingsPublic.git
synced 2026-03-13 21:03:14 +00:00
Compare commits
33 Commits
netatmo-ap
...
master_old
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
01a36696d8 | ||
|
|
797def2935 | ||
|
|
1034cd06e6 | ||
|
|
c37729242e | ||
|
|
d29c3ec557 | ||
|
|
17be85b846 | ||
|
|
da06104563 | ||
|
|
5d37ac8515 | ||
|
|
2a739fda07 | ||
|
|
f627fb4fac | ||
|
|
1d30a718b2 | ||
|
|
303ca7117c | ||
|
|
1c96645b9f | ||
|
|
af4dc0640a | ||
|
|
80b46153dc | ||
|
|
b92cd9c637 | ||
|
|
1039b65c81 | ||
|
|
4e203e8e13 | ||
|
|
61398105d1 | ||
|
|
505efc5463 | ||
|
|
3ddc82f996 | ||
|
|
13a324069d | ||
|
|
2fd5859326 | ||
|
|
bbedbddf9d | ||
|
|
e424e7abdd | ||
|
|
47fbdabf6b | ||
|
|
7defe1cc61 | ||
|
|
a78459347b | ||
|
|
c473745e47 | ||
|
|
fc587ef15a | ||
|
|
0f3b730f26 | ||
|
|
11df2f31b3 | ||
|
|
4d243bf44d |
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
|
|
||||||
133
build.gradle
133
build.gradle
@@ -1,133 +0,0 @@
|
|||||||
import java.nio.charset.StandardCharsets
|
|
||||||
import java.nio.file.Paths
|
|
||||||
import com.smartthings.deployment.slack.FileUpload
|
|
||||||
import com.smartthings.deployment.slack.Message
|
|
||||||
|
|
||||||
apply plugin: 'groovy'
|
|
||||||
apply plugin: 'smartthings-executable-deployment'
|
|
||||||
apply plugin: 'smartthings-slack'
|
|
||||||
|
|
||||||
buildscript {
|
|
||||||
dependencies {
|
|
||||||
classpath "com.smartthings.deployment:executable-deployment-scripts:1.0.8"
|
|
||||||
}
|
|
||||||
repositories {
|
|
||||||
mavenLocal()
|
|
||||||
jcenter()
|
|
||||||
maven {
|
|
||||||
credentials {
|
|
||||||
username smartThingsArtifactoryUserName
|
|
||||||
password smartThingsArtifactoryPassword
|
|
||||||
}
|
|
||||||
url "https://artifactory.smartthings.com/libs-release-local"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
repositories {
|
|
||||||
mavenLocal()
|
|
||||||
jcenter()
|
|
||||||
maven {
|
|
||||||
credentials {
|
|
||||||
username smartThingsArtifactoryUserName
|
|
||||||
password smartThingsArtifactoryPassword
|
|
||||||
}
|
|
||||||
url "https://artifactory.smartthings.com/libs-release-local"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sourceSets {
|
|
||||||
devicetypes {
|
|
||||||
groovy {
|
|
||||||
srcDirs = ['devicetypes']
|
|
||||||
}
|
|
||||||
}
|
|
||||||
smartapps {
|
|
||||||
groovy {
|
|
||||||
srcDirs = ['smartapps']
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
devicetypesCompile 'org.codehaus.groovy:groovy-all:2.4.7'
|
|
||||||
devicetypesCompile 'smartthings:appengine-z-wave:0.1.2'
|
|
||||||
devicetypesCompile 'smartthings:appengine-zigbee:0.1.11'
|
|
||||||
smartappsCompile 'org.codehaus.groovy:groovy-all:2.4.7'
|
|
||||||
smartappsCompile 'smartthings:appengine-common:0.1.8'
|
|
||||||
smartappsCompile 'org.codehaus.groovy.modules.http-builder:http-builder:0.7.1'
|
|
||||||
smartappsCompile 'org.grails:grails-web:2.3.11'
|
|
||||||
smartappsCompile 'org.json:json:20140107'
|
|
||||||
}
|
|
||||||
|
|
||||||
slackSendMessage {
|
|
||||||
String branch = project.hasProperty('branch') ? project.property('branch') : 'unknown'
|
|
||||||
String token = project.hasProperty('slackToken') ? project.property('slackToken') : null
|
|
||||||
String webhookUrl = project.hasProperty('slackWebhookUrl') ? project.property('slackWebhookUrl') : null
|
|
||||||
String channel = project.hasProperty('slackChannel') ? project.property('slackChannel') : null
|
|
||||||
String drinks = 'https://dl.dropboxusercontent.com/s/m1z5mpd3c83lwev/minion_beer.jpeg?dl=0'
|
|
||||||
String wolverine = 'https://dl.dropboxusercontent.com/s/4lbjqzvm2v033u9/minion_wolverine.jpg?dl=0'
|
|
||||||
String beach = 'https://dl.dropboxusercontent.com/s/rqrfgxk53gfng69/minion_beach.png?dl=0'
|
|
||||||
String iconUrl
|
|
||||||
String color
|
|
||||||
String messageText
|
|
||||||
String username
|
|
||||||
switch (branch) {
|
|
||||||
case 'master':
|
|
||||||
username = 'Hickory'
|
|
||||||
iconUrl = wolverine
|
|
||||||
color = '#35D0F2'
|
|
||||||
messageText = 'Began deployment of _SmartThingsPublic[master]_ branch to the _Dev_ environments.'
|
|
||||||
break
|
|
||||||
case 'staging':
|
|
||||||
username = 'Dickory'
|
|
||||||
iconUrl = beach
|
|
||||||
color = '#FFDE20'
|
|
||||||
messageText = 'Began deployment of _SmartThingsPublic[staging]_ branch to the _Staging_ environments.'
|
|
||||||
break
|
|
||||||
case 'production':
|
|
||||||
username = 'Dock'
|
|
||||||
iconUrl = drinks
|
|
||||||
color = '#FF1D23'
|
|
||||||
messageText = 'Began deployment of _SmartThingsPublic[production]_ branch to the _Prod_ environments.'
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
username = 'Hickory'
|
|
||||||
iconUrl = wolverine
|
|
||||||
color = '#35D0F2'
|
|
||||||
messageText = "Began deployment of an _SmartThingsPublic[${branch}]_ branch. Have no idea what's going on."
|
|
||||||
}
|
|
||||||
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())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Date date = new Date()
|
|
||||||
String fileDate = date.format('yyyy-MM-dd_HH-mm-ss', TimeZone.getTimeZone('GMT'))
|
|
||||||
|
|
||||||
// Required Task Arguments.
|
|
||||||
file = new FileUpload(
|
|
||||||
data: archives.join('\n').getBytes(StandardCharsets.UTF_8),
|
|
||||||
filename: "deployment-notes-${fileDate}.txt",
|
|
||||||
title: 'Deployment Notes',
|
|
||||||
channels: channel,
|
|
||||||
token: token,
|
|
||||||
color: color
|
|
||||||
)
|
|
||||||
message = new Message(
|
|
||||||
webhookUrl: webhookUrl,
|
|
||||||
username: username,
|
|
||||||
asUser: true,
|
|
||||||
iconUrl: iconUrl,
|
|
||||||
channel: channel,
|
|
||||||
fallback: 'Deployment Notification',
|
|
||||||
text: messageText
|
|
||||||
)
|
|
||||||
}
|
|
||||||
27
circle.yml
27
circle.yml
@@ -1,27 +0,0 @@
|
|||||||
machine:
|
|
||||||
java:
|
|
||||||
version:
|
|
||||||
oraclejdk8
|
|
||||||
|
|
||||||
dependencies:
|
|
||||||
override:
|
|
||||||
- ./gradlew dependencies -PsmartThingsArtifactoryUserName="$ARTIFACTORY_USERNAME" -PsmartThingsArtifactoryPassword="$ARTIFACTORY_PASSWORD"
|
|
||||||
post:
|
|
||||||
- ./gradlew compileSmartappsGroovy compileDevicetypesGroovy -PsmartThingsArtifactoryUserName="$ARTIFACTORY_USERNAME" -PsmartThingsArtifactoryPassword="$ARTIFACTORY_PASSWORD"
|
|
||||||
|
|
||||||
test:
|
|
||||||
override:
|
|
||||||
- echo "We don't have any tests :-("
|
|
||||||
|
|
||||||
deployment:
|
|
||||||
develop:
|
|
||||||
branch: master
|
|
||||||
commands:
|
|
||||||
- ./gradlew deployArchives -PsmartThingsArtifactoryUserName="$ARTIFACTORY_USERNAME" -PsmartThingsArtifactoryPassword="$ARTIFACTORY_PASSWORD" -Ps3Buckets="$S3_BUCKETS_DEV"
|
|
||||||
- ./gradlew slackSendMessage -PsmartThingsArtifactoryUserName="$ARTIFACTORY_USERNAME" -PsmartThingsArtifactoryPassword="$ARTIFACTORY_PASSWORD" -Pbranch="$CIRCLE_BRANCH" -PslackToken="$SLACK_TOKEN" -PslackWebhookUrl="$SLACK_WEBHOOK_URL" -PslackChannel="$SLACK_CHANNEL" --stacktrace
|
|
||||||
|
|
||||||
stage:
|
|
||||||
branch: staging
|
|
||||||
commands:
|
|
||||||
- ./gradlew deployArchives -PsmartThingsArtifactoryUserName="$ARTIFACTORY_USERNAME" -PsmartThingsArtifactoryPassword="$ARTIFACTORY_PASSWORD" -Ps3Buckets="$S3_BUCKETS_STAGE"
|
|
||||||
- ./gradlew slackSendMessage -PsmartThingsArtifactoryUserName="$ARTIFACTORY_USERNAME" -PsmartThingsArtifactoryPassword="$ARTIFACTORY_PASSWORD" -Pbranch="$CIRCLE_BRANCH" -PslackToken="$SLACK_TOKEN" -PslackWebhookUrl="$SLACK_WEBHOOK_URL" -PslackChannel="$SLACK_CHANNEL" --stacktrace
|
|
||||||
@@ -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)
|
|
||||||
}
|
|
||||||
@@ -15,7 +15,6 @@
|
|||||||
*/
|
*/
|
||||||
metadata {
|
metadata {
|
||||||
definition (name: "Netatmo Additional Module", namespace: "dianoga", author: "Brian Steere") {
|
definition (name: "Netatmo Additional Module", namespace: "dianoga", author: "Brian Steere") {
|
||||||
capability "Sensor"
|
|
||||||
capability "Relative Humidity Measurement"
|
capability "Relative Humidity Measurement"
|
||||||
capability "Temperature Measurement"
|
capability "Temperature Measurement"
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,6 @@
|
|||||||
*/
|
*/
|
||||||
metadata {
|
metadata {
|
||||||
definition (name: "Netatmo Basestation", namespace: "dianoga", author: "Brian Steere") {
|
definition (name: "Netatmo Basestation", namespace: "dianoga", author: "Brian Steere") {
|
||||||
capability "Sensor"
|
|
||||||
capability "Relative Humidity Measurement"
|
capability "Relative Humidity Measurement"
|
||||||
capability "Temperature Measurement"
|
capability "Temperature Measurement"
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,6 @@
|
|||||||
*/
|
*/
|
||||||
metadata {
|
metadata {
|
||||||
definition (name: "Netatmo Outdoor Module", namespace: "dianoga", author: "Brian Steere") {
|
definition (name: "Netatmo Outdoor Module", namespace: "dianoga", author: "Brian Steere") {
|
||||||
capability "Sensor"
|
|
||||||
capability "Relative Humidity Measurement"
|
capability "Relative Humidity Measurement"
|
||||||
capability "Temperature Measurement"
|
capability "Temperature Measurement"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,8 +15,6 @@
|
|||||||
*/
|
*/
|
||||||
metadata {
|
metadata {
|
||||||
definition (name: "Netatmo Rain", namespace: "dianoga", author: "Brian Steere") {
|
definition (name: "Netatmo Rain", namespace: "dianoga", author: "Brian Steere") {
|
||||||
capability "Sensor"
|
|
||||||
|
|
||||||
attribute "rain", "number"
|
attribute "rain", "number"
|
||||||
attribute "rainSumHour", "number"
|
attribute "rainSumHour", "number"
|
||||||
attribute "rainSumDay", "number"
|
attribute "rainSumDay", "number"
|
||||||
|
|||||||
@@ -1,104 +0,0 @@
|
|||||||
/**
|
|
||||||
* EnerTalk Energy Meter
|
|
||||||
*
|
|
||||||
* Copyright 2015 hyeon seok yang
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
metadata {
|
|
||||||
definition (name: "EnerTalk Energy Meter", namespace: "Encored Technologies", author: "hyeon seok yang") {
|
|
||||||
}
|
|
||||||
|
|
||||||
simulator {
|
|
||||||
// TODO: define status and reply messages here
|
|
||||||
}
|
|
||||||
|
|
||||||
tiles(scale:2) {
|
|
||||||
valueTile("view", "device.view", decoration: "flat") {
|
|
||||||
state "view", label:' ${currentValue} kWh'
|
|
||||||
}
|
|
||||||
valueTile("month", "device.month", width: 6, height : 3, decoration: "flat") {
|
|
||||||
state "month", label:' ${currentValue}'
|
|
||||||
}
|
|
||||||
valueTile("real", "device.real", width: 2, height : 2, decoration: "flat") {
|
|
||||||
state "real", label:' ${currentValue}'
|
|
||||||
}
|
|
||||||
valueTile("tier", "device.tier", width: 2, height : 2, decoration: "flat") {
|
|
||||||
state "tier", label:' ${currentValue}'
|
|
||||||
}
|
|
||||||
valueTile("plan", "device.plan", width: 2, height : 2, decoration: "flat") {
|
|
||||||
state "plan", label:' ${currentValue}'
|
|
||||||
}
|
|
||||||
|
|
||||||
htmlTile(name:"deepLink", action:"linkApp", whitelist:["code.jquery.com",
|
|
||||||
"ajax.googleapis.com",
|
|
||||||
"fonts.googleapis.com",
|
|
||||||
"code.highcharts.com",
|
|
||||||
"enertalk-card.encoredtech.com",
|
|
||||||
"s3-ap-northeast-1.amazonaws.com",
|
|
||||||
"s3.amazonaws.com",
|
|
||||||
"ui-hub.encoredtech.com",
|
|
||||||
"enertalk-auth.encoredtech.com",
|
|
||||||
"api.encoredtech.com",
|
|
||||||
"cdnjs.cloudflare.com",
|
|
||||||
"encoredtech.com",
|
|
||||||
"itunes.apple.com"], width:2, height:2){}
|
|
||||||
|
|
||||||
main (["view"])
|
|
||||||
details (["month", "real", "tier", "plan", "deepLink"])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mappings {
|
|
||||||
|
|
||||||
path("/linkApp") {action: [ GET: "getLinkedApp" ]}
|
|
||||||
}
|
|
||||||
|
|
||||||
def getLinkedApp() {
|
|
||||||
def lang = clientLocale?.language
|
|
||||||
if ("${lang}" == "ko") {
|
|
||||||
lang = "<p style=\'margin-left:15vw; color: #aeaeb0;\'>기기 설정</p>"
|
|
||||||
} else {
|
|
||||||
lang = "<p style=\'margin-left:5vw; color: #aeaeb0;\'>Setup Device</p>"
|
|
||||||
}
|
|
||||||
renderHTML() {
|
|
||||||
head {
|
|
||||||
"""
|
|
||||||
<meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no, width=device-width, height=device-height">
|
|
||||||
<style>
|
|
||||||
#레이어_1 { margin-left : 17vw; width : 50vw; height : 50vw;}
|
|
||||||
.st0{fill:#B5B6BB;}
|
|
||||||
</style>
|
|
||||||
"""
|
|
||||||
}
|
|
||||||
body {
|
|
||||||
"""
|
|
||||||
<div id="container">
|
|
||||||
<a id="st-deep-link" href="#">
|
|
||||||
<svg version="1.1" id="레이어_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 40 40" style="enable-background:new 0 0 40 40;" xml:space="preserve"><path class="st0" d="M20,0C9,0,0,9,0,20C0,30.5,8,39,18.2,40l3.8-4.8l-3.9-4.8c-4.9-0.9-8.6-5.2-8.6-10.4c0-5.8,4.7-10.5,10.5-10.5
|
|
||||||
S30.5,14.2,30.5,20c0,5.1-3.7,9.4-8.5,10.3l3.7,4.5L21.8,40C32,39.1,40,30.5,40,20C40,9,31,0,20,0z"/></svg>
|
|
||||||
</a>
|
|
||||||
${lang}
|
|
||||||
</div>
|
|
||||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
|
|
||||||
<script>
|
|
||||||
var ua = navigator.userAgent.toLowerCase();
|
|
||||||
var isAndroid = ua.indexOf("android") > -1;
|
|
||||||
if(!isAndroid) {
|
|
||||||
\$("#st-deep-link").attr("href", "https://itunes.apple.com/kr/app/enertalk-for-home/id1024660780?mt=8");
|
|
||||||
} else {
|
|
||||||
\$("#st-deep-link").attr("href", "market://details?id=com.ionicframework.enertalkhome874425");
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
"""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,272 +0,0 @@
|
|||||||
/**
|
|
||||||
* Fibaro Door/Window Sensor ZW5
|
|
||||||
*
|
|
||||||
* Copyright 2016 Fibar Group S.A.
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
metadata {
|
|
||||||
definition (name: "Fibaro Door/Window Sensor ZW5 with Temperature", namespace: "fibargroup", author: "Fibar Group S.A.") {
|
|
||||||
capability "Battery"
|
|
||||||
capability "Contact Sensor"
|
|
||||||
capability "Sensor"
|
|
||||||
capability "Configuration"
|
|
||||||
capability "Tamper Alert"
|
|
||||||
|
|
||||||
capability "Temperature Measurement"
|
|
||||||
|
|
||||||
fingerprint deviceId: "0x0701", inClusters: "0x5E, 0x85, 0x59, 0x22, 0x20, 0x80, 0x70, 0x56, 0x5A, 0x7A, 0x72, 0x8E, 0x71, 0x73, 0x98, 0x2B, 0x9C, 0x30, 0x31, 0x86", outClusters: ""
|
|
||||||
fingerprint deviceId: "0x0701", inClusters: "0x5E, 0x85, 0x59, 0x22, 0x20, 0x80, 0x70, 0x56, 0x5A, 0x7A, 0x72, 0x8E, 0x71, 0x73, 0x98, 0x2B, 0x9C, 0x30, 0x31, 0x86, 0x84", outClusters: ""//actual NIF
|
|
||||||
}
|
|
||||||
|
|
||||||
simulator {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
tiles(scale: 2) {
|
|
||||||
multiAttributeTile(name:"FGK", type:"lighting", width:6, height:4) {//with generic type secondary control text is not displayed in Android app
|
|
||||||
tileAttribute("device.contact", key:"PRIMARY_CONTROL") {
|
|
||||||
attributeState("open", icon:"st.contact.contact.open", backgroundColor:"#ffa81e")
|
|
||||||
attributeState("closed", icon:"st.contact.contact.closed", backgroundColor:"#79b821")
|
|
||||||
}
|
|
||||||
|
|
||||||
tileAttribute("device.tamper", key:"SECONDARY_CONTROL") {
|
|
||||||
attributeState("active", label:'tamper active', backgroundColor:"#53a7c0")
|
|
||||||
attributeState("inactive", label:'tamper inactive', backgroundColor:"#ffffff")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
valueTile("battery", "device.battery", inactiveLabel: false, , width: 2, height: 2, decoration: "flat") {
|
|
||||||
state "battery", label:'${currentValue}% battery', unit:""
|
|
||||||
}
|
|
||||||
|
|
||||||
valueTile("temperature", "device.temperature", inactiveLabel: false, 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"]
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
main "FGK"
|
|
||||||
details(["FGK","battery", "temperature"])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// parse events into attributes
|
|
||||||
def parse(String description) {
|
|
||||||
log.debug "Parsing '${description}'"
|
|
||||||
def result = []
|
|
||||||
|
|
||||||
if (description.startsWith("Err 106")) {
|
|
||||||
if (state.sec) {
|
|
||||||
result = createEvent(descriptionText:description, displayed:false)
|
|
||||||
} else {
|
|
||||||
result = createEvent(
|
|
||||||
descriptionText: "FGK failed to complete the network security key exchange. If you are unable to receive data from it, you must remove it from your network and add it again.",
|
|
||||||
eventType: "ALERT",
|
|
||||||
name: "secureInclusion",
|
|
||||||
value: "failed",
|
|
||||||
displayed: true,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else if (description == "updated") {
|
|
||||||
return null
|
|
||||||
} else {
|
|
||||||
def cmd = zwave.parse(description, [0x31: 5, 0x56: 1, 0x71: 3, 0x72: 2, 0x80: 1, 0x84: 2, 0x85: 2, 0x86: 1, 0x98: 1])
|
|
||||||
|
|
||||||
if (cmd) {
|
|
||||||
log.debug "Parsed '${cmd}'"
|
|
||||||
zwaveEvent(cmd)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//security
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.securityv1.SecurityMessageEncapsulation cmd) {
|
|
||||||
def encapsulatedCommand = cmd.encapsulatedCommand([0x71: 3, 0x84: 2, 0x85: 2, 0x98: 1])
|
|
||||||
if (encapsulatedCommand) {
|
|
||||||
return zwaveEvent(encapsulatedCommand)
|
|
||||||
} else {
|
|
||||||
log.warn "Unable to extract encapsulated cmd from $cmd"
|
|
||||||
createEvent(descriptionText: cmd.toString())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//crc16
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.crc16encapv1.Crc16Encap cmd)
|
|
||||||
{
|
|
||||||
def versions = [0x31: 5, 0x72: 2, 0x80: 1, 0x86: 1]
|
|
||||||
def version = versions[cmd.commandClass as Integer]
|
|
||||||
def ccObj = version ? zwave.commandClass(cmd.commandClass, version) : zwave.commandClass(cmd.commandClass)
|
|
||||||
def encapsulatedCommand = ccObj?.command(cmd.command)?.parse(cmd.data)
|
|
||||||
if (!encapsulatedCommand) {
|
|
||||||
log.debug "Could not extract command from $cmd"
|
|
||||||
} else {
|
|
||||||
zwaveEvent(encapsulatedCommand)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.notificationv3.NotificationReport cmd) {
|
|
||||||
//it is assumed that default notification events are used
|
|
||||||
//(parameter 20 was not changed before device's re-inclusion)
|
|
||||||
def map = [:]
|
|
||||||
if (cmd.notificationType == 6) {
|
|
||||||
switch (cmd.event) {
|
|
||||||
case 22:
|
|
||||||
map.name = "contact"
|
|
||||||
map.value = "open"
|
|
||||||
map.descriptionText = "${device.displayName}: is open"
|
|
||||||
break
|
|
||||||
|
|
||||||
case 23:
|
|
||||||
map.name = "contact"
|
|
||||||
map.value = "closed"
|
|
||||||
map.descriptionText = "${device.displayName}: is closed"
|
|
||||||
break
|
|
||||||
}
|
|
||||||
} else if (cmd.notificationType == 7) {
|
|
||||||
switch (cmd.event) {
|
|
||||||
case 0:
|
|
||||||
map.name = "tamper"
|
|
||||||
map.value = "inactive"
|
|
||||||
map.descriptionText = "${device.displayName}: tamper alarm has been deactivated"
|
|
||||||
break
|
|
||||||
|
|
||||||
case 3:
|
|
||||||
map.name = "tamper"
|
|
||||||
map.value = "active"
|
|
||||||
map.descriptionText = "${device.displayName}: tamper alarm activated"
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
createEvent(map)
|
|
||||||
}
|
|
||||||
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.batteryv1.BatteryReport cmd) {
|
|
||||||
def map = [:]
|
|
||||||
map.name = "battery"
|
|
||||||
map.value = cmd.batteryLevel == 255 ? 1 : cmd.batteryLevel.toString()
|
|
||||||
map.unit = "%"
|
|
||||||
map.displayed = true
|
|
||||||
createEvent(map)
|
|
||||||
}
|
|
||||||
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.wakeupv2.WakeUpNotification cmd) {
|
|
||||||
def event = createEvent(descriptionText: "${device.displayName} woke up", displayed: false)
|
|
||||||
def cmds = []
|
|
||||||
cmds << encap(zwave.batteryV1.batteryGet())
|
|
||||||
cmds << "delay 500"
|
|
||||||
cmds << encap(zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType: 1, scale: 0))
|
|
||||||
cmds << "delay 1200"
|
|
||||||
cmds << encap(zwave.wakeUpV1.wakeUpNoMoreInformation())
|
|
||||||
[event, response(cmds)]
|
|
||||||
}
|
|
||||||
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.manufacturerspecificv2.ManufacturerSpecificReport cmd) {
|
|
||||||
log.debug "manufacturerId: ${cmd.manufacturerId}"
|
|
||||||
log.debug "manufacturerName: ${cmd.manufacturerName}"
|
|
||||||
log.debug "productId: ${cmd.productId}"
|
|
||||||
log.debug "productTypeId: ${cmd.productTypeId}"
|
|
||||||
}
|
|
||||||
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.manufacturerspecificv2.DeviceSpecificReport cmd) {
|
|
||||||
log.debug "deviceIdData: ${cmd.deviceIdData}"
|
|
||||||
log.debug "deviceIdDataFormat: ${cmd.deviceIdDataFormat}"
|
|
||||||
log.debug "deviceIdDataLengthIndicator: ${cmd.deviceIdDataLengthIndicator}"
|
|
||||||
log.debug "deviceIdType: ${cmd.deviceIdType}"
|
|
||||||
|
|
||||||
if (cmd.deviceIdType == 1 && cmd.deviceIdDataFormat == 1) {//serial number in binary format
|
|
||||||
String serialNumber = "h'"
|
|
||||||
|
|
||||||
cmd.deviceIdData.each{ data ->
|
|
||||||
serialNumber += "${String.format("%02X", data)}"
|
|
||||||
}
|
|
||||||
|
|
||||||
updateDataValue("serialNumber", serialNumber)
|
|
||||||
log.debug "${device.displayName} - serial number: ${serialNumber}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.versionv1.VersionReport cmd) {
|
|
||||||
updateDataValue("version", "${cmd.applicationVersion}.${cmd.applicationSubVersion}")
|
|
||||||
log.debug "applicationVersion: ${cmd.applicationVersion}"
|
|
||||||
log.debug "applicationSubVersion: ${cmd.applicationSubVersion}"
|
|
||||||
log.debug "zWaveLibraryType: ${cmd.zWaveLibraryType}"
|
|
||||||
log.debug "zWaveProtocolVersion: ${cmd.zWaveProtocolVersion}"
|
|
||||||
log.debug "zWaveProtocolSubVersion: ${cmd.zWaveProtocolSubVersion}"
|
|
||||||
}
|
|
||||||
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.sensormultilevelv5.SensorMultilevelReport cmd) {
|
|
||||||
def map = [:]
|
|
||||||
if (cmd.sensorType == 1) {
|
|
||||||
// temperature
|
|
||||||
def cmdScale = cmd.scale == 1 ? "F" : "C"
|
|
||||||
map.value = convertTemperatureIfNeeded(cmd.scaledSensorValue, cmdScale, cmd.precision)
|
|
||||||
map.unit = getTemperatureScale()
|
|
||||||
map.name = "temperature"
|
|
||||||
map.displayed = true
|
|
||||||
}
|
|
||||||
|
|
||||||
createEvent(map)
|
|
||||||
}
|
|
||||||
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.deviceresetlocallyv1.DeviceResetLocallyNotification cmd) {
|
|
||||||
log.info "${device.displayName}: received command: $cmd - device has reset itself"
|
|
||||||
}
|
|
||||||
|
|
||||||
def configure() {
|
|
||||||
log.debug "Executing 'configure'"
|
|
||||||
|
|
||||||
def cmds = []
|
|
||||||
|
|
||||||
cmds += zwave.wakeUpV2.wakeUpIntervalSet(seconds:21600, nodeid: zwaveHubNodeId)//FGK's default wake up interval
|
|
||||||
cmds += zwave.manufacturerSpecificV2.manufacturerSpecificGet()
|
|
||||||
cmds += zwave.manufacturerSpecificV2.deviceSpecificGet()
|
|
||||||
cmds += zwave.versionV1.versionGet()
|
|
||||||
cmds += zwave.batteryV1.batteryGet()
|
|
||||||
cmds += zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType: 1, scale: 0)
|
|
||||||
cmds += zwave.associationV2.associationSet(groupingIdentifier:1, nodeId: [zwaveHubNodeId])
|
|
||||||
cmds += zwave.wakeUpV2.wakeUpNoMoreInformation()
|
|
||||||
|
|
||||||
encapSequence(cmds, 500)
|
|
||||||
}
|
|
||||||
|
|
||||||
private secure(physicalgraph.zwave.Command cmd) {
|
|
||||||
zwave.securityV1.securityMessageEncapsulation().encapsulate(cmd).format()
|
|
||||||
}
|
|
||||||
|
|
||||||
private crc16(physicalgraph.zwave.Command cmd) {
|
|
||||||
//zwave.crc16EncapV1.crc16Encap().encapsulate(cmd).format()
|
|
||||||
"5601${cmd.format()}0000"
|
|
||||||
}
|
|
||||||
|
|
||||||
private encapSequence(commands, delay=200) {
|
|
||||||
delayBetween(commands.collect{ encap(it) }, delay)
|
|
||||||
}
|
|
||||||
|
|
||||||
private encap(physicalgraph.zwave.Command cmd) {
|
|
||||||
def secureClasses = [0x20, 0x2B, 0x30, 0x5A, 0x70, 0x71, 0x84, 0x85, 0x8E, 0x9C]
|
|
||||||
|
|
||||||
//todo: check if secure inclusion was successful
|
|
||||||
//if not do not send security-encapsulated command
|
|
||||||
if (secureClasses.find{ it == cmd.commandClassId }) {
|
|
||||||
secure(cmd)
|
|
||||||
} else {
|
|
||||||
crc16(cmd)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,239 +0,0 @@
|
|||||||
/**
|
|
||||||
* Fibaro Door/Window Sensor ZW5
|
|
||||||
*
|
|
||||||
* Copyright 2016 Fibar Group S.A.
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
metadata {
|
|
||||||
definition (name: "Fibaro Door/Window Sensor ZW5", namespace: "fibargroup", author: "Fibar Group S.A.") {
|
|
||||||
capability "Battery"
|
|
||||||
capability "Contact Sensor"
|
|
||||||
capability "Sensor"
|
|
||||||
capability "Configuration"
|
|
||||||
capability "Tamper Alert"
|
|
||||||
|
|
||||||
fingerprint deviceId: "0x0701", inClusters: "0x5E, 0x85, 0x59, 0x22, 0x20, 0x80, 0x70, 0x56, 0x5A, 0x7A, 0x72, 0x8E, 0x71, 0x73, 0x98, 0x2B, 0x9C, 0x30, 0x86, 0x84", outClusters: ""
|
|
||||||
}
|
|
||||||
|
|
||||||
simulator {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
tiles(scale: 2) {
|
|
||||||
multiAttributeTile(name:"FGK", type:"lighting", width:6, height:4) {//with generic type secondary control text is not displayed in Android app
|
|
||||||
tileAttribute("device.contact", key:"PRIMARY_CONTROL") {
|
|
||||||
attributeState("open", icon:"st.contact.contact.open", backgroundColor:"#ffa81e")
|
|
||||||
attributeState("closed", icon:"st.contact.contact.closed", backgroundColor:"#79b821")
|
|
||||||
}
|
|
||||||
|
|
||||||
tileAttribute("device.tamper", key:"SECONDARY_CONTROL") {
|
|
||||||
attributeState("active", label:'tamper active', backgroundColor:"#53a7c0")
|
|
||||||
attributeState("inactive", label:'tamper inactive', backgroundColor:"#ffffff")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
valueTile("battery", "device.battery", inactiveLabel: false, , width: 2, height: 2, decoration: "flat") {
|
|
||||||
state "battery", label:'${currentValue}% battery', unit:""
|
|
||||||
}
|
|
||||||
|
|
||||||
main "FGK"
|
|
||||||
details(["FGK","battery"])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// parse events into attributes
|
|
||||||
def parse(String description) {
|
|
||||||
log.debug "Parsing '${description}'"
|
|
||||||
def result = []
|
|
||||||
|
|
||||||
if (description.startsWith("Err 106")) {
|
|
||||||
if (state.sec) {
|
|
||||||
result = createEvent(descriptionText:description, displayed:false)
|
|
||||||
} else {
|
|
||||||
result = createEvent(
|
|
||||||
descriptionText: "FGK failed to complete the network security key exchange. If you are unable to receive data from it, you must remove it from your network and add it again.",
|
|
||||||
eventType: "ALERT",
|
|
||||||
name: "secureInclusion",
|
|
||||||
value: "failed",
|
|
||||||
displayed: true,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else if (description == "updated") {
|
|
||||||
return null
|
|
||||||
} else {
|
|
||||||
def cmd = zwave.parse(description, [0x56: 1, 0x71: 3, 0x72: 2, 0x80: 1, 0x84: 2, 0x85: 2, 0x86: 1, 0x98: 1])
|
|
||||||
|
|
||||||
if (cmd) {
|
|
||||||
log.debug "Parsed '${cmd}'"
|
|
||||||
zwaveEvent(cmd)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//security
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.securityv1.SecurityMessageEncapsulation cmd) {
|
|
||||||
def encapsulatedCommand = cmd.encapsulatedCommand([0x71: 3, 0x84: 2, 0x85: 2, 0x98: 1])
|
|
||||||
if (encapsulatedCommand) {
|
|
||||||
return zwaveEvent(encapsulatedCommand)
|
|
||||||
} else {
|
|
||||||
log.warn "Unable to extract encapsulated cmd from $cmd"
|
|
||||||
createEvent(descriptionText: cmd.toString())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//crc16
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.crc16encapv1.Crc16Encap cmd)
|
|
||||||
{
|
|
||||||
def versions = [0x72: 2, 0x80: 1, 0x86: 1]
|
|
||||||
def version = versions[cmd.commandClass as Integer]
|
|
||||||
def ccObj = version ? zwave.commandClass(cmd.commandClass, version) : zwave.commandClass(cmd.commandClass)
|
|
||||||
def encapsulatedCommand = ccObj?.command(cmd.command)?.parse(cmd.data)
|
|
||||||
if (!encapsulatedCommand) {
|
|
||||||
log.debug "Could not extract command from $cmd"
|
|
||||||
} else {
|
|
||||||
zwaveEvent(encapsulatedCommand)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.notificationv3.NotificationReport cmd) {
|
|
||||||
//it is assumed that default notification events are used
|
|
||||||
//(parameter 20 was not changed before device's re-inclusion)
|
|
||||||
def map = [:]
|
|
||||||
if (cmd.notificationType == 6) {
|
|
||||||
switch (cmd.event) {
|
|
||||||
case 22:
|
|
||||||
map.name = "contact"
|
|
||||||
map.value = "open"
|
|
||||||
map.descriptionText = "${device.displayName}: is open"
|
|
||||||
break
|
|
||||||
|
|
||||||
case 23:
|
|
||||||
map.name = "contact"
|
|
||||||
map.value = "closed"
|
|
||||||
map.descriptionText = "${device.displayName}: is closed"
|
|
||||||
break
|
|
||||||
}
|
|
||||||
} else if (cmd.notificationType == 7) {
|
|
||||||
switch (cmd.event) {
|
|
||||||
case 0:
|
|
||||||
map.name = "tamper"
|
|
||||||
map.value = "inactive"
|
|
||||||
map.descriptionText = "${device.displayName}: tamper alarm has been deactivated"
|
|
||||||
break
|
|
||||||
|
|
||||||
case 3:
|
|
||||||
map.name = "tamper"
|
|
||||||
map.value = "active"
|
|
||||||
map.descriptionText = "${device.displayName}: tamper alarm activated"
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
createEvent(map)
|
|
||||||
}
|
|
||||||
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.batteryv1.BatteryReport cmd) {
|
|
||||||
def map = [:]
|
|
||||||
map.name = "battery"
|
|
||||||
map.value = cmd.batteryLevel == 255 ? 1 : cmd.batteryLevel.toString()
|
|
||||||
map.unit = "%"
|
|
||||||
map.displayed = true
|
|
||||||
createEvent(map)
|
|
||||||
}
|
|
||||||
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.wakeupv2.WakeUpNotification cmd) {
|
|
||||||
def event = createEvent(descriptionText: "${device.displayName} woke up", displayed: false)
|
|
||||||
def cmds = []
|
|
||||||
cmds << encap(zwave.batteryV1.batteryGet())
|
|
||||||
cmds << "delay 1200"
|
|
||||||
cmds << encap(zwave.wakeUpV1.wakeUpNoMoreInformation())
|
|
||||||
[event, response(cmds)]
|
|
||||||
}
|
|
||||||
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.manufacturerspecificv2.ManufacturerSpecificReport cmd) {
|
|
||||||
log.debug "manufacturerId: ${cmd.manufacturerId}"
|
|
||||||
log.debug "manufacturerName: ${cmd.manufacturerName}"
|
|
||||||
log.debug "productId: ${cmd.productId}"
|
|
||||||
log.debug "productTypeId: ${cmd.productTypeId}"
|
|
||||||
}
|
|
||||||
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.manufacturerspecificv2.DeviceSpecificReport cmd) {
|
|
||||||
log.debug "deviceIdData: ${cmd.deviceIdData}"
|
|
||||||
log.debug "deviceIdDataFormat: ${cmd.deviceIdDataFormat}"
|
|
||||||
log.debug "deviceIdDataLengthIndicator: ${cmd.deviceIdDataLengthIndicator}"
|
|
||||||
log.debug "deviceIdType: ${cmd.deviceIdType}"
|
|
||||||
|
|
||||||
if (cmd.deviceIdType == 1 && cmd.deviceIdDataFormat == 1) {//serial number in binary format
|
|
||||||
String serialNumber = "h'"
|
|
||||||
|
|
||||||
cmd.deviceIdData.each{ data ->
|
|
||||||
serialNumber += "${String.format("%02X", data)}"
|
|
||||||
}
|
|
||||||
|
|
||||||
updateDataValue("serialNumber", serialNumber)
|
|
||||||
log.debug "${device.displayName} - serial number: ${serialNumber}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.versionv1.VersionReport cmd) {
|
|
||||||
updateDataValue("version", "${cmd.applicationVersion}.${cmd.applicationSubVersion}")
|
|
||||||
log.debug "applicationVersion: ${cmd.applicationVersion}"
|
|
||||||
log.debug "applicationSubVersion: ${cmd.applicationSubVersion}"
|
|
||||||
log.debug "zWaveLibraryType: ${cmd.zWaveLibraryType}"
|
|
||||||
log.debug "zWaveProtocolVersion: ${cmd.zWaveProtocolVersion}"
|
|
||||||
log.debug "zWaveProtocolSubVersion: ${cmd.zWaveProtocolSubVersion}"
|
|
||||||
}
|
|
||||||
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.deviceresetlocallyv1.DeviceResetLocallyNotification cmd) {
|
|
||||||
log.info "${device.displayName}: received command: $cmd - device has reset itself"
|
|
||||||
}
|
|
||||||
|
|
||||||
def configure() {
|
|
||||||
log.debug "Executing 'configure'"
|
|
||||||
|
|
||||||
def cmds = []
|
|
||||||
|
|
||||||
cmds += zwave.wakeUpV2.wakeUpIntervalSet(seconds:21600, nodeid: zwaveHubNodeId)//FGK's default wake up interval
|
|
||||||
cmds += zwave.manufacturerSpecificV2.manufacturerSpecificGet()
|
|
||||||
cmds += zwave.manufacturerSpecificV2.deviceSpecificGet()
|
|
||||||
cmds += zwave.versionV1.versionGet()
|
|
||||||
cmds += zwave.batteryV1.batteryGet()
|
|
||||||
cmds += zwave.associationV2.associationSet(groupingIdentifier:1, nodeId: [zwaveHubNodeId])
|
|
||||||
cmds += zwave.wakeUpV2.wakeUpNoMoreInformation()
|
|
||||||
|
|
||||||
encapSequence(cmds, 500)
|
|
||||||
}
|
|
||||||
|
|
||||||
private secure(physicalgraph.zwave.Command cmd) {
|
|
||||||
zwave.securityV1.securityMessageEncapsulation().encapsulate(cmd).format()
|
|
||||||
}
|
|
||||||
|
|
||||||
private crc16(physicalgraph.zwave.Command cmd) {
|
|
||||||
//zwave.crc16EncapV1.crc16Encap().encapsulate(cmd).format()
|
|
||||||
"5601${cmd.format()}0000"
|
|
||||||
}
|
|
||||||
|
|
||||||
private encapSequence(commands, delay=200) {
|
|
||||||
delayBetween(commands.collect{ encap(it) }, delay)
|
|
||||||
}
|
|
||||||
|
|
||||||
private encap(physicalgraph.zwave.Command cmd) {
|
|
||||||
def secureClasses = [0x20, 0x2B, 0x30, 0x5A, 0x70, 0x71, 0x84, 0x85, 0x8E, 0x9C]
|
|
||||||
|
|
||||||
//todo: check if secure inclusion was successful
|
|
||||||
//if not do not send security-encapsulated command
|
|
||||||
if (secureClasses.find{ it == cmd.commandClassId }) {
|
|
||||||
secure(cmd)
|
|
||||||
} else {
|
|
||||||
crc16(cmd)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,269 +0,0 @@
|
|||||||
/**
|
|
||||||
* Fibaro Flood Sensor ZW5
|
|
||||||
*
|
|
||||||
* Copyright 2016 Fibar Group S.A.
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
metadata {
|
|
||||||
definition (name: "Fibaro Flood Sensor ZW5", namespace: "fibargroup", author: "Fibar Group S.A.") {
|
|
||||||
capability "Battery"
|
|
||||||
capability "Configuration"
|
|
||||||
capability "Sensor"
|
|
||||||
capability "Tamper Alert"
|
|
||||||
capability "Temperature Measurement"
|
|
||||||
capability "Water Sensor"
|
|
||||||
|
|
||||||
fingerprint deviceId: "0x0701", inClusters: "0x5E, 0x22, 0x85, 0x59, 0x20, 0x80, 0x70, 0x56, 0x5A, 0x7A, 0x72, 0x8E, 0x71, 0x73, 0x98, 0x9C, 0x31, 0x86", outClusters: ""
|
|
||||||
}
|
|
||||||
|
|
||||||
simulator {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
tiles(scale: 2) {
|
|
||||||
multiAttributeTile(name:"FGFS", type:"lighting", width:6, height:4) {//with generic type secondary control text is not displayed in Android app
|
|
||||||
tileAttribute("device.water", key:"PRIMARY_CONTROL") {
|
|
||||||
attributeState("dry", icon:"st.alarm.water.dry", backgroundColor:"#79b821")
|
|
||||||
attributeState("wet", icon:"st.alarm.water.wet", backgroundColor:"#ffa81e")
|
|
||||||
}
|
|
||||||
|
|
||||||
tileAttribute("device.tamper", key:"SECONDARY_CONTROL") {
|
|
||||||
attributeState("active", label:'tamper active', backgroundColor:"#53a7c0")
|
|
||||||
attributeState("inactive", label:'tamper inactive', backgroundColor:"#ffffff")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
valueTile("temperature", "device.temperature", inactiveLabel: false, 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", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
|
||||||
state "battery", label:'${currentValue}% battery', unit:""
|
|
||||||
}
|
|
||||||
|
|
||||||
main "FGFS"
|
|
||||||
details(["FGFS","battery", "temperature"])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// parse events into attributes
|
|
||||||
def parse(String description) {
|
|
||||||
log.debug "Parsing '${description}'"
|
|
||||||
def result = []
|
|
||||||
|
|
||||||
if (description.startsWith("Err 106")) {
|
|
||||||
if (state.sec) {
|
|
||||||
result = createEvent(descriptionText:description, displayed:false)
|
|
||||||
} else {
|
|
||||||
result = createEvent(
|
|
||||||
descriptionText: "FGFS failed to complete the network security key exchange. If you are unable to receive data from it, you must remove it from your network and add it again.",
|
|
||||||
eventType: "ALERT",
|
|
||||||
name: "secureInclusion",
|
|
||||||
value: "failed",
|
|
||||||
displayed: true,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else if (description == "updated") {
|
|
||||||
return null
|
|
||||||
} else {
|
|
||||||
def cmd = zwave.parse(description, [0x31: 5, 0x56: 1, 0x71: 3, 0x72:2, 0x80: 1, 0x84: 2, 0x85: 2, 0x86: 1, 0x98: 1])
|
|
||||||
|
|
||||||
if (cmd) {
|
|
||||||
log.debug "Parsed '${cmd}'"
|
|
||||||
zwaveEvent(cmd)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//security
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.securityv1.SecurityMessageEncapsulation cmd) {
|
|
||||||
def encapsulatedCommand = cmd.encapsulatedCommand([0x71: 3, 0x84: 2, 0x85: 2, 0x86: 1, 0x98: 1])
|
|
||||||
if (encapsulatedCommand) {
|
|
||||||
return zwaveEvent(encapsulatedCommand)
|
|
||||||
} else {
|
|
||||||
log.warn "Unable to extract encapsulated cmd from $cmd"
|
|
||||||
createEvent(descriptionText: cmd.toString())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//crc16
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.crc16encapv1.Crc16Encap cmd)
|
|
||||||
{
|
|
||||||
def versions = [0x31: 5, 0x72: 2, 0x80: 1]
|
|
||||||
def version = versions[cmd.commandClass as Integer]
|
|
||||||
def ccObj = version ? zwave.commandClass(cmd.commandClass, version) : zwave.commandClass(cmd.commandClass)
|
|
||||||
def encapsulatedCommand = ccObj?.command(cmd.command)?.parse(cmd.data)
|
|
||||||
if (!encapsulatedCommand) {
|
|
||||||
log.debug "Could not extract command from $cmd"
|
|
||||||
} else {
|
|
||||||
zwaveEvent(encapsulatedCommand)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.wakeupv2.WakeUpNotification cmd)
|
|
||||||
{
|
|
||||||
def event = createEvent(descriptionText: "${device.displayName} woke up", displayed: false)
|
|
||||||
def cmds = []
|
|
||||||
cmds << encap(zwave.batteryV1.batteryGet())
|
|
||||||
cmds << "delay 500"
|
|
||||||
cmds << encap(zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType: 1, scale: 0))
|
|
||||||
cmds << "delay 1200"
|
|
||||||
cmds << encap(zwave.wakeUpV1.wakeUpNoMoreInformation())
|
|
||||||
[event, response(cmds)]
|
|
||||||
}
|
|
||||||
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.manufacturerspecificv2.ManufacturerSpecificReport cmd) {
|
|
||||||
log.debug "manufacturerId: ${cmd.manufacturerId}"
|
|
||||||
log.debug "manufacturerName: ${cmd.manufacturerName}"
|
|
||||||
log.debug "productId: ${cmd.productId}"
|
|
||||||
log.debug "productTypeId: ${cmd.productTypeId}"
|
|
||||||
}
|
|
||||||
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.manufacturerspecificv2.DeviceSpecificReport cmd) {
|
|
||||||
log.debug "deviceIdData: ${cmd.deviceIdData}"
|
|
||||||
log.debug "deviceIdDataFormat: ${cmd.deviceIdDataFormat}"
|
|
||||||
log.debug "deviceIdDataLengthIndicator: ${cmd.deviceIdDataLengthIndicator}"
|
|
||||||
log.debug "deviceIdType: ${cmd.deviceIdType}"
|
|
||||||
|
|
||||||
if (cmd.deviceIdType == 1 && cmd.deviceIdDataFormat == 1) {//serial number in binary format
|
|
||||||
String serialNumber = "h'"
|
|
||||||
|
|
||||||
cmd.deviceIdData.each{ data ->
|
|
||||||
serialNumber += "${String.format("%02X", data)}"
|
|
||||||
}
|
|
||||||
|
|
||||||
updateDataValue("serialNumber", serialNumber)
|
|
||||||
log.debug "${device.displayName} - serial number: ${serialNumber}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.versionv1.VersionReport cmd) {
|
|
||||||
updateDataValue("version", "${cmd.applicationVersion}.${cmd.applicationSubVersion}")
|
|
||||||
log.debug "applicationVersion: ${cmd.applicationVersion}"
|
|
||||||
log.debug "applicationSubVersion: ${cmd.applicationSubVersion}"
|
|
||||||
log.debug "zWaveLibraryType: ${cmd.zWaveLibraryType}"
|
|
||||||
log.debug "zWaveProtocolVersion: ${cmd.zWaveProtocolVersion}"
|
|
||||||
log.debug "zWaveProtocolSubVersion: ${cmd.zWaveProtocolSubVersion}"
|
|
||||||
}
|
|
||||||
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.batteryv1.BatteryReport cmd) {
|
|
||||||
def map = [:]
|
|
||||||
map.name = "battery"
|
|
||||||
map.value = cmd.batteryLevel == 255 ? 1 : cmd.batteryLevel.toString()
|
|
||||||
map.unit = "%"
|
|
||||||
map.displayed = true
|
|
||||||
createEvent(map)
|
|
||||||
}
|
|
||||||
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.notificationv3.NotificationReport cmd) {
|
|
||||||
def map = [:]
|
|
||||||
if (cmd.notificationType == 5) {
|
|
||||||
switch (cmd.event) {
|
|
||||||
case 2:
|
|
||||||
map.name = "water"
|
|
||||||
map.value = "wet"
|
|
||||||
map.descriptionText = "${device.displayName} is ${map.value}"
|
|
||||||
break
|
|
||||||
|
|
||||||
case 0:
|
|
||||||
map.name = "water"
|
|
||||||
map.value = "dry"
|
|
||||||
map.descriptionText = "${device.displayName} is ${map.value}"
|
|
||||||
break
|
|
||||||
}
|
|
||||||
} else if (cmd.notificationType == 7) {
|
|
||||||
switch (cmd.event) {
|
|
||||||
case 0:
|
|
||||||
map.name = "tamper"
|
|
||||||
map.value = "inactive"
|
|
||||||
map.descriptionText = "${device.displayName}: tamper alarm has been deactivated"
|
|
||||||
break
|
|
||||||
|
|
||||||
case 3:
|
|
||||||
map.name = "tamper"
|
|
||||||
map.value = "active"
|
|
||||||
map.descriptionText = "${device.displayName}: tamper alarm activated"
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
createEvent(map)
|
|
||||||
}
|
|
||||||
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.sensormultilevelv5.SensorMultilevelReport cmd) {
|
|
||||||
def map = [:]
|
|
||||||
if (cmd.sensorType == 1) {
|
|
||||||
// temperature
|
|
||||||
def cmdScale = cmd.scale == 1 ? "F" : "C"
|
|
||||||
map.value = convertTemperatureIfNeeded(cmd.scaledSensorValue, cmdScale, cmd.precision)
|
|
||||||
map.unit = getTemperatureScale()
|
|
||||||
map.name = "temperature"
|
|
||||||
map.displayed = true
|
|
||||||
}
|
|
||||||
|
|
||||||
createEvent(map)
|
|
||||||
}
|
|
||||||
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.deviceresetlocallyv1.DeviceResetLocallyNotification cmd) {
|
|
||||||
log.info "${device.displayName}: received command: $cmd - device has reset itself"
|
|
||||||
}
|
|
||||||
|
|
||||||
def configure() {
|
|
||||||
log.debug "Executing 'configure'"
|
|
||||||
|
|
||||||
def cmds = []
|
|
||||||
|
|
||||||
cmds += zwave.wakeUpV2.wakeUpIntervalSet(seconds:21600, nodeid: zwaveHubNodeId)//FGFS' default wake up interval
|
|
||||||
cmds += zwave.manufacturerSpecificV2.manufacturerSpecificGet()
|
|
||||||
cmds += zwave.manufacturerSpecificV2.deviceSpecificGet()
|
|
||||||
cmds += zwave.versionV1.versionGet()
|
|
||||||
cmds += zwave.batteryV1.batteryGet()
|
|
||||||
cmds += zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType: 1, scale: 0)
|
|
||||||
cmds += zwave.associationV2.associationSet(groupingIdentifier:1, nodeId: [zwaveHubNodeId])
|
|
||||||
cmds += zwave.wakeUpV2.wakeUpNoMoreInformation()
|
|
||||||
|
|
||||||
encapSequence(cmds, 500)
|
|
||||||
}
|
|
||||||
|
|
||||||
private secure(physicalgraph.zwave.Command cmd) {
|
|
||||||
zwave.securityV1.securityMessageEncapsulation().encapsulate(cmd).format()
|
|
||||||
}
|
|
||||||
|
|
||||||
private crc16(physicalgraph.zwave.Command cmd) {
|
|
||||||
//zwave.crc16EncapV1.crc16Encap().encapsulate(cmd).format()
|
|
||||||
"5601${cmd.format()}0000"
|
|
||||||
}
|
|
||||||
|
|
||||||
private encapSequence(commands, delay=200) {
|
|
||||||
delayBetween(commands.collect{ encap(it) }, delay)
|
|
||||||
}
|
|
||||||
|
|
||||||
private encap(physicalgraph.zwave.Command cmd) {
|
|
||||||
def secureClasses = [0x20, 0x5A, 0x70, 0x71, 0x84, 0x85, 0x8E, 0x9C]
|
|
||||||
|
|
||||||
//todo: check if secure inclusion was successful
|
|
||||||
//if not do not send security-encapsulated command
|
|
||||||
if (secureClasses.find{ it == cmd.commandClassId }) {
|
|
||||||
secure(cmd)
|
|
||||||
} else {
|
|
||||||
crc16(cmd)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,282 +0,0 @@
|
|||||||
/**
|
|
||||||
* Fibaro Motion Sensor ZW5
|
|
||||||
*
|
|
||||||
* Copyright 2016 Fibar Group S.A.
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
metadata {
|
|
||||||
definition (name: "Fibaro Motion Sensor ZW5", namespace: "fibargroup", author: "Fibar Group S.A.") {
|
|
||||||
capability "Battery"
|
|
||||||
capability "Configuration"
|
|
||||||
capability "Illuminance Measurement"
|
|
||||||
capability "Motion Sensor"
|
|
||||||
capability "Sensor"
|
|
||||||
capability "Tamper Alert"
|
|
||||||
capability "Temperature Measurement"
|
|
||||||
|
|
||||||
fingerprint deviceId: "0x0701", inClusters: "0x5E, 0x20, 0x86, 0x72, 0x5A, 0x59, 0x85, 0x73, 0x84, 0x80, 0x71, 0x56, 0x70, 0x31, 0x8E, 0x22, 0x30, 0x9C, 0x98, 0x7A", outClusters: ""
|
|
||||||
}
|
|
||||||
|
|
||||||
simulator {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
tiles(scale: 2) {
|
|
||||||
multiAttributeTile(name:"FGMS", type:"lighting", width:6, height:4) {//with generic type secondary control text is not displayed in Android app
|
|
||||||
tileAttribute("device.motion", key:"PRIMARY_CONTROL") {
|
|
||||||
attributeState("inactive", label:"no motion", icon:"st.motion.motion.inactive", backgroundColor:"#79b821")
|
|
||||||
attributeState("active", label:"motion", icon:"st.motion.motion.active", backgroundColor:"#ffa81e")
|
|
||||||
}
|
|
||||||
|
|
||||||
tileAttribute("device.tamper", key:"SECONDARY_CONTROL") {
|
|
||||||
attributeState("active", label:'tamper active', backgroundColor:"#53a7c0")
|
|
||||||
attributeState("inactive", label:'tamper inactive', backgroundColor:"#ffffff")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
valueTile("temperature", "device.temperature", inactiveLabel: false, 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("illuminance", "device.illuminance", inactiveLabel: false, width: 2, height: 2) {
|
|
||||||
state "luminosity", label:'${currentValue} ${unit}', unit:"lux"
|
|
||||||
}
|
|
||||||
|
|
||||||
valueTile("battery", "device.battery", inactiveLabel: false, width: 2, height: 2, decoration: "flat") {
|
|
||||||
state "battery", label:'${currentValue}% battery', unit:""
|
|
||||||
}
|
|
||||||
|
|
||||||
main "FGMS"
|
|
||||||
details(["FGMS","battery","temperature","illuminance"])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// parse events into attributes
|
|
||||||
def parse(String description) {
|
|
||||||
log.debug "Parsing '${description}'"
|
|
||||||
def result = []
|
|
||||||
|
|
||||||
if (description.startsWith("Err 106")) {
|
|
||||||
if (state.sec) {
|
|
||||||
result = createEvent(descriptionText:description, displayed:false)
|
|
||||||
} else {
|
|
||||||
result = createEvent(
|
|
||||||
descriptionText: "FGK failed to complete the network security key exchange. If you are unable to receive data from it, you must remove it from your network and add it again.",
|
|
||||||
eventType: "ALERT",
|
|
||||||
name: "secureInclusion",
|
|
||||||
value: "failed",
|
|
||||||
displayed: true,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else if (description == "updated") {
|
|
||||||
return null
|
|
||||||
} else {
|
|
||||||
def cmd = zwave.parse(description, [0x31: 5, 0x56: 1, 0x71: 3, 0x72: 2, 0x80: 1, 0x84: 2, 0x85: 2, 0x86: 1, 0x98: 1])
|
|
||||||
|
|
||||||
if (cmd) {
|
|
||||||
log.debug "Parsed '${cmd}'"
|
|
||||||
zwaveEvent(cmd)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//security
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.securityv1.SecurityMessageEncapsulation cmd) {
|
|
||||||
def encapsulatedCommand = cmd.encapsulatedCommand([0x71: 3, 0x84: 2, 0x85: 2, 0x86: 1, 0x98: 1])
|
|
||||||
if (encapsulatedCommand) {
|
|
||||||
return zwaveEvent(encapsulatedCommand)
|
|
||||||
} else {
|
|
||||||
log.warn "Unable to extract encapsulated cmd from $cmd"
|
|
||||||
createEvent(descriptionText: cmd.toString())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//crc16
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.crc16encapv1.Crc16Encap cmd)
|
|
||||||
{
|
|
||||||
def versions = [0x31: 5, 0x71: 3, 0x72: 2, 0x80: 1, 0x84: 2, 0x85: 2, 0x86: 1]
|
|
||||||
def version = versions[cmd.commandClass as Integer]
|
|
||||||
def ccObj = version ? zwave.commandClass(cmd.commandClass, version) : zwave.commandClass(cmd.commandClass)
|
|
||||||
def encapsulatedCommand = ccObj?.command(cmd.command)?.parse(cmd.data)
|
|
||||||
if (!encapsulatedCommand) {
|
|
||||||
log.debug "Could not extract command from $cmd"
|
|
||||||
} else {
|
|
||||||
zwaveEvent(encapsulatedCommand)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.sensormultilevelv5.SensorMultilevelReport cmd) {
|
|
||||||
def map = [ displayed: true ]
|
|
||||||
switch (cmd.sensorType) {
|
|
||||||
case 1:
|
|
||||||
def cmdScale = cmd.scale == 1 ? "F" : "C"
|
|
||||||
map.name = "temperature"
|
|
||||||
map.unit = getTemperatureScale()
|
|
||||||
map.value = convertTemperatureIfNeeded(cmd.scaledSensorValue, cmdScale, cmd.precision)
|
|
||||||
break
|
|
||||||
case 3:
|
|
||||||
map.name = "illuminance"
|
|
||||||
map.value = cmd.scaledSensorValue.toInteger().toString()
|
|
||||||
map.unit = "lux"
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
createEvent(map)
|
|
||||||
}
|
|
||||||
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.notificationv3.NotificationReport cmd) {
|
|
||||||
def map = [:]
|
|
||||||
if (cmd.notificationType == 7) {
|
|
||||||
switch (cmd.event) {
|
|
||||||
case 0:
|
|
||||||
if (cmd.eventParameter[0] == 3) {
|
|
||||||
map.name = "tamper"
|
|
||||||
map.value = "inactive"
|
|
||||||
map.descriptionText = "${device.displayName}: tamper alarm has been deactivated"
|
|
||||||
}
|
|
||||||
if (cmd.eventParameter[0] == 8) {
|
|
||||||
map.name = "motion"
|
|
||||||
map.value = "inactive"
|
|
||||||
map.descriptionText = "${device.displayName}: motion has stopped"
|
|
||||||
}
|
|
||||||
break
|
|
||||||
|
|
||||||
case 3:
|
|
||||||
map.name = "tamper"
|
|
||||||
map.value = "active"
|
|
||||||
map.descriptionText = "${device.displayName}: tamper alarm activated"
|
|
||||||
break
|
|
||||||
|
|
||||||
case 8:
|
|
||||||
map.name = "motion"
|
|
||||||
map.value = "active"
|
|
||||||
map.descriptionText = "${device.displayName}: motion detected"
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
createEvent(map)
|
|
||||||
}
|
|
||||||
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.batteryv1.BatteryReport cmd) {
|
|
||||||
def map = [:]
|
|
||||||
map.name = "battery"
|
|
||||||
map.value = cmd.batteryLevel == 255 ? 1 : cmd.batteryLevel.toString()
|
|
||||||
map.unit = "%"
|
|
||||||
map.displayed = true
|
|
||||||
createEvent(map)
|
|
||||||
}
|
|
||||||
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.wakeupv2.WakeUpNotification cmd)
|
|
||||||
{
|
|
||||||
def event = createEvent(descriptionText: "${device.displayName} woke up", displayed: false)
|
|
||||||
def cmds = []
|
|
||||||
cmds << encap(zwave.batteryV1.batteryGet())
|
|
||||||
cmds << "delay 500"
|
|
||||||
cmds << encap(zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType: 1, scale: 0))
|
|
||||||
cmds << "delay 500"
|
|
||||||
cmds << encap(zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType: 3, scale: 1))
|
|
||||||
cmds << "delay 1200"
|
|
||||||
cmds << encap(zwave.wakeUpV1.wakeUpNoMoreInformation())
|
|
||||||
[event, response(cmds)]
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.manufacturerspecificv2.ManufacturerSpecificReport cmd) {
|
|
||||||
log.debug "manufacturerId: ${cmd.manufacturerId}"
|
|
||||||
log.debug "manufacturerName: ${cmd.manufacturerName}"
|
|
||||||
log.debug "productId: ${cmd.productId}"
|
|
||||||
log.debug "productTypeId: ${cmd.productTypeId}"
|
|
||||||
}
|
|
||||||
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.manufacturerspecificv2.DeviceSpecificReport cmd) {
|
|
||||||
log.debug "deviceIdData: ${cmd.deviceIdData}"
|
|
||||||
log.debug "deviceIdDataFormat: ${cmd.deviceIdDataFormat}"
|
|
||||||
log.debug "deviceIdDataLengthIndicator: ${cmd.deviceIdDataLengthIndicator}"
|
|
||||||
log.debug "deviceIdType: ${cmd.deviceIdType}"
|
|
||||||
|
|
||||||
if (cmd.deviceIdType == 1 && cmd.deviceIdDataFormat == 1) {//serial number in binary format
|
|
||||||
String serialNumber = "h'"
|
|
||||||
|
|
||||||
cmd.deviceIdData.each{ data ->
|
|
||||||
serialNumber += "${String.format("%02X", data)}"
|
|
||||||
}
|
|
||||||
|
|
||||||
updateDataValue("serialNumber", serialNumber)
|
|
||||||
log.debug "${device.displayName} - serial number: ${serialNumber}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.versionv1.VersionReport cmd) {
|
|
||||||
updateDataValue("version", "${cmd.applicationVersion}.${cmd.applicationSubVersion}")
|
|
||||||
log.debug "applicationVersion: ${cmd.applicationVersion}"
|
|
||||||
log.debug "applicationSubVersion: ${cmd.applicationSubVersion}"
|
|
||||||
log.debug "zWaveLibraryType: ${cmd.zWaveLibraryType}"
|
|
||||||
log.debug "zWaveProtocolVersion: ${cmd.zWaveProtocolVersion}"
|
|
||||||
log.debug "zWaveProtocolSubVersion: ${cmd.zWaveProtocolSubVersion}"
|
|
||||||
}
|
|
||||||
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.deviceresetlocallyv1.DeviceResetLocallyNotification cmd) {
|
|
||||||
log.info "${device.displayName}: received command: $cmd - device has reset itself"
|
|
||||||
}
|
|
||||||
|
|
||||||
def configure() {
|
|
||||||
log.debug "Executing 'configure'"
|
|
||||||
|
|
||||||
def cmds = []
|
|
||||||
|
|
||||||
cmds += zwave.wakeUpV2.wakeUpIntervalSet(seconds: 7200, nodeid: zwaveHubNodeId)//FGMS' default wake up interval
|
|
||||||
cmds += zwave.manufacturerSpecificV2.manufacturerSpecificGet()
|
|
||||||
cmds += zwave.manufacturerSpecificV2.deviceSpecificGet()
|
|
||||||
cmds += zwave.versionV1.versionGet()
|
|
||||||
cmds += zwave.associationV2.associationSet(groupingIdentifier:1, nodeId:[zwaveHubNodeId])
|
|
||||||
cmds += zwave.batteryV1.batteryGet()
|
|
||||||
cmds += zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType: 1, scale: 0)
|
|
||||||
cmds += zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType: 3, scale: 1)
|
|
||||||
cmds += zwave.wakeUpV2.wakeUpNoMoreInformation()
|
|
||||||
|
|
||||||
encapSequence(cmds, 500)
|
|
||||||
}
|
|
||||||
|
|
||||||
private secure(physicalgraph.zwave.Command cmd) {
|
|
||||||
zwave.securityV1.securityMessageEncapsulation().encapsulate(cmd).format()
|
|
||||||
}
|
|
||||||
|
|
||||||
private crc16(physicalgraph.zwave.Command cmd) {
|
|
||||||
//zwave.crc16encapV1.crc16Encap().encapsulate(cmd).format()
|
|
||||||
"5601${cmd.format()}0000"
|
|
||||||
}
|
|
||||||
|
|
||||||
private encapSequence(commands, delay=200) {
|
|
||||||
delayBetween(commands.collect{ encap(it) }, delay)
|
|
||||||
}
|
|
||||||
|
|
||||||
private encap(physicalgraph.zwave.Command cmd) {
|
|
||||||
def secureClasses = [0x20, 0x30, 0x5A, 0x70, 0x71, 0x84, 0x85, 0x8E, 0x9C]
|
|
||||||
|
|
||||||
//todo: check if secure inclusion was successful
|
|
||||||
//if not do not send security-encapsulated command
|
|
||||||
if (secureClasses.find{ it == cmd.commandClassId }) {
|
|
||||||
secure(cmd)
|
|
||||||
} else {
|
|
||||||
crc16(cmd)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,795 +0,0 @@
|
|||||||
/**
|
|
||||||
* CoopBoss H3Vx
|
|
||||||
* 02/29/16 Fixed app crash with Android by changing the syntax of default state in tile definition.
|
|
||||||
* Fixed null value errors during join process. Added 3 new commands to refresh data.
|
|
||||||
*
|
|
||||||
* 01/18/16 Masked invalid temperature reporting when TempProbe1 is below 0C
|
|
||||||
* Added setBaseCurrentNE, readBaseCurrentNE, commands as well as baseCurrentNE attribute.
|
|
||||||
*
|
|
||||||
* Copyright 2016 John Rucker
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
* Icon location = http://scripts.3dgo.net/smartthings/icons/
|
|
||||||
*/
|
|
||||||
metadata {
|
|
||||||
definition (name: "CoopBoss H3Vx", namespace: "JohnRucker", author: "John.Rucker@Solar-Current.com") {
|
|
||||||
capability "Refresh"
|
|
||||||
capability "Polling"
|
|
||||||
capability "Sensor"
|
|
||||||
capability "Actuator"
|
|
||||||
capability "Configuration"
|
|
||||||
capability "Temperature Measurement"
|
|
||||||
capability "Door Control"
|
|
||||||
capability "Switch"
|
|
||||||
|
|
||||||
command "closeDoor"
|
|
||||||
command "closeDoorHiI"
|
|
||||||
command "openDoor"
|
|
||||||
command "autoCloseOn"
|
|
||||||
command "autoCloseOff"
|
|
||||||
command "autoOpenOn"
|
|
||||||
command "autoOpenOff"
|
|
||||||
command "setCloseLevelTo"
|
|
||||||
command "setOpenLevelTo"
|
|
||||||
command "setSensitivityLevel"
|
|
||||||
command "Aux1On"
|
|
||||||
command "Aux1Off"
|
|
||||||
command "Aux2On"
|
|
||||||
command "Aux2Off"
|
|
||||||
command "updateTemp1"
|
|
||||||
command "updateTemp2"
|
|
||||||
command "updateSun"
|
|
||||||
command "setNewBaseCurrent"
|
|
||||||
command "setNewPhotoCalibration"
|
|
||||||
command "readNewPhotoCalibration"
|
|
||||||
command "readBaseCurrentNE"
|
|
||||||
command "setBaseCurrentNE"
|
|
||||||
command "updateSensitivity"
|
|
||||||
command "updateCloseLightLevel"
|
|
||||||
command "updateOpenLightLevel"
|
|
||||||
|
|
||||||
attribute "doorState","string"
|
|
||||||
attribute "currentLightLevel","number"
|
|
||||||
attribute "closeLightLevel","number"
|
|
||||||
attribute "openLightLevel","number"
|
|
||||||
attribute "autoCloseEnable","string"
|
|
||||||
attribute "autoOpenEnable","string"
|
|
||||||
attribute "TempProb1","number"
|
|
||||||
attribute "TempProb2","number"
|
|
||||||
attribute "dayOrNight","string"
|
|
||||||
attribute "doorSensitivity","number"
|
|
||||||
attribute "doorCurrent","number"
|
|
||||||
attribute "doorVoltage","number"
|
|
||||||
attribute "Aux1","string"
|
|
||||||
attribute "Aux2","string"
|
|
||||||
attribute "coopStatus","string"
|
|
||||||
attribute "baseDoorCurrent","number"
|
|
||||||
attribute "photoCalibration","number"
|
|
||||||
attribute "baseCurrentNE","string"
|
|
||||||
|
|
||||||
fingerprint profileId: "0104", inClusters: "0000,0101,0402", manufacturer: "Solar-Current", model: "Coop Boss"
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// simulator metadata
|
|
||||||
simulator {
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
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"
|
|
||||||
input "tempOffsetCoop", "number", title: "Coop Temperature Offset", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false
|
|
||||||
input "tempOffsetOutside", "number", title: "Outside Temperature Offset", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// UI tile definitions
|
|
||||||
tiles(scale: 2){
|
|
||||||
multiAttributeTile(name:"doorCtrl", type:"generic", width:6, height:4) {tileAttribute("device.doorState", key: "PRIMARY_CONTROL")
|
|
||||||
{
|
|
||||||
attributeState "unknown", label: '${name}', action:"openDoor", icon: "st.Outdoor.outdoor20", nextState:"Sent"
|
|
||||||
attributeState "open", label: '${name}', action:"closeDoor", icon: "st.Outdoor.outdoor20", backgroundColor: "#0000ff" , nextState:"Sent"
|
|
||||||
attributeState "opening", label: '${name}', action:"closeDoor", icon: "st.Outdoor.outdoor20", backgroundColor: "#ffa81e"
|
|
||||||
attributeState "closed", label: '${name}', action:"openDoor", icon: "st.Outdoor.outdoor20", backgroundColor: "#79b821", nextState:"Sent"
|
|
||||||
attributeState "closing", label: '${name}', action:"openDoor", icon: "st.Outdoor.outdoor20", backgroundColor: "#ffa81e"
|
|
||||||
attributeState "jammed", label: '${name}', action:"closeDoorHiI", icon: "st.Outdoor.outdoor20", backgroundColor: "#ff0000", nextState:"Sent"
|
|
||||||
attributeState "forced close", label: 'forced\rclose', action:"openDoor", icon: "st.Outdoor.outdoor20", backgroundColor: "#ff8000", nextState:"Sent"
|
|
||||||
attributeState "fault", label: 'FAULT', action:"openDoor", icon: "st.Outdoor.outdoor20", backgroundColor: "#ff0000", nextState:"Sent"
|
|
||||||
attributeState "Sent", label: 'wait', icon: "st.motion.motion.active", backgroundColor: "#ffa81e"
|
|
||||||
}
|
|
||||||
tileAttribute ("device.coopStatus", key: "SECONDARY_CONTROL") {
|
|
||||||
attributeState "device.coopStatus", label:'${currentValue}'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
multiAttributeTile(name:"dtlsDoorCtrl", type:"generic", width:6, height:4) {tileAttribute("device.doorState", key: "PRIMARY_CONTROL")
|
|
||||||
{
|
|
||||||
attributeState "unknown", label: '${name}', action:"openDoor", icon: "st.secondary.tools", nextState:"Sent"
|
|
||||||
attributeState "open", label: '${name}', action:"closeDoor", icon: "st.doors.garage.garage-open", backgroundColor: "#0000ff", nextState:"Sent"
|
|
||||||
attributeState "opening", label: '${name}', action:"closeDoor", icon: "st.doors.garage.garage-opening", backgroundColor: "#ffa81e"
|
|
||||||
attributeState "closed", label: '${name}', action:"openDoor", icon: "st.doors.garage.garage-closed", backgroundColor: "#79b821", nextState:"Sent"
|
|
||||||
attributeState "closing", label: '${name}', action:"openDoor", icon: "st.doors.garage.garage-closing", backgroundColor: "#ffa81e"
|
|
||||||
attributeState "jammed", label: '${name}', action:"closeDoorHiI", icon: "st.doors.garage.garage-open", backgroundColor: "#ff0000", nextState:"Sent"
|
|
||||||
attributeState "forced close", label: "forced", action:"openDoor", icon: "st.doors.garage.garage-closed", backgroundColor: "#ff8000", nextState:"Sent"
|
|
||||||
attributeState "fault", label: 'FAULT', action:"openDoor", icon: "st.secondary.tools", backgroundColor: "#ff0000", nextState:"Sent"
|
|
||||||
attributeState "Sent", label: 'wait', icon: "st.motion.motion.active", backgroundColor: "#ffa81e"
|
|
||||||
}
|
|
||||||
tileAttribute ("device.doorState", key: "SECONDARY_CONTROL") {
|
|
||||||
attributeState "unknown", label: 'Door is in unknown state. Push to open.'
|
|
||||||
attributeState "open", label: 'Coop door is open. Push to close.'
|
|
||||||
attributeState "opening", label: 'Caution, door is opening!'
|
|
||||||
attributeState "closed", label: 'Coop door is closed. Push to open.'
|
|
||||||
attributeState "closing", label: 'Caution, door is closing!'
|
|
||||||
attributeState "jammed", label: 'Door open! Push for high-force close'
|
|
||||||
attributeState "forced close", label: "Door is closed. Push to open."
|
|
||||||
attributeState "fault", label: 'Door fault check electrical connection.'
|
|
||||||
attributeState "Sent", label: 'Command sent to CoopBoss...'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
standardTile("autoClose", "device.autoCloseEnable", width: 2, height: 2){
|
|
||||||
state "on", label: 'Auto', action:"autoCloseOff", icon: "st.doors.garage.garage-closing", backgroundColor: "#79b821", nextState:"Sent"
|
|
||||||
state "off", label: 'Auto', action:"autoCloseOn", icon: "st.doors.garage.garage-closing", nextState:"Sent"
|
|
||||||
state "Sent", label: 'wait', icon: "st.motion.motion.active", backgroundColor: "#ffa81e"
|
|
||||||
}
|
|
||||||
|
|
||||||
standardTile("autoOpen", "device.autoOpenEnable", width: 2, height: 2){
|
|
||||||
state "on", label: 'Auto', action:"autoOpenOff", icon: "st.doors.garage.garage-opening", backgroundColor: "#79b821", nextState:"Sent"
|
|
||||||
state "off", label: 'Auto', action:"autoOpenOn", icon: "st.doors.garage.garage-opening", nextState:"Sent"
|
|
||||||
state "Sent", label: 'wait', icon: "st.motion.motion.active", backgroundColor: "#ffa81e"
|
|
||||||
}
|
|
||||||
|
|
||||||
valueTile("TempProb1", "device.TempProb1", width: 2, height: 2, decoration: "flat"){
|
|
||||||
state "default", label:'Coop\r${currentValue}°', unit:"F", action:"updateTemp1"}
|
|
||||||
|
|
||||||
valueTile("TempProb2", "device.TempProb2", width: 2, height: 2, decoration: "flat"){
|
|
||||||
state "default", label:'Outside\r${currentValue}°', unit:"F", action:"updateTemp2"}
|
|
||||||
|
|
||||||
valueTile("currentLevel", "device.currentLightLevel", width: 2, height: 2, decoration: "flat") {
|
|
||||||
state "default", label:'Sun\r${currentValue}', action:"updateSun"}
|
|
||||||
|
|
||||||
valueTile("dayOrNight", "device.dayOrNight", decoration: "flat", inactiveLabel: false, width: 2, height: 2) {
|
|
||||||
state "default", label:'${currentValue}.'
|
|
||||||
}
|
|
||||||
|
|
||||||
controlTile("SetClSlider", "device.closeLightLevel", "slider", height: 2, width: 4, inactiveLabel: false, range:"(1..100)") {
|
|
||||||
state "closeLightLevel", action:"setCloseLevelTo", backgroundColor:"#d04e00"
|
|
||||||
}
|
|
||||||
|
|
||||||
valueTile("SetClValue", "device.closeLightLevel", decoration: "flat", inactiveLabel: false, width: 2, height: 2) {
|
|
||||||
state "default", label:'Close\nSunlight\n${currentValue}', action:'updateCloseLightLevel'
|
|
||||||
}
|
|
||||||
|
|
||||||
controlTile("SetOpSlider", "device.openLightLevel", "slider", height: 2, width: 4, inactiveLabel: false, range:"(1..100)") {
|
|
||||||
state "openLightLevel", action:"setOpenLevelTo", backgroundColor:"#d04e00"
|
|
||||||
}
|
|
||||||
|
|
||||||
valueTile("SetOpValue", "device.openLightLevel", decoration: "flat", inactiveLabel: false, width: 2, height: 2) {
|
|
||||||
state "default", label:'Open\nSunlight\n${currentValue}', action:'updateOpenLightLevel'
|
|
||||||
}
|
|
||||||
|
|
||||||
controlTile("SetSensitivitySlider", "device.doorSensitivity", "slider", height: 2, width: 4, inactiveLabel: false, range:"(1..100)") {
|
|
||||||
state "openLightLevel", action:"setSensitivityLevel", backgroundColor:"#d04e00"
|
|
||||||
}
|
|
||||||
|
|
||||||
valueTile("SetSensitivityValue", "device.doorSensitivity", decoration: "flat", inactiveLabel: false, width: 2, height: 2) {
|
|
||||||
state "default", label:'Door\nSensitivity\n${currentValue}', action:'updateSensitivity'
|
|
||||||
}
|
|
||||||
|
|
||||||
standardTile("refresh", "device.refresh", width: 2, height: 2, decoration: "flat", inactiveLabel: false) {
|
|
||||||
state "default", label:'All', action:"refresh.refresh", icon:"st.secondary.refresh-icon"
|
|
||||||
}
|
|
||||||
|
|
||||||
standardTile("aux1", "device.Aux1", width: 2, height: 2, canChangeIcon: true) {
|
|
||||||
state "off", label:'Aux 1', action:"Aux1On", icon:"st.switches.switch.off", backgroundColor:"#ffffff", nextState:"Sent"
|
|
||||||
state "on", label:'Aux 1', action:"Aux1Off", icon:"st.switches.switch.on", backgroundColor:"#79b821", nextState:"Sent"
|
|
||||||
state "Sent", label: 'wait', icon: "st.motion.motion.active", backgroundColor: "#ffa81e"
|
|
||||||
}
|
|
||||||
|
|
||||||
standardTile("aux2", "device.Aux2", width: 2, height: 2, canChangeIcon: true) {
|
|
||||||
state "off", label:'Aux 2', action:"Aux2On", icon:"st.switches.switch.off", backgroundColor:"#ffffff", nextState:"Sent"
|
|
||||||
state "on", label:'Aux 2', action:"Aux2Off", icon:"st.switches.switch.on", backgroundColor:"#79b821", nextState:"Sent"
|
|
||||||
state "Sent", label: 'wait', icon: "st.motion.motion.active", backgroundColor: "#ffa81e"
|
|
||||||
}
|
|
||||||
|
|
||||||
main "doorCtrl"
|
|
||||||
details (["dtlsDoorCtrl", "TempProb1", "TempProb2", "currentLevel", "autoClose", "autoOpen", "dayOrNight",
|
|
||||||
"SetClSlider", "SetClValue", "SetOpSlider", "SetOpValue", "SetSensitivitySlider", "SetSensitivityValue",
|
|
||||||
"aux1", "aux2", "refresh"])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse incoming device messages to generate events def parse(String description) {
|
|
||||||
def parse(String description) {
|
|
||||||
log.debug "description: $description"
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
log.debug map
|
|
||||||
//return map ? createEvent(map) : null
|
|
||||||
sendEvent(map)
|
|
||||||
callUpdateStatusTxt()
|
|
||||||
}
|
|
||||||
|
|
||||||
private Map parseCatchAllMessage(String description) {
|
|
||||||
Map resultMap = [:]
|
|
||||||
def cluster = zigbee.parse(description)
|
|
||||||
log.debug cluster
|
|
||||||
if (cluster.clusterId == 0x0402) {
|
|
||||||
switch(cluster.sourceEndpoint) {
|
|
||||||
|
|
||||||
case 0x39: // Endpoint 0x39 is the temperature of probe 1
|
|
||||||
String temp = cluster.data[-2..-1].reverse().collect { cluster.hex1(it) }.join()
|
|
||||||
resultMap.name = "TempProb1"
|
|
||||||
def celsius = Integer.valueOf(temp,16).shortValue()
|
|
||||||
if (celsius == -32768){ // This number is used to indicate an error in the temperature reading
|
|
||||||
resultMap.value = "---"
|
|
||||||
}else{
|
|
||||||
celsius = celsius / 100 // Temperature value is sent X 100.
|
|
||||||
resultMap.value = celsiusToFahrenheit(celsius)
|
|
||||||
if (tempOffsetOutside) {
|
|
||||||
def offset = tempOffsetOutside as int
|
|
||||||
resultMap.value = resultMap.value + offset
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sendEvent(name: "temperature", value: resultMap.value, displayed: false) // set the temperatureMeasurment capability to temperature
|
|
||||||
break
|
|
||||||
|
|
||||||
case 0x40: // Endpoint 0x40 is the temperature of probe 2
|
|
||||||
String temp = cluster.data[-2..-1].reverse().collect { cluster.hex1(it) }.join()
|
|
||||||
resultMap.name = "TempProb2"
|
|
||||||
def celsius = Integer.valueOf(temp,16).shortValue()
|
|
||||||
//resultMap.descriptionText = "Prob2 celsius value = ${celsius}"
|
|
||||||
if (celsius == -32768){ // This number is used to indicate an error in the temperature reading
|
|
||||||
resultMap.value = "---"
|
|
||||||
}else{
|
|
||||||
celsius = celsius / 100 // Temperature value is sent X 100.
|
|
||||||
resultMap.value = celsiusToFahrenheit(celsius)
|
|
||||||
if (tempOffsetCoop) {
|
|
||||||
def offset = tempOffsetCoop as int
|
|
||||||
resultMap.value = resultMap.value + offset
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cluster.clusterId == 0x0101 && cluster.command == 0x0b) { // This is a default response to a command sent to cluster 0x0101 door control
|
|
||||||
//log.debug "Default Response Data = $cluster.data"
|
|
||||||
switch(cluster.data) {
|
|
||||||
|
|
||||||
case "[10, 0]": // 0x0a turn auto close on command verified
|
|
||||||
resultMap.name = "autoCloseEnable"
|
|
||||||
resultMap.value = "on"
|
|
||||||
break
|
|
||||||
|
|
||||||
case "[11, 0]": // 0x0b turn auto close off command verified
|
|
||||||
resultMap.name = "autoCloseEnable"
|
|
||||||
resultMap.value = "off"
|
|
||||||
break
|
|
||||||
|
|
||||||
case "[12, 0]": // 0x0C turn auto open on command verified
|
|
||||||
resultMap.name = "autoOpenEnable"
|
|
||||||
resultMap.value = "on"
|
|
||||||
break
|
|
||||||
|
|
||||||
case "[13, 0]": // 0x0d turn auto open off command verified
|
|
||||||
resultMap.name = "autoOpenEnable"
|
|
||||||
resultMap.value = "off"
|
|
||||||
break
|
|
||||||
|
|
||||||
|
|
||||||
case "[20, 0]": // 0x14 Aux1 On command verified
|
|
||||||
log.info "verified Aux1 On"
|
|
||||||
sendEvent(name: "switch", value: "on", displayed: false)
|
|
||||||
resultMap.name = "Aux1"
|
|
||||||
resultMap.value = "on"
|
|
||||||
break
|
|
||||||
|
|
||||||
case "[21, 0]": // 0x15 Aux1 Off command verified
|
|
||||||
log.info "verified Aux1 Off"
|
|
||||||
sendEvent(name: "switch", value: "off", displayed: false)
|
|
||||||
resultMap.name = "Aux1"
|
|
||||||
resultMap.value = "off"
|
|
||||||
break
|
|
||||||
|
|
||||||
case "[22, 0]": // 0x16 Aux2 On command verified
|
|
||||||
log.info "verified Aux2 On"
|
|
||||||
resultMap.name = "Aux2"
|
|
||||||
resultMap.value = "on"
|
|
||||||
break
|
|
||||||
|
|
||||||
case "[23, 0]": // 0x17 Aux2 Off command verified
|
|
||||||
log.info "verified Aux2 Off"
|
|
||||||
resultMap.name = "Aux2"
|
|
||||||
resultMap.value = "off"
|
|
||||||
break
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return resultMap
|
|
||||||
}
|
|
||||||
|
|
||||||
private Map parseReportAttributeMessage(String description) {
|
|
||||||
Map resultMap = [:]
|
|
||||||
def descMap = parseDescriptionAsMap(description)
|
|
||||||
//log.debug "read attr descMap --> $descMap"
|
|
||||||
if (descMap.cluster == "0101" && descMap.attrId == "0003") {
|
|
||||||
resultMap.name = "doorState"
|
|
||||||
if (descMap.value == "00"){
|
|
||||||
resultMap.value = "unknown"
|
|
||||||
sendEvent(name: "door", value: "unknown", displayed: false)
|
|
||||||
}else if(descMap.value == "01"){
|
|
||||||
resultMap.value = "closed"
|
|
||||||
sendEvent(name: "door", value: "closed", displayed: false)
|
|
||||||
}else if(descMap.value == "02"){
|
|
||||||
resultMap.value = "open"
|
|
||||||
sendEvent(name: "door", value: "open", displayed: false)
|
|
||||||
}else if(descMap.value == "03"){
|
|
||||||
resultMap.value = "jammed"
|
|
||||||
}else if(descMap.value == "04"){
|
|
||||||
resultMap.value = "forced close"
|
|
||||||
}else if(descMap.value == "05"){
|
|
||||||
resultMap.value = "forced close"
|
|
||||||
}else if(descMap.value == "06"){
|
|
||||||
resultMap.value = "closing"
|
|
||||||
sendEvent(name: "door", value: "closing", displayed: false)
|
|
||||||
}else if(descMap.value == "07"){
|
|
||||||
resultMap.value = "opening"
|
|
||||||
sendEvent(name: "door", value: "opening", displayed: false)
|
|
||||||
}else if(descMap.value == "08"){
|
|
||||||
resultMap.value = "fault"
|
|
||||||
}else {
|
|
||||||
resultMap.value = "unknown"
|
|
||||||
}
|
|
||||||
resultMap.descriptionText = "Door State Changed to ${resultMap.value}"
|
|
||||||
|
|
||||||
} else if (descMap.cluster == "0101" && descMap.attrId == "0400") {
|
|
||||||
resultMap.name = "currentLightLevel"
|
|
||||||
resultMap.value = (Integer.parseInt(descMap.value, 16))
|
|
||||||
resultMap.displayed = false
|
|
||||||
|
|
||||||
} else if (descMap.cluster == "0101" && descMap.attrId == "0401") {
|
|
||||||
resultMap.name = "closeLightLevel"
|
|
||||||
resultMap.value = (Integer.parseInt(descMap.value, 16))
|
|
||||||
|
|
||||||
} else if (descMap.cluster == "0101" && descMap.attrId == "0402") {
|
|
||||||
resultMap.name = "openLightLevel"
|
|
||||||
resultMap.value = (Integer.parseInt(descMap.value, 16))
|
|
||||||
|
|
||||||
} else if (descMap.cluster == "0101" && descMap.attrId == "0403") {
|
|
||||||
resultMap.name = "autoCloseEnable"
|
|
||||||
if (descMap.value == "01"){resultMap.value = "on"}
|
|
||||||
else{resultMap.value = "off"}
|
|
||||||
|
|
||||||
} else if (descMap.cluster == "0101" && descMap.attrId == "0404") {
|
|
||||||
resultMap.name = "autoOpenEnable"
|
|
||||||
if (descMap.value == "01"){resultMap.value = "on"}
|
|
||||||
else{resultMap.value = "off"}
|
|
||||||
|
|
||||||
} else if (descMap.cluster == "0101" && descMap.attrId == "0405") {
|
|
||||||
resultMap.name = "doorCurrent"
|
|
||||||
resultMap.value = (Integer.parseInt(descMap.value, 16))
|
|
||||||
resultMap.value = resultMap.value * 0.001
|
|
||||||
|
|
||||||
} else if (descMap.cluster == "0101" && descMap.attrId == "0408") {
|
|
||||||
resultMap.name = "doorSensitivity"
|
|
||||||
resultMap.value = (100 - Integer.parseInt(descMap.value, 16))
|
|
||||||
|
|
||||||
} else if (descMap.cluster == "0101" && descMap.attrId == "0409") {
|
|
||||||
resultMap.name = "baseDoorCurrent"
|
|
||||||
resultMap.value = (Integer.parseInt(descMap.value, 16))
|
|
||||||
resultMap.value = resultMap.value * 0.001
|
|
||||||
|
|
||||||
} else if (descMap.cluster == "0101" && descMap.attrId == "040a") {
|
|
||||||
resultMap.name = "doorVoltage"
|
|
||||||
resultMap.value = (Integer.parseInt(descMap.value, 16))
|
|
||||||
resultMap.value = resultMap.value * 0.001
|
|
||||||
|
|
||||||
} else if (descMap.cluster == "0101" && descMap.attrId == "040b") {
|
|
||||||
resultMap.name = "Aux1"
|
|
||||||
if(descMap.value == "01"){
|
|
||||||
resultMap.value = "on"
|
|
||||||
sendEvent(name: "switch", value: "on", displayed: false)
|
|
||||||
}else{
|
|
||||||
resultMap.value = "off"
|
|
||||||
sendEvent(name: "switch", value: "off", displayed: false)
|
|
||||||
}
|
|
||||||
|
|
||||||
} else if (descMap.cluster == "0101" && descMap.attrId == "040c") {
|
|
||||||
resultMap.name = "Aux2"
|
|
||||||
if(descMap.value == "01"){
|
|
||||||
resultMap.value = "on"
|
|
||||||
}else{
|
|
||||||
resultMap.value = "off"
|
|
||||||
}
|
|
||||||
} else if (descMap.cluster == "0101" && descMap.attrId == "040d") {
|
|
||||||
resultMap.name = "photoCalibration"
|
|
||||||
resultMap.value = (Integer.parseInt(descMap.value, 16))
|
|
||||||
} else if (descMap.cluster == "0101" && descMap.attrId == "040e") {
|
|
||||||
resultMap.name = "baseCurrentNE"
|
|
||||||
resultMap.value = (Integer.parseInt(descMap.value, 16))
|
|
||||||
}
|
|
||||||
return resultMap
|
|
||||||
}
|
|
||||||
|
|
||||||
private Map parseCustomMessage(String description) {
|
|
||||||
//log.info "ParseCustomMessage called with ${description}"
|
|
||||||
Map resultMap = [:]
|
|
||||||
if (description?.startsWith('temperature: ')) {
|
|
||||||
resultMap.name = "temperature"
|
|
||||||
def rawT = (description - "temperature: ").trim()
|
|
||||||
resultMap.descriptionText = "Temperature celsius value = ${rawT}"
|
|
||||||
def rawTint = Float.parseFloat(rawT)
|
|
||||||
if (rawTint > 65){
|
|
||||||
resultMap.name = null
|
|
||||||
resultMap.value = null
|
|
||||||
resultMap.descriptionText = "Temperature celsius value = ${rawT} is invalid not updating"
|
|
||||||
log.warn "Invalid temperature value detected! rawT = ${rawT}, description = ${description}"
|
|
||||||
}else if (rawT == -32768){ // This number is used to indicate an error in the temperature reading
|
|
||||||
resultMap.value = "ERR"
|
|
||||||
}else{
|
|
||||||
resultMap.value = celsiusToFahrenheit(rawT.toFloat()) as Float
|
|
||||||
sendEvent(name: "TempProb1", value: resultMap.value, displayed: false) // Workaround for lack of access to endpoint information for Temperature report
|
|
||||||
}
|
|
||||||
}
|
|
||||||
resultMap.displayed = false
|
|
||||||
log.info "Temperature reported = ${resultMap.value}"
|
|
||||||
return resultMap
|
|
||||||
}
|
|
||||||
|
|
||||||
def parseDescriptionAsMap(description) {
|
|
||||||
(description - "read attr - ").split(",").inject([:]) { map, param ->
|
|
||||||
def nameAndValue = param.split(":")
|
|
||||||
map += [(nameAndValue[0].trim()):nameAndValue[1].trim()]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Added for Temeperature parse
|
|
||||||
def getFahrenheit(value) {
|
|
||||||
def celsius = Integer.parseInt(value, 16)
|
|
||||||
return celsiusToFahrenheit(celsius) as Integer
|
|
||||||
}
|
|
||||||
|
|
||||||
// Private methods
|
|
||||||
def callUpdateStatusTxt(){
|
|
||||||
def cTemp = device.currentState("TempProb1")?.value
|
|
||||||
def cLight = 0
|
|
||||||
def testNull = device.currentState("currentLightLevel")?.value
|
|
||||||
if (testNull != null){
|
|
||||||
cLight = device.currentState("currentLightLevel")?.value as int
|
|
||||||
}
|
|
||||||
updateStatusTxt(cTemp, cLight)
|
|
||||||
}
|
|
||||||
|
|
||||||
def updateStatusTxt(currentTemp, currentLight){
|
|
||||||
//log.info "called updateStatusTxt with ${currentTemp}, ${currentLight}"
|
|
||||||
def cTmp = currentTemp
|
|
||||||
def cLL = 10
|
|
||||||
def oLL = 10
|
|
||||||
|
|
||||||
def testNull = device.currentState("closeLightLevel")?.value
|
|
||||||
if (testNull != null){
|
|
||||||
cLL = device.currentState("closeLightLevel")?.value as int
|
|
||||||
}
|
|
||||||
|
|
||||||
testNull = device.currentState("openLightLevel")?.value
|
|
||||||
if (testNull != null){
|
|
||||||
oLL = device.currentState("openLightLevel")?.value as int
|
|
||||||
}
|
|
||||||
|
|
||||||
def aOpnEn = device.currentState("autoOpenEnable")?.value
|
|
||||||
def aClsEn = device.currentState("autoCloseEnable")?.value
|
|
||||||
|
|
||||||
if (currentLight < cLL){
|
|
||||||
if (aOpnEn == "on"){
|
|
||||||
sendEvent(name: "dayOrNight", value: "Sun must be > ${oLL} to auto open", displayed: false)
|
|
||||||
sendEvent(name: "coopStatus", value: "Sunlight ${currentLight} open at ${oLL}. Coop ${cTmp}°", displayed: false)
|
|
||||||
}else{
|
|
||||||
sendEvent(name: "dayOrNight", value: "Auto Open is turned off.", displayed: false)
|
|
||||||
sendEvent(name: "coopStatus", value: "Sunlight ${currentLight} auto open off. Coop ${cTmp}°", displayed: false)
|
|
||||||
}
|
|
||||||
}else {
|
|
||||||
if (aClsEn == "on"){
|
|
||||||
sendEvent(name: "dayOrNight", value: "Sun must be < ${cLL} to auto close", displayed: false)
|
|
||||||
sendEvent(name: "coopStatus", value: "Sunlight ${currentLight} close at ${cLL}. Coop ${cTmp}°", displayed: false)
|
|
||||||
}else{
|
|
||||||
sendEvent(name: "dayOrNight", value: "Auto Close is turned off.", displayed: false)
|
|
||||||
sendEvent(name: "coopStatus", value: "Sunlight ${currentLight} auto close off. Coop ${cTmp}°", displayed: false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Commands to device
|
|
||||||
def on() {
|
|
||||||
log.debug "on calling Aux1On"
|
|
||||||
Aux1On()
|
|
||||||
}
|
|
||||||
|
|
||||||
def off() {
|
|
||||||
log.debug "off calling Aux1Off"
|
|
||||||
Aux1Off()
|
|
||||||
}
|
|
||||||
|
|
||||||
def close() {
|
|
||||||
log.debug "close calling closeDoor"
|
|
||||||
closeDoor()
|
|
||||||
}
|
|
||||||
|
|
||||||
def open() {
|
|
||||||
log.debug "open calling openDoor"
|
|
||||||
openDoor()
|
|
||||||
}
|
|
||||||
|
|
||||||
def Aux1On(){
|
|
||||||
log.debug "Sending Aux1 = on command"
|
|
||||||
"st cmd 0x${device.deviceNetworkId} 0x38 0x0101 0x14 {}"
|
|
||||||
}
|
|
||||||
|
|
||||||
def Aux1Off(){
|
|
||||||
log.debug "Sending Aux1 = off command"
|
|
||||||
"st cmd 0x${device.deviceNetworkId} 0x38 0x0101 0x15 {}"
|
|
||||||
}
|
|
||||||
|
|
||||||
def Aux2On(){
|
|
||||||
log.debug "Sending Aux2 = on command"
|
|
||||||
"st cmd 0x${device.deviceNetworkId} 0x38 0x0101 0x16 {}"
|
|
||||||
}
|
|
||||||
|
|
||||||
def Aux2Off(){
|
|
||||||
log.debug "Sending Aux2 = off command"
|
|
||||||
"st cmd 0x${device.deviceNetworkId} 0x38 0x0101 0x17 {}"
|
|
||||||
}
|
|
||||||
|
|
||||||
def openDoor() {
|
|
||||||
log.debug "Sending Open command"
|
|
||||||
"st cmd 0x${device.deviceNetworkId} 0x38 0x0101 0x1 {}"
|
|
||||||
}
|
|
||||||
|
|
||||||
def closeDoor() {
|
|
||||||
log.debug "Sending Close command"
|
|
||||||
"st cmd 0x${device.deviceNetworkId} 0x38 0x0101 0x0 {}"
|
|
||||||
}
|
|
||||||
|
|
||||||
def closeDoorHiI() {
|
|
||||||
log.debug "Sending High Current Close command"
|
|
||||||
"st cmd 0x${device.deviceNetworkId} 0x38 0x0101 0x4 {}"
|
|
||||||
}
|
|
||||||
|
|
||||||
def autoOpenOn() {
|
|
||||||
log.debug "Setting Auto Open On"
|
|
||||||
"st cmd 0x${device.deviceNetworkId} 0x38 0x0101 0x0C {}"
|
|
||||||
}
|
|
||||||
|
|
||||||
def autoOpenOff() {
|
|
||||||
log.debug "Setting Auto Open Off"
|
|
||||||
"st cmd 0x${device.deviceNetworkId} 0x38 0x0101 0x0D {}"
|
|
||||||
}
|
|
||||||
|
|
||||||
def autoCloseOn() {
|
|
||||||
log.debug "Setting Auto Close On"
|
|
||||||
"st cmd 0x${device.deviceNetworkId} 0x38 0x0101 0x0A {}"
|
|
||||||
}
|
|
||||||
|
|
||||||
def autoCloseOff() {
|
|
||||||
log.debug "Setting Auto Close Off"
|
|
||||||
"st cmd 0x${device.deviceNetworkId} 0x38 0x0101 0x0B {}"
|
|
||||||
}
|
|
||||||
|
|
||||||
def setOpenLevelTo(cValue) {
|
|
||||||
def cX = cValue
|
|
||||||
log.debug "Setting Open Light Level to ${cX} Hex = 0x${Integer.toHexString(cX)}"
|
|
||||||
|
|
||||||
def cmd = []
|
|
||||||
cmd << "st wattr 0x${device.deviceNetworkId} 0x38 0x0101 0x402 0x23 {${Integer.toHexString(cX)}}"
|
|
||||||
cmd << "delay 150"
|
|
||||||
cmd << "st rattr 0x${device.deviceNetworkId} 0x38 0x0101 0x402" // Read light value
|
|
||||||
cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
def setCloseLevelTo(cValue) {
|
|
||||||
def cX = cValue
|
|
||||||
log.debug "Setting Close Light Level to ${cX} Hex = 0x${Integer.toHexString(cX)}"
|
|
||||||
|
|
||||||
def cmd = []
|
|
||||||
cmd << "st wattr 0x${device.deviceNetworkId} 0x38 0x0101 0x401 0x23 {${Integer.toHexString(cX)}}"
|
|
||||||
cmd << "delay 150"
|
|
||||||
cmd << "st rattr 0x${device.deviceNetworkId} 0x38 0x0101 0x401" // Read light value
|
|
||||||
cmd
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
def setSensitivityLevel(cValue) {
|
|
||||||
def cX = 100 - cValue
|
|
||||||
log.debug "Setting Door sensitivity level to ${cX} Hex = 0x${Integer.toHexString(cX)}"
|
|
||||||
|
|
||||||
def cmd = []
|
|
||||||
cmd << "st wattr 0x${device.deviceNetworkId} 0x38 0x0101 0x408 0x23 {${Integer.toHexString(cX)}}" // Write attribute. 0x23 is a 32 bit integer value.
|
|
||||||
cmd << "delay 150"
|
|
||||||
cmd << "st rattr 0x${device.deviceNetworkId} 0x38 0x0101 0x408" // Read attribute
|
|
||||||
cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
def setNewBaseCurrent(cValue) {
|
|
||||||
def cX = cValue as int
|
|
||||||
log.info "Setting new BaseCurrent to ${cX} Hex = 0x${Integer.toHexString(cX)}"
|
|
||||||
|
|
||||||
def cmd = []
|
|
||||||
cmd << "st wattr 0x${device.deviceNetworkId} 0x38 0x0101 0x409 0x23 {${Integer.toHexString(cX)}}" // Write attribute. 0x23 is a 32 bit integer value.
|
|
||||||
cmd << "delay 150"
|
|
||||||
cmd << "st rattr 0x${device.deviceNetworkId} 0x38 0x0101 0x409" // Read attribute
|
|
||||||
cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
def setNewPhotoCalibration(cValue) {
|
|
||||||
def cX = cValue as int
|
|
||||||
log.info "Setting new Photoresister calibration to ${cX} Hex = 0x${Integer.toHexString(cX)}"
|
|
||||||
|
|
||||||
def cmd = []
|
|
||||||
cmd << "st wattr 0x${device.deviceNetworkId} 0x38 0x0101 0x40D 0x2B {${Integer.toHexString(cX)}}" // Write attribute. 0x2B is a 32 bit signed integer value.
|
|
||||||
cmd << "st rattr 0x${device.deviceNetworkId} 0x38 0x0101 0x40D" // Read attribute
|
|
||||||
cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
def readNewPhotoCalibration() {
|
|
||||||
log.info "Requesting current Photoresister calibration "
|
|
||||||
|
|
||||||
def cmd = []
|
|
||||||
cmd << "st rattr 0x${device.deviceNetworkId} 0x38 0x0101 0x40D" // Read attribute
|
|
||||||
cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
def readBaseCurrentNE() {
|
|
||||||
log.info "Requesting base current never exceed setting "
|
|
||||||
|
|
||||||
def cmd = []
|
|
||||||
cmd << "st rattr 0x${device.deviceNetworkId} 0x38 0x0101 0x40E" // Read attribute
|
|
||||||
cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
def setBaseCurrentNE(cValue) {
|
|
||||||
def cX = cValue as int
|
|
||||||
log.info "Setting new base Current Never Exceed to ${cX} Hex = 0x${Integer.toHexString(cX)}"
|
|
||||||
|
|
||||||
def cmd = []
|
|
||||||
cmd << "st wattr 0x${device.deviceNetworkId} 0x38 0x0101 0x40E 0x23 {${Integer.toHexString(cX)}}" // Write attribute. 0x23 is a 32 bit unsigned integer value.
|
|
||||||
cmd << "st rattr 0x${device.deviceNetworkId} 0x38 0x0101 0x40E" // Read attribute
|
|
||||||
cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
def poll(){
|
|
||||||
log.debug "Polling Device"
|
|
||||||
def cmd = []
|
|
||||||
cmd << "st rattr 0x${device.deviceNetworkId} 0x38 0x0101 0x0003" // Read Door State
|
|
||||||
cmd << "delay 150"
|
|
||||||
|
|
||||||
cmd << "st rattr 0x${device.deviceNetworkId} 0x38 0x0101 0x0400" // Read Current Light Level
|
|
||||||
cmd << "delay 150"
|
|
||||||
|
|
||||||
cmd << "st rattr 0x${device.deviceNetworkId} 0x39 0x0402 0x0000" // Read probe 1 Temperature
|
|
||||||
cmd << "delay 150"
|
|
||||||
|
|
||||||
cmd << "st rattr 0x${device.deviceNetworkId} 0x40 0x0402 0x0000" // Read probe 2 Temperature
|
|
||||||
|
|
||||||
cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
def updateTemp1() {
|
|
||||||
log.debug "Sending attribute read request for Temperature Probe1"
|
|
||||||
def cmd = []
|
|
||||||
cmd << "st rattr 0x${device.deviceNetworkId} 0x39 0x0402 0x0000" // Read Current Temperature from Coop Probe 1
|
|
||||||
cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
def updateTemp2() {
|
|
||||||
log.debug "Sending attribute read request for Temperature Probe2"
|
|
||||||
def cmd = []
|
|
||||||
cmd << "st rattr 0x${device.deviceNetworkId} 0x40 0x0402 0x0000" // Read Current Temperature from Coop Probe 2
|
|
||||||
cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def updateSun() {
|
|
||||||
log.debug "Sending attribute read request for Sun Light Level"
|
|
||||||
def cmd = []
|
|
||||||
cmd << "st rattr 0x${device.deviceNetworkId} 0x38 0x0101 0x0400" // Read Current Light Level
|
|
||||||
cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
def updateSensitivity() {
|
|
||||||
log.debug "Sending attribute read request for door sensitivity"
|
|
||||||
def cmd = []
|
|
||||||
cmd << "st rattr 0x${device.deviceNetworkId} 0x38 0x0101 0x0408" // Read Door sensitivity
|
|
||||||
cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
def updateCloseLightLevel() {
|
|
||||||
log.debug "Sending attribute read close light level"
|
|
||||||
def cmd = []
|
|
||||||
cmd << "st rattr 0x${device.deviceNetworkId} 0x38 0x0101 0x0401"
|
|
||||||
cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
def updateOpenLightLevel() {
|
|
||||||
log.debug "Sending attribute read open light level"
|
|
||||||
def cmd = []
|
|
||||||
cmd << "st rattr 0x${device.deviceNetworkId} 0x38 0x0101 0x0402"
|
|
||||||
cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
def refresh() {
|
|
||||||
log.debug "sending refresh command"
|
|
||||||
def cmd = []
|
|
||||||
cmd << "st rattr 0x${device.deviceNetworkId} 0x38 0x0101 0x0003" // Read Door State
|
|
||||||
cmd << "delay 150"
|
|
||||||
|
|
||||||
cmd << "st rattr 0x${device.deviceNetworkId} 0x38 0x0101 0x0400" // Read Current Light Level
|
|
||||||
cmd << "delay 150"
|
|
||||||
|
|
||||||
cmd << "st rattr 0x${device.deviceNetworkId} 0x38 0x0101 0x0401" // Read Door Close Light Level
|
|
||||||
cmd << "delay 150"
|
|
||||||
|
|
||||||
cmd << "st rattr 0x${device.deviceNetworkId} 0x38 0x0101 0x0402" // Read Door Open Light Level
|
|
||||||
cmd << "delay 150"
|
|
||||||
|
|
||||||
cmd << "st rattr 0x${device.deviceNetworkId} 0x38 0x0101 0x0403" // Read Auto Door Close Settings
|
|
||||||
cmd << "delay 150"
|
|
||||||
|
|
||||||
cmd << "st rattr 0x${device.deviceNetworkId} 0x38 0x0101 0x0404" // Read Auto Door Open Settings
|
|
||||||
cmd << "delay 150"
|
|
||||||
|
|
||||||
cmd << "st rattr 0x${device.deviceNetworkId} 0x39 0x0402 0x0000" // Read Current Temperature from Coop Probe 1
|
|
||||||
cmd << "delay 150"
|
|
||||||
|
|
||||||
cmd << "st rattr 0x${device.deviceNetworkId} 0x38 0x0101 0x0408" // Object detection sensitivity
|
|
||||||
cmd << "delay 150"
|
|
||||||
|
|
||||||
cmd << "st rattr 0x${device.deviceNetworkId} 0x40 0x0402 0x0000" // Read Current Temperature from Coop Probe 2
|
|
||||||
cmd << "delay 150"
|
|
||||||
|
|
||||||
cmd << "st rattr 0x${device.deviceNetworkId} 0x38 0x0101 0x0405" // Current required to close door
|
|
||||||
cmd << "delay 150"
|
|
||||||
|
|
||||||
cmd << "st rattr 0x${device.deviceNetworkId} 0x38 0x0101 0x040B" // Aux1 Status
|
|
||||||
cmd << "delay 150"
|
|
||||||
|
|
||||||
cmd << "st rattr 0x${device.deviceNetworkId} 0x38 0x0101 0x040C" // Aux2 Status
|
|
||||||
cmd << "delay 150"
|
|
||||||
|
|
||||||
cmd << "st rattr 0x${device.deviceNetworkId} 0x38 0x0101 0x409" // Read Base current
|
|
||||||
|
|
||||||
cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
def configure() {
|
|
||||||
log.debug "Binding SEP 0x38 DEP 0x01 Cluster 0x0101 Lock cluster to hub"
|
|
||||||
log.debug "Binding SEP 0x39 DEP 0x01 Cluster 0x0402 Temperature cluster to hub"
|
|
||||||
log.debug "Binding SEP 0x40 DEP 0x01 Cluster 0x0402 Temperature cluster to hub"
|
|
||||||
|
|
||||||
def cmd = []
|
|
||||||
cmd << "zdo bind 0x${device.deviceNetworkId} 0x38 0x01 0x0101 {${device.zigbeeId}} {}" // Bind to end point 0x38 and the lock cluster
|
|
||||||
cmd << "delay 150"
|
|
||||||
cmd << "zdo bind 0x${device.deviceNetworkId} 0x39 0x01 0x0402 {${device.zigbeeId}} {}" // Bind to end point 0x39 and the temperature cluster
|
|
||||||
cmd << "delay 150"
|
|
||||||
cmd << "zdo bind 0x${device.deviceNetworkId} 0x40 0x01 0x0402 {${device.zigbeeId}} {}" // Bind to end point 0x40 and the temperature cluster
|
|
||||||
cmd << "delay 1500"
|
|
||||||
|
|
||||||
log.info "Sending ZigBee Configuration Commands to Coop Control"
|
|
||||||
return cmd + refresh()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@@ -274,7 +274,6 @@ private Map makeTemperatureResult(value) {
|
|||||||
name: 'temperature',
|
name: 'temperature',
|
||||||
value: "" + value,
|
value: "" + value,
|
||||||
descriptionText: "${linkText} is ${value}°${temperatureScale}",
|
descriptionText: "${linkText} is ${value}°${temperatureScale}",
|
||||||
unit: temperatureScale
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,164 +0,0 @@
|
|||||||
/**
|
|
||||||
* PlantLink
|
|
||||||
*
|
|
||||||
* This device type takes sensor data and converts it into a json packet to send to myplantlink.com
|
|
||||||
* where its values will be computed for soil and plant type to show user readable values of how your
|
|
||||||
* specific plant is doing.
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* Copyright 2015 Oso Technologies
|
|
||||||
*
|
|
||||||
* 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.JsonBuilder
|
|
||||||
|
|
||||||
metadata {
|
|
||||||
definition (name: "PlantLink", namespace: "OsoTech", author: "Oso Technologies") {
|
|
||||||
capability "Sensor"
|
|
||||||
|
|
||||||
command "setStatusIcon"
|
|
||||||
command "setPlantFuelLevel"
|
|
||||||
command "setBatteryLevel"
|
|
||||||
command "setInstallSmartApp"
|
|
||||||
|
|
||||||
attribute "plantStatus","string"
|
|
||||||
attribute "plantFuelLevel","number"
|
|
||||||
attribute "linkBatteryLevel","string"
|
|
||||||
attribute "installSmartApp","string"
|
|
||||||
|
|
||||||
fingerprint profileId: "0104", inClusters: "0000,0001,0B04"
|
|
||||||
}
|
|
||||||
|
|
||||||
simulator {
|
|
||||||
status "battery": "read attr - raw: C0720100010A000021340A, dni: C072, endpoint: 01, cluster: 0001, size: 0A, attrId: 0000, encoding: 21, value: 0a34"
|
|
||||||
status "moisture": "read attr - raw: C072010B040A0001290000, dni: C072, endpoint: 01, cluster: 0B04, size: 0A, attrId: 0100, encoding: 29, value: 0000"
|
|
||||||
}
|
|
||||||
|
|
||||||
tiles {
|
|
||||||
standardTile("Title", "device.label") {
|
|
||||||
state("label", label:'PlantLink ${device.label}')
|
|
||||||
}
|
|
||||||
|
|
||||||
valueTile("plantMoistureTile", "device.plantFuelLevel", width: 1, height: 1) {
|
|
||||||
state("plantMoisture", label: '${currentValue}% Moisture')
|
|
||||||
}
|
|
||||||
|
|
||||||
valueTile("plantStatusTextTile", "device.plantStatus", decoration: "flat", width: 2, height: 2) {
|
|
||||||
state("plantStatusTextTile", label:'${currentValue}')
|
|
||||||
}
|
|
||||||
|
|
||||||
valueTile("battery", "device.linkBatteryLevel" ) {
|
|
||||||
state("battery", label:'${currentValue}% battery')
|
|
||||||
}
|
|
||||||
|
|
||||||
valueTile("installSmartApp","device.installSmartApp", decoration: "flat", width: 3, height: 1) {
|
|
||||||
state "needSmartApp", label:'Please install SmartApp "Required PlantLink Connector"', defaultState:true
|
|
||||||
state "connectedToSmartApp", label:'Connected to myplantlink.com'
|
|
||||||
}
|
|
||||||
|
|
||||||
main "plantStatusTextTile"
|
|
||||||
details(['plantStatusTextTile', "plantMoistureTile", "battery", "installSmartApp"])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def setStatusIcon(value){
|
|
||||||
def status = ''
|
|
||||||
switch (value) {
|
|
||||||
case '0':
|
|
||||||
status = 'Needs Water'
|
|
||||||
break
|
|
||||||
case '1':
|
|
||||||
status = 'Dry'
|
|
||||||
break
|
|
||||||
case '2':
|
|
||||||
case '3':
|
|
||||||
status = 'Good'
|
|
||||||
break
|
|
||||||
case '4':
|
|
||||||
status = 'Too Wet'
|
|
||||||
break
|
|
||||||
case 'No Soil':
|
|
||||||
status = 'Too Dry'
|
|
||||||
setPlantFuelLevel(0)
|
|
||||||
break
|
|
||||||
case 'Recently Watered':
|
|
||||||
status = 'Watered'
|
|
||||||
setPlantFuelLevel(100)
|
|
||||||
break
|
|
||||||
case 'Low Battery':
|
|
||||||
status = 'Low Battery'
|
|
||||||
break
|
|
||||||
case 'Waiting on First Measurement':
|
|
||||||
status = 'Calibrating'
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
status = "?"
|
|
||||||
break
|
|
||||||
}
|
|
||||||
sendEvent("name":"plantStatus", "value":status, "description":statusText, displayed: true, isStateChange: true)
|
|
||||||
}
|
|
||||||
|
|
||||||
def setPlantFuelLevel(value){
|
|
||||||
sendEvent("name":"plantFuelLevel", "value":value, "description":statusText, displayed: true, isStateChange: true)
|
|
||||||
}
|
|
||||||
|
|
||||||
def setBatteryLevel(value){
|
|
||||||
sendEvent("name":"linkBatteryLevel", "value":value, "description":statusText, displayed: true, isStateChange: true)
|
|
||||||
}
|
|
||||||
|
|
||||||
def setInstallSmartApp(value){
|
|
||||||
sendEvent("name":"installSmartApp", "value":value)
|
|
||||||
}
|
|
||||||
|
|
||||||
def parse(String description) {
|
|
||||||
log.debug description
|
|
||||||
def description_map = parseDescriptionAsMap(description)
|
|
||||||
def event_name = ""
|
|
||||||
def measurement_map = [
|
|
||||||
type: "link",
|
|
||||||
signal: "0x00",
|
|
||||||
zigbeedeviceid: device.zigbeeId,
|
|
||||||
created: new Date().time /1000 as int
|
|
||||||
]
|
|
||||||
if (description_map.cluster == "0001"){
|
|
||||||
/* battery voltage in mV (device needs minimium 2.1v to run) */
|
|
||||||
log.debug "PlantLink - id ${device.zigbeeId} battery ${description_map.value}"
|
|
||||||
event_name = "battery_status"
|
|
||||||
measurement_map["battery"] = "0x${description_map.value}"
|
|
||||||
|
|
||||||
} else if (description_map.cluster == "0B04"){
|
|
||||||
/* raw moisture reading (needs to be sent to plantlink for soil/plant type conversion) */
|
|
||||||
log.debug "PlantLink - id ${device.zigbeeId} raw moisture ${description_map.value}"
|
|
||||||
measurement_map["moisture"] = "0x${description_map.value}"
|
|
||||||
event_name = "moisture_status"
|
|
||||||
|
|
||||||
} else{
|
|
||||||
log.debug "PlantLink - id ${device.zigbeeId} Unknown '${description}'"
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
def json_builder = new JsonBuilder(measurement_map)
|
|
||||||
def result = createEvent(name: event_name, value: json_builder.toString())
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def parseDescriptionAsMap(description) {
|
|
||||||
(description - "read attr - ").split(",").inject([:]) { map, param ->
|
|
||||||
def nameAndValue = param.split(":")
|
|
||||||
if(nameAndValue.length == 2){
|
|
||||||
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,398 +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,
|
|
||||||
unit: temperatureScale
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
@@ -28,7 +28,6 @@ metadata {
|
|||||||
attribute "powerSupply", "enum", ["USB Cable", "Battery"]
|
attribute "powerSupply", "enum", ["USB Cable", "Battery"]
|
||||||
|
|
||||||
fingerprint deviceId: "0x2101", inClusters: "0x5E,0x86,0x72,0x59,0x85,0x73,0x71,0x84,0x80,0x30,0x31,0x70,0x7A", outClusters: "0x5A"
|
fingerprint deviceId: "0x2101", inClusters: "0x5E,0x86,0x72,0x59,0x85,0x73,0x71,0x84,0x80,0x30,0x31,0x70,0x7A", outClusters: "0x5A"
|
||||||
fingerprint deviceId: "0x2101", inClusters: "0x5E,0x86,0x72,0x59,0x85,0x73,0x71,0x84,0x80,0x30,0x31,0x70,0x7A,0x5A"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
simulator {
|
simulator {
|
||||||
@@ -353,7 +352,7 @@ def configure() {
|
|||||||
motionSensitivity == "minimum" ? 0 : 64)
|
motionSensitivity == "minimum" ? 0 : 64)
|
||||||
|
|
||||||
//5. report every x minutes (threshold reports don't work on battery power, default 8 mins)
|
//5. report every x minutes (threshold reports don't work on battery power, default 8 mins)
|
||||||
request << zwave.configurationV1.configurationSet(parameterNumber: 111, size: 4, scaledConfigurationValue: timeOptionValueMap[reportInterval] ?: (8*60)) //association group 1
|
request << zwave.configurationV1.configurationSet(parameterNumber: 111, size: 4, scaledConfigurationValue: timeOptionValueMap[reportInterval] ?: 8*60) //association group 1
|
||||||
|
|
||||||
request << zwave.configurationV1.configurationSet(parameterNumber: 112, size: 4, scaledConfigurationValue: 6*60*60) //association group 2
|
request << zwave.configurationV1.configurationSet(parameterNumber: 112, size: 4, scaledConfigurationValue: 6*60*60) //association group 2
|
||||||
|
|
||||||
|
|||||||
@@ -21,8 +21,6 @@ metadata {
|
|||||||
capability "Sensor"
|
capability "Sensor"
|
||||||
capability "Battery"
|
capability "Battery"
|
||||||
|
|
||||||
command "configureAfterSecure"
|
|
||||||
|
|
||||||
fingerprint deviceId: "0x0701", inClusters: "0x5E,0x86,0x72,0x59,0x85,0x73,0x71,0x84,0x80,0x30,0x31,0x70,0x98,0x7A", outClusters:"0x5A"
|
fingerprint deviceId: "0x0701", inClusters: "0x5E,0x86,0x72,0x59,0x85,0x73,0x71,0x84,0x80,0x30,0x31,0x70,0x98,0x7A", outClusters:"0x5A"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,170 +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.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
metadata {
|
|
||||||
definition (name: "Arrival Sensor HA", namespace: "smartthings", author: "SmartThings") {
|
|
||||||
capability "Tone"
|
|
||||||
capability "Actuator"
|
|
||||||
capability "Presence Sensor"
|
|
||||||
capability "Sensor"
|
|
||||||
capability "Battery"
|
|
||||||
capability "Configuration"
|
|
||||||
|
|
||||||
fingerprint inClusters: "0000,0001,0003,000F,0020", outClusters: "0003,0019",
|
|
||||||
manufacturer: "SmartThings", model: "tagv4", deviceJoinName: "Arrival Sensor"
|
|
||||||
}
|
|
||||||
|
|
||||||
preferences {
|
|
||||||
section {
|
|
||||||
image(name: 'educationalcontent', multiple: true, images: [
|
|
||||||
"http://cdn.device-gse.smartthings.com/Arrival/Arrival1.png",
|
|
||||||
"http://cdn.device-gse.smartthings.com/Arrival/Arrival2.png"
|
|
||||||
])
|
|
||||||
}
|
|
||||||
section {
|
|
||||||
input "checkInterval", "enum", title: "Presence timeout (minutes)", description: "Tap to set",
|
|
||||||
defaultValue:"2", options: ["2", "3", "5"], displayDuringSetup: false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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"
|
|
||||||
}
|
|
||||||
standardTile("beep", "device.beep", decoration: "flat") {
|
|
||||||
state "beep", label:'', action:"tone.beep", icon:"st.secondary.beep", backgroundColor:"#ffffff"
|
|
||||||
}
|
|
||||||
valueTile("battery", "device.battery", decoration: "flat", inactiveLabel: false) {
|
|
||||||
state "battery", label:'${currentValue}% battery', unit:""
|
|
||||||
}
|
|
||||||
|
|
||||||
main "presence"
|
|
||||||
details(["presence", "beep", "battery"])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def updated() {
|
|
||||||
startTimer()
|
|
||||||
}
|
|
||||||
|
|
||||||
def configure() {
|
|
||||||
def cmds = zigbee.configureReporting(0x0001, 0x0020, 0x20, 20, 20, 0x01)
|
|
||||||
log.debug "configure -- cmds: ${cmds}"
|
|
||||||
return cmds
|
|
||||||
}
|
|
||||||
|
|
||||||
def beep() {
|
|
||||||
log.debug "Sending Identify command to beep the sensor for 5 seconds"
|
|
||||||
return zigbee.command(0x0003, 0x00, "0500")
|
|
||||||
}
|
|
||||||
|
|
||||||
def parse(String description) {
|
|
||||||
state.lastCheckin = now()
|
|
||||||
handlePresenceEvent(true)
|
|
||||||
|
|
||||||
if (description?.startsWith('read attr -')) {
|
|
||||||
handleReportAttributeMessage(description)
|
|
||||||
}
|
|
||||||
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
|
|
||||||
private handleReportAttributeMessage(String description) {
|
|
||||||
def descMap = zigbee.parseDescriptionAsMap(description)
|
|
||||||
if (descMap.clusterInt == 0x0001 && descMap.attrInt == 0x0020) {
|
|
||||||
handleBatteryEvent(Integer.parseInt(descMap.value, 16))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create battery event from reported battery voltage.
|
|
||||||
*
|
|
||||||
* @param volts Battery voltage in .1V increments
|
|
||||||
*/
|
|
||||||
private handleBatteryEvent(volts) {
|
|
||||||
def descriptionText
|
|
||||||
if (volts == 0 || volts == 255) {
|
|
||||||
log.debug "Ignoring invalid value for voltage (${volts/10}V)"
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
def batteryMap = [28:100, 27:100, 26:100, 25:90, 24:90, 23:70,
|
|
||||||
22:70, 21:50, 20:50, 19:30, 18:30, 17:15, 16:1, 15:0]
|
|
||||||
def minVolts = 15
|
|
||||||
def maxVolts = 28
|
|
||||||
|
|
||||||
if (volts < minVolts)
|
|
||||||
volts = minVolts
|
|
||||||
else if (volts > maxVolts)
|
|
||||||
volts = maxVolts
|
|
||||||
def value = batteryMap[volts]
|
|
||||||
if (value != null) {
|
|
||||||
def linkText = getLinkText(device)
|
|
||||||
descriptionText = '{{ linkText }} battery was {{ value }}'
|
|
||||||
def eventMap = [
|
|
||||||
name: 'battery',
|
|
||||||
value: value,
|
|
||||||
descriptionText: descriptionText,
|
|
||||||
translatable: true
|
|
||||||
]
|
|
||||||
log.debug "Creating battery event for voltage=${volts/10}V: ${linkText} ${eventMap.name} is ${eventMap.value}%"
|
|
||||||
sendEvent(eventMap)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private handlePresenceEvent(present) {
|
|
||||||
def wasPresent = device.currentState("presence")?.value == "present"
|
|
||||||
if (!wasPresent && present) {
|
|
||||||
log.debug "Sensor is present"
|
|
||||||
startTimer()
|
|
||||||
} else if (!present) {
|
|
||||||
log.debug "Sensor is not present"
|
|
||||||
stopTimer()
|
|
||||||
}
|
|
||||||
def linkText = getLinkText(device)
|
|
||||||
def descriptionText
|
|
||||||
if ( present )
|
|
||||||
descriptionText = "{{ linkText }} has arrived"
|
|
||||||
else
|
|
||||||
descriptionText = "{{ linkText }} has left"
|
|
||||||
def eventMap = [
|
|
||||||
name: "presence",
|
|
||||||
value: present ? "present" : "not present",
|
|
||||||
linkText: linkText,
|
|
||||||
descriptionText: descriptionText,
|
|
||||||
translatable: true
|
|
||||||
]
|
|
||||||
log.debug "Creating presence event: ${device.displayName} ${eventMap.name} is ${eventMap.value}"
|
|
||||||
sendEvent(eventMap)
|
|
||||||
}
|
|
||||||
|
|
||||||
private startTimer() {
|
|
||||||
log.debug "Scheduling periodic timer"
|
|
||||||
schedule("0 * * * * ?", checkPresenceCallback)
|
|
||||||
}
|
|
||||||
|
|
||||||
private stopTimer() {
|
|
||||||
log.debug "Stopping periodic timer"
|
|
||||||
unschedule()
|
|
||||||
}
|
|
||||||
|
|
||||||
def checkPresenceCallback() {
|
|
||||||
def timeSinceLastCheckin = (now() - state.lastCheckin) / 1000
|
|
||||||
def theCheckInterval = (checkInterval ? checkInterval as int : 2) * 60
|
|
||||||
log.debug "Sensor checked in ${timeSinceLastCheckin} seconds ago"
|
|
||||||
if (timeSinceLastCheckin >= theCheckInterval) {
|
|
||||||
handlePresenceEvent(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,26 +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.
|
|
||||||
# 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=도착알림 센서
|
|
||||||
'''${currentValue}% battery'''.ko=${currentValue}% 배터리
|
|
||||||
# Events / Notifications
|
|
||||||
'''{{ linkText }} battery was {{ value }}'''.ko={{ linkText }}의 남은 배터리 {{ value }}
|
|
||||||
'''{{ linkText }} has arrived'''.ko={{ linkText }} 귀가
|
|
||||||
'''{{ linkText }} has left'''.ko={{ linkText }} 외출
|
|
||||||
#==============================================================================
|
|
||||||
@@ -87,27 +87,16 @@ def beep() {
|
|||||||
up to this long from the time you send the message to the time you hear a sound.
|
up to this long from the time you send the message to the time you hear a sound.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// Used source endpoint of 0x02 because we are using smartthings manufacturer specific cluster.
|
|
||||||
[
|
[
|
||||||
"raw 0xFC05 {15 0A 11 00 00 15 01}",
|
"raw 0xFC05 {15 0A 11 00 00 15 01}",
|
||||||
"delay 200",
|
|
||||||
"send 0x$zigbee.deviceNetworkId 0x02 0x$zigbee.endpointId",
|
|
||||||
"delay 7000",
|
"delay 7000",
|
||||||
"raw 0xFC05 {15 0A 11 00 00 15 01}",
|
"raw 0xFC05 {15 0A 11 00 00 15 01}",
|
||||||
"delay 200",
|
|
||||||
"send 0x$zigbee.deviceNetworkId 0x02 0x$zigbee.endpointId",
|
|
||||||
"delay 7000",
|
"delay 7000",
|
||||||
"raw 0xFC05 {15 0A 11 00 00 15 01}",
|
"raw 0xFC05 {15 0A 11 00 00 15 01}",
|
||||||
"delay 200",
|
|
||||||
"send 0x$zigbee.deviceNetworkId 0x02 0x$zigbee.endpointId",
|
|
||||||
"delay 7000",
|
"delay 7000",
|
||||||
"raw 0xFC05 {15 0A 11 00 00 15 01}",
|
"raw 0xFC05 {15 0A 11 00 00 15 01}",
|
||||||
"delay 200",
|
|
||||||
"send 0x$zigbee.deviceNetworkId 0x02 0x$zigbee.endpointId",
|
|
||||||
"delay 7000",
|
"delay 7000",
|
||||||
"raw 0xFC05 {15 0A 11 00 00 15 01}",
|
"raw 0xFC05 {15 0A 11 00 00 15 01}"
|
||||||
"delay 200",
|
|
||||||
"send 0x$zigbee.deviceNetworkId 0x02 0x$zigbee.endpointId",
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -47,9 +47,6 @@ metadata {
|
|||||||
|
|
||||||
command "everywhereJoin"
|
command "everywhereJoin"
|
||||||
command "everywhereLeave"
|
command "everywhereLeave"
|
||||||
|
|
||||||
command "forceOff"
|
|
||||||
command "forceOn"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -67,10 +64,8 @@ metadata {
|
|||||||
}
|
}
|
||||||
|
|
||||||
standardTile("switch", "device.switch", width: 1, height: 1, canChangeIcon: true) {
|
standardTile("switch", "device.switch", width: 1, height: 1, canChangeIcon: true) {
|
||||||
state "on", label: '${name}', action: "forceOff", icon: "st.Electronics.electronics16", backgroundColor: "#79b821", nextState:"turningOff"
|
state "off", label: '${name}', action: "switch.on", icon: "st.Electronics.electronics16", backgroundColor: "#ffffff"
|
||||||
state "turningOff", label:'TURNING OFF', icon:"st.Electronics.electronics16", backgroundColor:"#ffffff"
|
state "on", label: '${name}', action: "switch.off", icon: "st.Electronics.electronics16", backgroundColor: "#79b821"
|
||||||
state "off", label: '${name}', action: "forceOn", icon: "st.Electronics.electronics16", backgroundColor: "#ffffff", nextState:"turningOn"
|
|
||||||
state "turningOn", label:'TURNING ON', icon:"st.Electronics.electronics16", backgroundColor:"#79b821"
|
|
||||||
}
|
}
|
||||||
valueTile("1", "device.station1", decoration: "flat", canChangeIcon: false) {
|
valueTile("1", "device.station1", decoration: "flat", canChangeIcon: false) {
|
||||||
state "station1", label:'${currentValue}', action:"preset1"
|
state "station1", label:'${currentValue}', action:"preset1"
|
||||||
@@ -143,22 +138,8 @@ metadata {
|
|||||||
* one place.
|
* one place.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
def off() {
|
def off() { onAction("off") }
|
||||||
if (device.currentState("switch")?.value == "on") {
|
def on() { onAction("on") }
|
||||||
onAction("off")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
def forceOff() {
|
|
||||||
onAction("off")
|
|
||||||
}
|
|
||||||
def on() {
|
|
||||||
if (device.currentState("switch")?.value == "off") {
|
|
||||||
onAction("on")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
def forceOn() {
|
|
||||||
onAction("on")
|
|
||||||
}
|
|
||||||
def volup() { onAction("volup") }
|
def volup() { onAction("volup") }
|
||||||
def voldown() { onAction("voldown") }
|
def voldown() { onAction("voldown") }
|
||||||
def preset1() { onAction("1") }
|
def preset1() { onAction("1") }
|
||||||
@@ -257,11 +238,11 @@ def onAction(String user, data=null) {
|
|||||||
def actions = null
|
def actions = null
|
||||||
switch (user) {
|
switch (user) {
|
||||||
case "on":
|
case "on":
|
||||||
boseSetPowerState(true)
|
actions = boseSetPowerState(true)
|
||||||
break
|
break
|
||||||
case "off":
|
case "off":
|
||||||
boseSetNowPlaying(null, "STANDBY")
|
boseSetNowPlaying(null, "STANDBY")
|
||||||
boseSetPowerState(false)
|
actions = boseSetPowerState(false)
|
||||||
break
|
break
|
||||||
case "volume":
|
case "volume":
|
||||||
actions = boseSetVolume(data)
|
actions = boseSetVolume(data)
|
||||||
@@ -766,16 +747,8 @@ def cb_boseSetInput(xml, input) {
|
|||||||
*/
|
*/
|
||||||
def boseSetPowerState(boolean enable) {
|
def boseSetPowerState(boolean enable) {
|
||||||
log.info "boseSetPowerState(${enable})"
|
log.info "boseSetPowerState(${enable})"
|
||||||
// Fix to get faster update of power status back from speaker after sending on/off
|
queueCallback('nowPlaying', "cb_boseSetPowerState", enable ? "POWERON" : "POWEROFF")
|
||||||
// Instead of queuing the command to be sent after the refresh send it directly via sendHubCommand
|
return boseRefreshNowPlaying()
|
||||||
// Note: This is a temporary hack that should be replaced by a re-design of the
|
|
||||||
// DTH to use sendHubCommand for all commands
|
|
||||||
sendHubCommand(bosePOST("/key", "<key state=\"press\" sender=\"Gabbo\">POWER</key>"))
|
|
||||||
sendHubCommand(bosePOST("/key", "<key state=\"release\" sender=\"Gabbo\">POWER</key>"))
|
|
||||||
sendHubCommand(boseGET("/now_playing"))
|
|
||||||
if (enable) {
|
|
||||||
queueCallback('nowPlaying', "cb_boseConfirmPowerOn", 5)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -814,11 +787,10 @@ def cb_boseSetPowerState(xml, state) {
|
|||||||
*/
|
*/
|
||||||
def cb_boseConfirmPowerOn(xml, tries) {
|
def cb_boseConfirmPowerOn(xml, tries) {
|
||||||
def result = []
|
def result = []
|
||||||
def attempt = tries as Integer
|
log.warn "boseConfirmPowerOn() attempt #" + tries
|
||||||
log.warn "boseConfirmPowerOn() attempt #$attempt"
|
if (xml.attributes()['source'] == "STANDBY" && tries > 0) {
|
||||||
if (xml.attributes()['source'] == "STANDBY" && attempt > 0) {
|
|
||||||
result << boseRefreshNowPlaying()
|
result << boseRefreshNowPlaying()
|
||||||
queueCallback('nowPlaying', "cb_boseConfirmPowerOn", attempt-1)
|
queueCallback('nowPlaying', "cb_boseConfirmPowerOn", tries-1)
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,6 @@
|
|||||||
* Author: SmartThings
|
* Author: SmartThings
|
||||||
* Date: 2013-12-04
|
* Date: 2013-12-04
|
||||||
*/
|
*/
|
||||||
//DEPRECATED - Using the generic DTH for this device. Users need to be moved before deleting this DTH
|
|
||||||
metadata {
|
metadata {
|
||||||
definition (name: "CentraLite Dimmer", namespace: "smartthings", author: "SmartThings") {
|
definition (name: "CentraLite Dimmer", namespace: "smartthings", author: "SmartThings") {
|
||||||
capability "Switch Level"
|
capability "Switch Level"
|
||||||
@@ -26,6 +25,7 @@ metadata {
|
|||||||
capability "Refresh"
|
capability "Refresh"
|
||||||
capability "Sensor"
|
capability "Sensor"
|
||||||
|
|
||||||
|
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0B04,0B05", outClusters: "0019"
|
||||||
}
|
}
|
||||||
|
|
||||||
// simulator metadata
|
// simulator metadata
|
||||||
@@ -105,21 +105,11 @@ def parseDescriptionAsMap(description) {
|
|||||||
|
|
||||||
// Commands to device
|
// Commands to device
|
||||||
def on() {
|
def on() {
|
||||||
[
|
'zcl on-off on'
|
||||||
'zcl on-off on',
|
|
||||||
'delay 200',
|
|
||||||
"send 0x${zigbee.deviceNetworkId} 0x01 0x${zigbee.endpointId}",
|
|
||||||
'delay 500'
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def off() {
|
def off() {
|
||||||
[
|
'zcl on-off off'
|
||||||
'zcl on-off off',
|
|
||||||
'delay 200',
|
|
||||||
"send 0x${zigbee.deviceNetworkId} 0x01 0x${zigbee.endpointId}",
|
|
||||||
'delay 500'
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def setLevel(value) {
|
def setLevel(value) {
|
||||||
|
|||||||
@@ -89,17 +89,14 @@ def parse(String description) {
|
|||||||
log.debug "TEMP"
|
log.debug "TEMP"
|
||||||
map.name = "temperature"
|
map.name = "temperature"
|
||||||
map.value = getTemperature(descMap.value)
|
map.value = getTemperature(descMap.value)
|
||||||
map.unit = temperatureScale
|
|
||||||
} else if (descMap.cluster == "0201" && descMap.attrId == "0011") {
|
} else if (descMap.cluster == "0201" && descMap.attrId == "0011") {
|
||||||
log.debug "COOLING SETPOINT"
|
log.debug "COOLING SETPOINT"
|
||||||
map.name = "coolingSetpoint"
|
map.name = "coolingSetpoint"
|
||||||
map.value = getTemperature(descMap.value)
|
map.value = getTemperature(descMap.value)
|
||||||
map.unit = temperatureScale
|
|
||||||
} else if (descMap.cluster == "0201" && descMap.attrId == "0012") {
|
} else if (descMap.cluster == "0201" && descMap.attrId == "0012") {
|
||||||
log.debug "HEATING SETPOINT"
|
log.debug "HEATING SETPOINT"
|
||||||
map.name = "heatingSetpoint"
|
map.name = "heatingSetpoint"
|
||||||
map.value = getTemperature(descMap.value)
|
map.value = getTemperature(descMap.value)
|
||||||
map.unit = temperatureScale
|
|
||||||
} else if (descMap.cluster == "0201" && descMap.attrId == "001c") {
|
} else if (descMap.cluster == "0201" && descMap.attrId == "001c") {
|
||||||
log.debug "MODE"
|
log.debug "MODE"
|
||||||
map.name = "thermostatMode"
|
map.name = "thermostatMode"
|
||||||
@@ -172,7 +169,7 @@ def setHeatingSetpoint(degrees) {
|
|||||||
|
|
||||||
def degreesInteger = Math.round(degrees)
|
def degreesInteger = Math.round(degrees)
|
||||||
log.debug "setHeatingSetpoint({$degreesInteger} ${temperatureScale})"
|
log.debug "setHeatingSetpoint({$degreesInteger} ${temperatureScale})"
|
||||||
sendEvent("name": "heatingSetpoint", "value": degreesInteger, "unit": temperatureScale)
|
sendEvent("name": "heatingSetpoint", "value": degreesInteger)
|
||||||
|
|
||||||
def celsius = (getTemperatureScale() == "C") ? degreesInteger : (fahrenheitToCelsius(degreesInteger) as Double).round(2)
|
def celsius = (getTemperatureScale() == "C") ? degreesInteger : (fahrenheitToCelsius(degreesInteger) as Double).round(2)
|
||||||
"st wattr 0x${device.deviceNetworkId} 1 0x201 0x12 0x29 {" + hex(celsius * 100) + "}"
|
"st wattr 0x${device.deviceNetworkId} 1 0x201 0x12 0x29 {" + hex(celsius * 100) + "}"
|
||||||
@@ -183,7 +180,7 @@ def setCoolingSetpoint(degrees) {
|
|||||||
if (degrees != null) {
|
if (degrees != null) {
|
||||||
def degreesInteger = Math.round(degrees)
|
def degreesInteger = Math.round(degrees)
|
||||||
log.debug "setCoolingSetpoint({$degreesInteger} ${temperatureScale})"
|
log.debug "setCoolingSetpoint({$degreesInteger} ${temperatureScale})"
|
||||||
sendEvent("name": "coolingSetpoint", "value": degreesInteger, "unit": temperatureScale)
|
sendEvent("name": "coolingSetpoint", "value": degreesInteger)
|
||||||
def celsius = (getTemperatureScale() == "C") ? degreesInteger : (fahrenheitToCelsius(degreesInteger) as Double).round(2)
|
def celsius = (getTemperatureScale() == "C") ? degreesInteger : (fahrenheitToCelsius(degreesInteger) as Double).round(2)
|
||||||
"st wattr 0x${device.deviceNetworkId} 1 0x201 0x11 0x29 {" + hex(celsius * 100) + "}"
|
"st wattr 0x${device.deviceNetworkId} 1 0x201 0x11 0x29 {" + hex(celsius * 100) + "}"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,2 +0,0 @@
|
|||||||
.st-ignore
|
|
||||||
README.md
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
# Connected Cree LED Bulb
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Works with:
|
|
||||||
|
|
||||||
* [Connected Cree LED Bulb](https://support.smartthings.com/hc/en-us/articles/204258280-Cree-Connected-LED-Bulb)
|
|
||||||
|
|
||||||
## Table of contents
|
|
||||||
|
|
||||||
* [Capabilities](#capabilities)
|
|
||||||
* [Health](#device-health)
|
|
||||||
|
|
||||||
## Capabilities
|
|
||||||
|
|
||||||
* **Actuator** - represents that a Device has commands
|
|
||||||
* **Configuration** - _configure()_ command called when device is installed or device preferences updated
|
|
||||||
* **Polling** - represents that poll() can be implemented for the device
|
|
||||||
* **Refresh** - _refresh()_ command for status updates
|
|
||||||
* **Switch** - can detect state (possible values: on/off)
|
|
||||||
* **Switch Level** - represents current light level, usually 0-100 in percent
|
|
||||||
* **Health Check** - indicates ability to get device health notifications
|
|
||||||
|
|
||||||
## Device Health
|
|
||||||
|
|
||||||
A Category C6 Connected Cree LED Bulb with maxReportTime of 5 mins.
|
|
||||||
Check-in interval = 12 mins
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
If the device doesn't pair when trying from the SmartThings mobile app, it is possible that the device is out of range.
|
|
||||||
Pairing needs to be tried again by placing the device closer to the hub.
|
|
||||||
Instructions related to pairing, resetting and removing the device from SmartThings can be found in the following link:
|
|
||||||
* [Cree Connected LED Bulb Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/204258280-Cree-Connected-LED-Bulb)
|
|
||||||
@@ -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:
|
||||||
@@ -13,32 +13,31 @@
|
|||||||
* for the specific language governing permissions and limitations under the License.
|
* for the specific language governing permissions and limitations under the License.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
metadata {
|
metadata {
|
||||||
definition (name: "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"
|
||||||
capability "Health Check"
|
|
||||||
|
|
||||||
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") {
|
||||||
@@ -63,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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -82,30 +87,14 @@ 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)
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* PING is used by Device-Watch in attempt to reach the Device
|
|
||||||
* */
|
|
||||||
def ping() {
|
|
||||||
return zigbee.levelRefresh()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def refresh() {
|
def refresh() {
|
||||||
zigbee.onOffRefresh() + zigbee.levelRefresh()
|
zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.onOffConfig() + zigbee.levelConfig()
|
||||||
}
|
|
||||||
|
|
||||||
def healthPoll() {
|
|
||||||
log.debug "healthPoll()"
|
|
||||||
def cmds = zigbee.onOffRefresh() + zigbee.levelRefresh()
|
|
||||||
cmds.each{ sendHubCommand(new physicalgraph.device.HubAction(it))}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def configure() {
|
def configure() {
|
||||||
unschedule()
|
log.debug "Configuring Reporting and Bindings."
|
||||||
runEvery5Minutes("healthPoll")
|
zigbee.onOffConfig() + zigbee.levelConfig() + zigbee.onOffRefresh() + zigbee.levelRefresh()
|
||||||
// Device-Watch allows 2 check-in misses from device
|
|
||||||
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
|
|
||||||
zigbee.onOffRefresh() + zigbee.levelRefresh()
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,9 +21,7 @@ metadata {
|
|||||||
capability "Refresh"
|
capability "Refresh"
|
||||||
capability "Sensor"
|
capability "Sensor"
|
||||||
|
|
||||||
fingerprint mfr:"0063", prod:"4457", deviceJoinName: "Z-Wave Wall Dimmer"
|
fingerprint inClusters: "0x26"
|
||||||
fingerprint mfr:"0063", prod:"4944", deviceJoinName: "Z-Wave Wall Dimmer"
|
|
||||||
fingerprint mfr:"0063", prod:"5044", deviceJoinName: "Z-Wave Plug-In Dimmer"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
simulator {
|
simulator {
|
||||||
@@ -44,10 +42,6 @@ metadata {
|
|||||||
reply "200163,delay 5000,2602": "command: 2603, payload: 63"
|
reply "200163,delay 5000,2602": "command: 2603, payload: 63"
|
||||||
}
|
}
|
||||||
|
|
||||||
preferences {
|
|
||||||
input "ledIndicator", "enum", title: "LED Indicator", description: "Turn LED indicator... ", required: false, options:["on": "When On", "off": "When Off", "never": "Never"], defaultValue: "off"
|
|
||||||
}
|
|
||||||
|
|
||||||
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") {
|
||||||
@@ -76,28 +70,11 @@ metadata {
|
|||||||
}
|
}
|
||||||
|
|
||||||
main(["switch"])
|
main(["switch"])
|
||||||
details(["switch", "level", "refresh"])
|
details(["switch", "level", "indicator", "refresh"])
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def updated(){
|
|
||||||
switch (ledIndicator) {
|
|
||||||
case "on":
|
|
||||||
indicatorWhenOn()
|
|
||||||
break
|
|
||||||
case "off":
|
|
||||||
indicatorWhenOff()
|
|
||||||
break
|
|
||||||
case "never":
|
|
||||||
indicatorNever()
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
indicatorWhenOn()
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def parse(String description) {
|
def parse(String description) {
|
||||||
def result = null
|
def result = null
|
||||||
if (description != "updated") {
|
if (description != "updated") {
|
||||||
@@ -161,7 +138,6 @@ def zwaveEvent(physicalgraph.zwave.commands.manufacturerspecificv2.ManufacturerS
|
|||||||
log.debug "productTypeId: ${cmd.productTypeId}"
|
log.debug "productTypeId: ${cmd.productTypeId}"
|
||||||
def msr = String.format("%04X-%04X-%04X", cmd.manufacturerId, cmd.productTypeId, cmd.productId)
|
def msr = String.format("%04X-%04X-%04X", cmd.manufacturerId, cmd.productTypeId, cmd.productId)
|
||||||
updateDataValue("MSR", msr)
|
updateDataValue("MSR", msr)
|
||||||
updateDataValue("manufacturer", cmd.manufacturerName)
|
|
||||||
createEvent([descriptionText: "$device.displayName MSR: $msr", isStateChange: false])
|
createEvent([descriptionText: "$device.displayName MSR: $msr", isStateChange: false])
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -225,19 +201,19 @@ def refresh() {
|
|||||||
delayBetween(commands,100)
|
delayBetween(commands,100)
|
||||||
}
|
}
|
||||||
|
|
||||||
void indicatorWhenOn() {
|
def indicatorWhenOn() {
|
||||||
sendEvent(name: "indicatorStatus", value: "when on", display: false)
|
sendEvent(name: "indicatorStatus", value: "when on")
|
||||||
sendHubCommand(new physicalgraph.device.HubAction(zwave.configurationV1.configurationSet(configurationValue: [1], parameterNumber: 3, size: 1).format()))
|
zwave.configurationV1.configurationSet(configurationValue: [1], parameterNumber: 3, size: 1).format()
|
||||||
}
|
}
|
||||||
|
|
||||||
void indicatorWhenOff() {
|
def indicatorWhenOff() {
|
||||||
sendEvent(name: "indicatorStatus", value: "when off", display: false)
|
sendEvent(name: "indicatorStatus", value: "when off")
|
||||||
sendHubCommand(new physicalgraph.device.HubAction(zwave.configurationV1.configurationSet(configurationValue: [0], parameterNumber: 3, size: 1).format()))
|
zwave.configurationV1.configurationSet(configurationValue: [0], parameterNumber: 3, size: 1).format()
|
||||||
}
|
}
|
||||||
|
|
||||||
void indicatorNever() {
|
def indicatorNever() {
|
||||||
sendEvent(name: "indicatorStatus", value: "never", display: false)
|
sendEvent(name: "indicatorStatus", value: "never")
|
||||||
sendHubCommand(new physicalgraph.device.HubAction(zwave.configurationV1.configurationSet(configurationValue: [2], parameterNumber: 3, size: 1).format()))
|
zwave.configurationV1.configurationSet(configurationValue: [2], parameterNumber: 3, size: 1).format()
|
||||||
}
|
}
|
||||||
|
|
||||||
def invertSwitch(invert=true) {
|
def invertSwitch(invert=true) {
|
||||||
@@ -247,4 +223,4 @@ def invertSwitch(invert=true) {
|
|||||||
else {
|
else {
|
||||||
zwave.configurationV1.configurationSet(configurationValue: [0], parameterNumber: 4, size: 1).format()
|
zwave.configurationV1.configurationSet(configurationValue: [0], parameterNumber: 4, size: 1).format()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright 2015 SmartThings
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
|
||||||
* in compliance with the License. You may obtain a copy of the License at:
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
|
|
||||||
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
|
|
||||||
* for the specific language governing permissions and limitations under the License.
|
|
||||||
*
|
|
||||||
* Ecobee Sensor
|
|
||||||
*
|
|
||||||
* Author: SmartThings
|
|
||||||
*/
|
|
||||||
metadata {
|
|
||||||
definition (name: "Ecobee Sensor", namespace: "smartthings", author: "SmartThings") {
|
|
||||||
capability "Sensor"
|
|
||||||
capability "Temperature Measurement"
|
|
||||||
capability "Motion Sensor"
|
|
||||||
capability "Refresh"
|
|
||||||
}
|
|
||||||
|
|
||||||
tiles {
|
|
||||||
valueTile("temperature", "device.temperature", width: 2, height: 2) {
|
|
||||||
state("temperature", label:'${currentValue}°', unit:"F",
|
|
||||||
backgroundColors:[
|
|
||||||
// Celsius
|
|
||||||
[value: 0, color: "#153591"],
|
|
||||||
[value: 7, color: "#1e9cbb"],
|
|
||||||
[value: 15, color: "#90d2a7"],
|
|
||||||
[value: 23, color: "#44b621"],
|
|
||||||
[value: 28, color: "#f1d801"],
|
|
||||||
[value: 35, color: "#d04e00"],
|
|
||||||
[value: 37, color: "#bc2323"],
|
|
||||||
// Fahrenheit
|
|
||||||
[value: 40, color: "#153591"],
|
|
||||||
[value: 44, color: "#1e9cbb"],
|
|
||||||
[value: 59, color: "#90d2a7"],
|
|
||||||
[value: 74, color: "#44b621"],
|
|
||||||
[value: 84, color: "#f1d801"],
|
|
||||||
[value: 95, color: "#d04e00"],
|
|
||||||
[value: 96, color: "#bc2323"]
|
|
||||||
]
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
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")
|
|
||||||
}
|
|
||||||
|
|
||||||
standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat") {
|
|
||||||
state "default", action:"refresh.refresh", icon:"st.secondary.refresh"
|
|
||||||
}
|
|
||||||
|
|
||||||
main (["temperature","motion"])
|
|
||||||
details(["temperature","motion","refresh"])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def refresh() {
|
|
||||||
log.debug "refresh called"
|
|
||||||
poll()
|
|
||||||
}
|
|
||||||
|
|
||||||
void poll() {
|
|
||||||
log.debug "Executing 'poll' using parent SmartApp"
|
|
||||||
parent.pollChild()
|
|
||||||
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -21,7 +21,6 @@ metadata {
|
|||||||
attribute "tamper", "enum", ["detected", "clear"]
|
attribute "tamper", "enum", ["detected", "clear"]
|
||||||
attribute "heatAlarm", "enum", ["overheat detected", "clear", "rapid temperature rise", "underheat detected"]
|
attribute "heatAlarm", "enum", ["overheat detected", "clear", "rapid temperature rise", "underheat detected"]
|
||||||
fingerprint deviceId: "0x0701", inClusters: "0x5E, 0x86, 0x72, 0x5A, 0x59, 0x85, 0x73, 0x84, 0x80, 0x71, 0x56, 0x70, 0x31, 0x8E, 0x22, 0x9C, 0x98, 0x7A", outClusters: "0x20, 0x8B"
|
fingerprint deviceId: "0x0701", inClusters: "0x5E, 0x86, 0x72, 0x5A, 0x59, 0x85, 0x73, 0x84, 0x80, 0x71, 0x56, 0x70, 0x31, 0x8E, 0x22, 0x9C, 0x98, 0x7A", outClusters: "0x20, 0x8B"
|
||||||
fingerprint mfr:"010F", prod:"0C02", model:"1002"
|
|
||||||
}
|
}
|
||||||
simulator {
|
simulator {
|
||||||
//battery
|
//battery
|
||||||
|
|||||||
@@ -682,7 +682,7 @@ def setHeatingSetpoint(degrees) {
|
|||||||
def temperatureScale = getTemperatureScale()
|
def temperatureScale = getTemperatureScale()
|
||||||
|
|
||||||
def degreesInteger = degrees as Integer
|
def degreesInteger = degrees as Integer
|
||||||
sendEvent("name":"heatingSetpoint", "value":degreesInteger, "unit":temperatureScale)
|
sendEvent("name":"heatingSetpoint", "value":degreesInteger)
|
||||||
|
|
||||||
def celsius = (getTemperatureScale() == "C") ? degreesInteger : (fahrenheitToCelsius(degreesInteger) as Double).round(2)
|
def celsius = (getTemperatureScale() == "C") ? degreesInteger : (fahrenheitToCelsius(degreesInteger) as Double).round(2)
|
||||||
"st wattr 0x${device.deviceNetworkId} 1 0x201 0x12 0x29 {" + hex(celsius*100) + "}"
|
"st wattr 0x${device.deviceNetworkId} 1 0x201 0x12 0x29 {" + hex(celsius*100) + "}"
|
||||||
@@ -691,7 +691,7 @@ def setHeatingSetpoint(degrees) {
|
|||||||
|
|
||||||
def setCoolingSetpoint(degrees) {
|
def setCoolingSetpoint(degrees) {
|
||||||
def degreesInteger = degrees as Integer
|
def degreesInteger = degrees as Integer
|
||||||
sendEvent("name":"coolingSetpoint", "value":degreesInteger, "unit":temperatureScale)
|
sendEvent("name":"coolingSetpoint", "value":degreesInteger)
|
||||||
def celsius = (getTemperatureScale() == "C") ? degreesInteger : (fahrenheitToCelsius(degreesInteger) as Double).round(2)
|
def celsius = (getTemperatureScale() == "C") ? degreesInteger : (fahrenheitToCelsius(degreesInteger) as Double).round(2)
|
||||||
"st wattr 0x${device.deviceNetworkId} 1 0x201 0x11 0x29 {" + hex(celsius*100) + "}"
|
"st wattr 0x${device.deviceNetworkId} 1 0x201 0x11 0x29 {" + hex(celsius*100) + "}"
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
* GE Link Bulb
|
* GE Link 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:
|
||||||
@@ -36,73 +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"
|
||||||
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,1000", outClusters: "0019", manufacturer: "GE", model: "SoftWhite", deviceJoinName: "GE Link Soft White Bulb"
|
}
|
||||||
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,1000", outClusters: "0019", manufacturer: "GE", model: "Daylight", deviceJoinName: "GE Link Daylight 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() {
|
||||||
@@ -188,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
|
||||||
|
}
|
||||||
@@ -1,126 +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.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
metadata {
|
|
||||||
definition (name: "Gentle Wake Up Controller", namespace: "smartthings", author: "SmartThings") {
|
|
||||||
capability "Switch"
|
|
||||||
capability "Timed Session"
|
|
||||||
|
|
||||||
attribute "percentComplete", "number"
|
|
||||||
|
|
||||||
command "setPercentComplete", ["number"]
|
|
||||||
}
|
|
||||||
|
|
||||||
simulator {
|
|
||||||
// TODO: define status and reply messages here
|
|
||||||
}
|
|
||||||
|
|
||||||
tiles(scale: 2) {
|
|
||||||
|
|
||||||
multiAttributeTile(name: "richTile", type:"generic", width:6, height:4) {
|
|
||||||
tileAttribute("sessionStatus", key: "PRIMARY_CONTROL") {
|
|
||||||
attributeState "cancelled", action: "timed session.start", icon: "http://f.cl.ly/items/322n181j2K3f281r2s0A/playbutton.png", backgroundColor: "#ffffff", nextState: "running"
|
|
||||||
attributeState "stopped", action: "timed session.start", icon: "http://f.cl.ly/items/322n181j2K3f281r2s0A/playbutton.png", backgroundColor: "#ffffff", nextState: "cancelled"
|
|
||||||
attributeState "running", action: "timed session.stop", icon: "http://f.cl.ly/items/0B3y3p2V3X2l3P3y3W09/stopbutton.png", backgroundColor: "#79b821", nextState: "cancelled"
|
|
||||||
}
|
|
||||||
tileAttribute("timeRemaining", key: "SECONDARY_CONTROL") {
|
|
||||||
attributeState "timeRemaining", label:'${currentValue} remaining'
|
|
||||||
}
|
|
||||||
tileAttribute("percentComplete", key: "SLIDER_CONTROL") {
|
|
||||||
attributeState "percentComplete", action: "timed session.setTimeRemaining"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// start/stop
|
|
||||||
standardTile("sessionStatusTile", "sessionStatus", width: 1, height: 1, canChangeIcon: true) {
|
|
||||||
state "cancelled", label: "Stopped", action: "timed session.start", backgroundColor: "#ffffff", icon: "http://f.cl.ly/items/1J1g0H2P0S1G1f2O1s1s/icon.png"
|
|
||||||
state "stopped", label: "Stopped", action: "timed session.start", backgroundColor: "#ffffff", icon: "http://f.cl.ly/items/1J1g0H2P0S1G1f2O1s1s/icon.png"
|
|
||||||
state "running", label: "Running", action: "timed session.stop", backgroundColor: "#79b821", icon: "http://f.cl.ly/items/1J1g0H2P0S1G1f2O1s1s/icon.png"
|
|
||||||
}
|
|
||||||
|
|
||||||
// duration
|
|
||||||
valueTile("timeRemainingTile", "timeRemaining", decoration: "flat", width: 2) {
|
|
||||||
state "timeRemaining", label:'${currentValue} left'
|
|
||||||
}
|
|
||||||
controlTile("percentCompleteTile", "percentComplete", "slider", height: 1, width: 3) {
|
|
||||||
state "percentComplete", action: "timed session.setTimeRemaining"
|
|
||||||
}
|
|
||||||
|
|
||||||
main "sessionStatusTile"
|
|
||||||
details "richTile"
|
|
||||||
// details(["richTile", "sessionStatusTile", "timeRemainingTile", "percentCompleteTile"])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// parse events into attributes
|
|
||||||
def parse(description) {
|
|
||||||
log.debug "Parsing '${description}'"
|
|
||||||
// TODO: handle 'switch' attribute
|
|
||||||
// TODO: handle 'level' attribute
|
|
||||||
// TODO: handle 'sessionStatus' attribute
|
|
||||||
// TODO: handle 'timeRemaining' attribute
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// handle commands
|
|
||||||
def on() {
|
|
||||||
log.debug "Executing 'on'"
|
|
||||||
startDimming()
|
|
||||||
}
|
|
||||||
|
|
||||||
def off() {
|
|
||||||
log.debug "Executing 'off'"
|
|
||||||
stopDimming()
|
|
||||||
}
|
|
||||||
|
|
||||||
def setTimeRemaining(percentComplete) {
|
|
||||||
log.debug "Executing 'setTimeRemaining' to ${percentComplete}% complete"
|
|
||||||
parent.jumpTo(percentComplete)
|
|
||||||
}
|
|
||||||
|
|
||||||
def start() {
|
|
||||||
log.debug "Executing 'start'"
|
|
||||||
startDimming()
|
|
||||||
}
|
|
||||||
|
|
||||||
def stop() {
|
|
||||||
log.debug "Executing 'stop'"
|
|
||||||
stopDimming()
|
|
||||||
}
|
|
||||||
|
|
||||||
def pause() {
|
|
||||||
log.debug "Executing 'pause'"
|
|
||||||
// TODO: handle 'pause' command
|
|
||||||
}
|
|
||||||
|
|
||||||
def cancel() {
|
|
||||||
log.debug "Executing 'cancel'"
|
|
||||||
stopDimming()
|
|
||||||
}
|
|
||||||
|
|
||||||
def startDimming() {
|
|
||||||
log.trace "startDimming"
|
|
||||||
log.debug "parent: ${parent}"
|
|
||||||
parent.start("controller")
|
|
||||||
}
|
|
||||||
|
|
||||||
def stopDimming() {
|
|
||||||
log.trace "stopDimming"
|
|
||||||
log.debug "parent: ${parent}"
|
|
||||||
parent.stop("controller")
|
|
||||||
}
|
|
||||||
|
|
||||||
def controllerEvent(eventData) {
|
|
||||||
log.trace "controllerEvent"
|
|
||||||
sendEvent(eventData)
|
|
||||||
}
|
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
metadata {
|
metadata {
|
||||||
definition (name: "Harmony Activity", namespace: "smartthings", author: "Juan Risso") {
|
definition (name: "Logitech Harmony Activity", namespace: "smartthings", author: "Juan Risso") {
|
||||||
capability "Switch"
|
capability "Switch"
|
||||||
capability "Actuator"
|
capability "Actuator"
|
||||||
capability "Refresh"
|
capability "Refresh"
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,177 +0,0 @@
|
|||||||
/**
|
|
||||||
* Hue Bloom
|
|
||||||
*
|
|
||||||
* Philips Hue Type "Color Light"
|
|
||||||
*
|
|
||||||
* Author: SmartThings
|
|
||||||
*/
|
|
||||||
|
|
||||||
// for the UI
|
|
||||||
metadata {
|
|
||||||
// Automatically generated. Make future change here.
|
|
||||||
definition (name: "Hue Bloom", namespace: "smartthings", author: "SmartThings") {
|
|
||||||
capability "Switch Level"
|
|
||||||
capability "Actuator"
|
|
||||||
capability "Color Control"
|
|
||||||
capability "Switch"
|
|
||||||
capability "Refresh"
|
|
||||||
capability "Sensor"
|
|
||||||
capability "Health Check"
|
|
||||||
|
|
||||||
command "setAdjustedColor"
|
|
||||||
command "reset"
|
|
||||||
command "refresh"
|
|
||||||
}
|
|
||||||
|
|
||||||
simulator {
|
|
||||||
// TODO: define status and reply messages here
|
|
||||||
}
|
|
||||||
|
|
||||||
tiles (scale: 2){
|
|
||||||
multiAttributeTile(name:"rich-control", type: "lighting", width: 6, height: 4, canChangeIcon: true){
|
|
||||||
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
|
|
||||||
attributeState "on", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff"
|
|
||||||
attributeState "off", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
|
|
||||||
attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff"
|
|
||||||
attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
|
|
||||||
}
|
|
||||||
tileAttribute ("device.level", key: "SLIDER_CONTROL") {
|
|
||||||
attributeState "level", action:"switch level.setLevel", range:"(0..100)"
|
|
||||||
}
|
|
||||||
tileAttribute ("device.color", key: "COLOR_CONTROL") {
|
|
||||||
attributeState "color", action:"setAdjustedColor"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
standardTile("reset", "device.reset", height: 2, width: 2, inactiveLabel: false, decoration: "flat") {
|
|
||||||
state "default", label:"Reset To White", action:"reset", icon:"st.lights.philips.hue-single"
|
|
||||||
}
|
|
||||||
|
|
||||||
standardTile("refresh", "device.refresh", height: 2, width: 2, inactiveLabel: false, decoration: "flat") {
|
|
||||||
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
|
|
||||||
}
|
|
||||||
|
|
||||||
main(["rich-control"])
|
|
||||||
details(["rich-control", "reset", "refresh"])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void installed() {
|
|
||||||
sendEvent(name: "checkInterval", value: 60 * 12, data: [protocol: "lan", hubHardwareId: device.hub.hardwareID], displayed: false)
|
|
||||||
}
|
|
||||||
|
|
||||||
// parse events into attributes
|
|
||||||
def parse(description) {
|
|
||||||
log.debug "parse() - $description"
|
|
||||||
def results = []
|
|
||||||
|
|
||||||
def map = description
|
|
||||||
if (description instanceof String) {
|
|
||||||
log.debug "Hue Bulb stringToMap - ${map}"
|
|
||||||
map = stringToMap(description)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (map?.name && map?.value) {
|
|
||||||
results << createEvent(name: "${map?.name}", value: "${map?.value}")
|
|
||||||
}
|
|
||||||
results
|
|
||||||
}
|
|
||||||
|
|
||||||
// handle commands
|
|
||||||
void on() {
|
|
||||||
log.trace parent.on(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
void off() {
|
|
||||||
log.trace parent.off(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
void setLevel(percent) {
|
|
||||||
log.debug "Executing 'setLevel'"
|
|
||||||
if (verifyPercent(percent)) {
|
|
||||||
log.trace parent.setLevel(this, percent)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void setSaturation(percent) {
|
|
||||||
log.debug "Executing 'setSaturation'"
|
|
||||||
if (verifyPercent(percent)) {
|
|
||||||
log.trace parent.setSaturation(this, percent)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void setHue(percent) {
|
|
||||||
log.debug "Executing 'setHue'"
|
|
||||||
if (verifyPercent(percent)) {
|
|
||||||
log.trace parent.setHue(this, percent)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void setColor(value) {
|
|
||||||
def events = []
|
|
||||||
def validValues = [:]
|
|
||||||
|
|
||||||
if (verifyPercent(value.hue)) {
|
|
||||||
validValues.hue = value.hue
|
|
||||||
}
|
|
||||||
if (verifyPercent(value.saturation)) {
|
|
||||||
validValues.saturation = value.saturation
|
|
||||||
}
|
|
||||||
if (value.hex != null) {
|
|
||||||
if (value.hex ==~ /^\#([A-Fa-f0-9]){6}$/) {
|
|
||||||
validValues.hex = value.hex
|
|
||||||
} else {
|
|
||||||
log.warn "$value.hex is not a valid color"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (verifyPercent(value.level)) {
|
|
||||||
validValues.level = value.level
|
|
||||||
}
|
|
||||||
if (value.switch == "off" || (value.level != null && value.level <= 0)) {
|
|
||||||
validValues.switch = "off"
|
|
||||||
} else {
|
|
||||||
validValues.switch = "on"
|
|
||||||
}
|
|
||||||
if (!validValues.isEmpty()) {
|
|
||||||
log.trace parent.setColor(this, validValues)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void reset() {
|
|
||||||
log.debug "Executing 'reset'"
|
|
||||||
def value = [hue:20, saturation:2]
|
|
||||||
setAdjustedColor(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
void setAdjustedColor(value) {
|
|
||||||
if (value) {
|
|
||||||
log.trace "setAdjustedColor: ${value}"
|
|
||||||
def adjusted = value + [:]
|
|
||||||
// Needed because color picker always sends 100
|
|
||||||
adjusted.level = null
|
|
||||||
setColor(adjusted)
|
|
||||||
} else {
|
|
||||||
log.warn "Invalid color input $value"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void refresh() {
|
|
||||||
log.debug "Executing 'refresh'"
|
|
||||||
parent.manualRefresh()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def ping() {
|
|
||||||
log.debug "${parent.ping(this)}"
|
|
||||||
}
|
|
||||||
@@ -7,15 +7,8 @@
|
|||||||
metadata {
|
metadata {
|
||||||
// Automatically generated. Make future change here.
|
// Automatically generated. Make future change here.
|
||||||
definition (name: "Hue Bridge", namespace: "smartthings", author: "SmartThings") {
|
definition (name: "Hue Bridge", namespace: "smartthings", author: "SmartThings") {
|
||||||
capability "Health Check"
|
attribute "serialNumber", "string"
|
||||||
|
|
||||||
attribute "networkAddress", "string"
|
attribute "networkAddress", "string"
|
||||||
// Used to indicate if bridge is reachable or not, i.e. is the bridge connected to the network
|
|
||||||
// Possible values "Online" or "Offline"
|
|
||||||
attribute "status", "string"
|
|
||||||
// Id is the number on the back of the hub, Hue uses last six digits of Mac address
|
|
||||||
// This is also used in the Hue application as ID
|
|
||||||
attribute "idNumber", "string"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
simulator {
|
simulator {
|
||||||
@@ -24,30 +17,28 @@ metadata {
|
|||||||
|
|
||||||
tiles(scale: 2) {
|
tiles(scale: 2) {
|
||||||
multiAttributeTile(name:"rich-control"){
|
multiAttributeTile(name:"rich-control"){
|
||||||
tileAttribute ("device.status", key: "PRIMARY_CONTROL") {
|
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
|
||||||
attributeState "Offline", label: '${currentValue}', action: "", icon: "st.Lighting.light99-hue", backgroundColor: "#ffffff"
|
attributeState "default", label: "Hue Bridge", action: "", icon: "st.Lighting.light99-hue", backgroundColor: "#F3C200"
|
||||||
attributeState "Online", label: '${currentValue}', action: "", icon: "st.Lighting.light99-hue", backgroundColor: "#79b821"
|
}
|
||||||
}
|
tileAttribute ("serialNumber", key: "SECONDARY_CONTROL") {
|
||||||
}
|
attributeState "default", label:'SN: ${currentValue}'
|
||||||
valueTile("doNotRemove", "v", decoration: "flat", height: 2, width: 6, inactiveLabel: false) {
|
}
|
||||||
state "default", label:'If removed, Hue lights will not work properly'
|
}
|
||||||
|
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("idNumber", "device.idNumber", decoration: "flat", height: 2, width: 6, inactiveLabel: false) {
|
valueTile("serialNumber", "device.serialNumber", decoration: "flat", height: 1, width: 2, inactiveLabel: false) {
|
||||||
state "default", label:'ID: ${currentValue}'
|
state "default", label:'SN: ${currentValue}'
|
||||||
}
|
}
|
||||||
valueTile("networkAddress", "device.networkAddress", decoration: "flat", height: 2, width: 6, inactiveLabel: false) {
|
valueTile("networkAddress", "device.networkAddress", decoration: "flat", height: 2, width: 4, inactiveLabel: false) {
|
||||||
state "default", label:'IP: ${currentValue}'
|
state "default", label:'${currentValue}', height: 1, width: 2, inactiveLabel: false
|
||||||
}
|
}
|
||||||
|
|
||||||
main (["rich-control"])
|
main (["icon"])
|
||||||
details(["rich-control", "doNotRemove", "idNumber", "networkAddress"])
|
details(["rich-control", "networkAddress"])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void installed() {
|
|
||||||
sendEvent(name: "checkInterval", value: 60 * 12, data: [protocol: "lan"], displayed: false)
|
|
||||||
}
|
|
||||||
|
|
||||||
// parse events into attributes
|
// parse events into attributes
|
||||||
def parse(description) {
|
def parse(description) {
|
||||||
log.debug "Parsing '${description}'"
|
log.debug "Parsing '${description}'"
|
||||||
@@ -68,7 +59,7 @@ def parse(description) {
|
|||||||
log.trace "HUE BRIDGE, GENERATING EVENT: $map.name: $map.value"
|
log.trace "HUE BRIDGE, GENERATING EVENT: $map.name: $map.value"
|
||||||
results << createEvent(name: "${map.name}", value: "${map.value}")
|
results << createEvent(name: "${map.name}", value: "${map.value}")
|
||||||
} else {
|
} else {
|
||||||
log.trace "Parsing description"
|
log.trace "Parsing description"
|
||||||
def msg = parseLanMessage(description)
|
def msg = parseLanMessage(description)
|
||||||
if (msg.body) {
|
if (msg.body) {
|
||||||
def contentType = msg.headers["Content-Type"]
|
def contentType = msg.headers["Content-Type"]
|
||||||
@@ -76,17 +67,17 @@ def parse(description) {
|
|||||||
def bulbs = new groovy.json.JsonSlurper().parseText(msg.body)
|
def bulbs = new groovy.json.JsonSlurper().parseText(msg.body)
|
||||||
if (bulbs.state) {
|
if (bulbs.state) {
|
||||||
log.info "Bridge response: $msg.body"
|
log.info "Bridge response: $msg.body"
|
||||||
|
} else {
|
||||||
|
// Sending Bulbs List to parent"
|
||||||
|
if (parent.state.inBulbDiscovery)
|
||||||
|
log.info parent.bulbListHandler(device.hub.id, msg.body)
|
||||||
}
|
}
|
||||||
} 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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
results
|
results
|
||||||
}
|
}
|
||||||
|
|
||||||
def ping() {
|
|
||||||
log.debug "${parent.ping(this)}"
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Hue Bulb
|
* Hue Bulb
|
||||||
*
|
*
|
||||||
* Philips Hue Type "Extended Color Light"
|
|
||||||
*
|
|
||||||
* Author: SmartThings
|
* Author: SmartThings
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// for the UI
|
// for the UI
|
||||||
metadata {
|
metadata {
|
||||||
// Automatically generated. Make future change here.
|
// Automatically generated. Make future change here.
|
||||||
@@ -13,15 +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"
|
||||||
capability "Health Check"
|
|
||||||
|
|
||||||
command "setAdjustedColor"
|
command "setAdjustedColor"
|
||||||
command "reset"
|
command "reset"
|
||||||
command "refresh"
|
command "refresh"
|
||||||
}
|
}
|
||||||
|
|
||||||
simulator {
|
simulator {
|
||||||
@@ -29,7 +25,7 @@ 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:"#79b821", nextState:"turningOff"
|
attributeState "on", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff"
|
||||||
attributeState "off", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
|
attributeState "off", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||||
@@ -37,49 +33,34 @@ metadata {
|
|||||||
attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
|
attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||||
}
|
}
|
||||||
tileAttribute ("device.level", key: "SLIDER_CONTROL") {
|
tileAttribute ("device.level", key: "SLIDER_CONTROL") {
|
||||||
attributeState "level", action:"switch level.setLevel", range:"(0..100)"
|
attributeState "level", action:"switch level.setLevel"
|
||||||
}
|
}
|
||||||
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"
|
state "default", label:"Reset Color", action:"reset", icon:"st.lights.philips.hue-single"
|
||||||
}
|
|
||||||
|
|
||||||
valueTile("colorTemp", "device.colorTemperature", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
|
||||||
state "colorTemperature", label: 'WHITES'
|
|
||||||
}
|
|
||||||
|
|
||||||
standardTile("reset", "device.reset", height: 2, width: 2, inactiveLabel: false, decoration: "flat") {
|
|
||||||
state "default", label:"Reset To White", 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"])
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
void installed() {
|
main(["switch"])
|
||||||
sendEvent(name: "checkInterval", value: 60 * 12, data: [protocol: "lan", hubHardwareId: device.hub.hardwareID], displayed: false)
|
details(["switch", "levelSliderControl", "rgbSelector", "refresh", "reset"])
|
||||||
}
|
}
|
||||||
|
|
||||||
// parse events into attributes
|
// parse events into attributes
|
||||||
def parse(description) {
|
def parse(description) {
|
||||||
log.debug "parse() - $description"
|
log.debug "parse() - $description"
|
||||||
def results = []
|
def results = []
|
||||||
|
|
||||||
def map = description
|
def map = description
|
||||||
if (description instanceof String) {
|
if (description instanceof String) {
|
||||||
log.debug "Hue Bulb stringToMap - ${map}"
|
log.debug "Hue Bulb stringToMap - ${map}"
|
||||||
map = stringToMap(description)
|
map = stringToMap(description)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (map?.name && map?.value) {
|
if (map?.name && map?.value) {
|
||||||
results << createEvent(name: "${map?.name}", value: "${map?.value}")
|
results << createEvent(name: "${map?.name}", value: "${map?.value}")
|
||||||
}
|
}
|
||||||
@@ -87,107 +68,91 @@ 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")
|
||||||
}
|
}
|
||||||
|
|
||||||
void off() {
|
def off() {
|
||||||
log.trace parent.off(this)
|
log.trace parent.off(this)
|
||||||
|
sendEvent(name: "switch", value: "off")
|
||||||
}
|
}
|
||||||
|
|
||||||
void setLevel(percent) {
|
def nextLevel() {
|
||||||
log.debug "Executing 'setLevel'"
|
def level = device.latestValue("level") as Integer ?: 0
|
||||||
if (verifyPercent(percent)) {
|
if (level <= 100) {
|
||||||
log.trace parent.setLevel(this, percent)
|
level = Math.min(25 * (Math.round(level / 25) + 1), 100) as Integer
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
level = 25
|
||||||
|
}
|
||||||
|
setLevel(level)
|
||||||
}
|
}
|
||||||
|
|
||||||
void setSaturation(percent) {
|
def setLevel(percent) {
|
||||||
log.debug "Executing 'setSaturation'"
|
log.debug "Executing 'setLevel'"
|
||||||
if (verifyPercent(percent)) {
|
parent.setLevel(this, percent)
|
||||||
log.trace parent.setSaturation(this, percent)
|
sendEvent(name: "level", value: percent)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void setHue(percent) {
|
def setSaturation(percent) {
|
||||||
log.debug "Executing 'setHue'"
|
log.debug "Executing 'setSaturation'"
|
||||||
if (verifyPercent(percent)) {
|
parent.setSaturation(this, percent)
|
||||||
log.trace parent.setHue(this, percent)
|
sendEvent(name: "saturation", value: percent)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void setColor(value) {
|
def setHue(percent) {
|
||||||
def events = []
|
log.debug "Executing 'setHue'"
|
||||||
def validValues = [:]
|
parent.setHue(this, percent)
|
||||||
|
sendEvent(name: "hue", value: percent)
|
||||||
if (verifyPercent(value.hue)) {
|
|
||||||
validValues.hue = value.hue
|
|
||||||
}
|
|
||||||
if (verifyPercent(value.saturation)) {
|
|
||||||
validValues.saturation = value.saturation
|
|
||||||
}
|
|
||||||
if (value.hex != null) {
|
|
||||||
if (value.hex ==~ /^\#([A-Fa-f0-9]){6}$/) {
|
|
||||||
validValues.hex = value.hex
|
|
||||||
} else {
|
|
||||||
log.warn "$value.hex is not a valid color"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (verifyPercent(value.level)) {
|
|
||||||
validValues.level = value.level
|
|
||||||
}
|
|
||||||
if (value.switch == "off" || (value.level != null && value.level <= 0)) {
|
|
||||||
validValues.switch = "off"
|
|
||||||
} else {
|
|
||||||
validValues.switch = "on"
|
|
||||||
}
|
|
||||||
if (!validValues.isEmpty()) {
|
|
||||||
log.trace parent.setColor(this, validValues)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void reset() {
|
def setColor(value) {
|
||||||
log.debug "Executing 'reset'"
|
log.debug "setColor: ${value}, $this"
|
||||||
setColorTemperature(4000)
|
parent.setColor(this, value)
|
||||||
|
if (value.hue) { sendEvent(name: "hue", value: value.hue)}
|
||||||
|
if (value.saturation) { sendEvent(name: "saturation", value: value.saturation)}
|
||||||
|
if (value.hex) { sendEvent(name: "color", value: value.hex)}
|
||||||
|
if (value.level) { sendEvent(name: "level", value: value.level)}
|
||||||
|
if (value.switch) { sendEvent(name: "switch", value: value.switch)}
|
||||||
}
|
}
|
||||||
|
|
||||||
void setAdjustedColor(value) {
|
def reset() {
|
||||||
if (value) {
|
log.debug "Executing 'reset'"
|
||||||
|
def value = [level:100, hex:"#90C638", saturation:56, hue:23]
|
||||||
|
setAdjustedColor(value)
|
||||||
|
parent.poll()
|
||||||
|
}
|
||||||
|
|
||||||
|
def setAdjustedColor(value) {
|
||||||
|
if (value) {
|
||||||
log.trace "setAdjustedColor: ${value}"
|
log.trace "setAdjustedColor: ${value}"
|
||||||
def adjusted = value + [:]
|
def adjusted = value + [:]
|
||||||
|
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 $value"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void setColorTemperature(value) {
|
def refresh() {
|
||||||
if (value) {
|
log.debug "Executing 'refresh'"
|
||||||
log.trace "setColorTemperature: ${value}k"
|
parent.manualRefresh()
|
||||||
log.trace parent.setColorTemperature(this, value)
|
|
||||||
} else {
|
|
||||||
log.warn "Invalid color temperature $value"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void refresh() {
|
def adjustOutgoingHue(percent) {
|
||||||
log.debug "Executing 'refresh'"
|
def adjusted = percent
|
||||||
parent?.manualRefresh()
|
if (percent > 31) {
|
||||||
}
|
if (percent < 63.0) {
|
||||||
|
adjusted = percent + (7 * (percent -30 ) / 32)
|
||||||
def verifyPercent(percent) {
|
}
|
||||||
if (percent == null)
|
else if (percent < 73.0) {
|
||||||
return false
|
adjusted = 69 + (5 * (percent - 62) / 10)
|
||||||
else if (percent >= 0 && percent <= 100) {
|
}
|
||||||
return true
|
else {
|
||||||
} else {
|
adjusted = percent + (2 * (100 - percent) / 28)
|
||||||
log.warn "$percent is not 0-100"
|
}
|
||||||
return false
|
}
|
||||||
}
|
log.info "percent: $percent, adjusted: $adjusted"
|
||||||
}
|
adjusted
|
||||||
|
|
||||||
def ping() {
|
|
||||||
log.trace "${parent.ping(this)}"
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
* Hue Lux Bulb
|
* Hue Lux Bulb
|
||||||
*
|
*
|
||||||
* Philips Hue Type "Dimmable Light"
|
|
||||||
*
|
|
||||||
* Author: SmartThings
|
* Author: SmartThings
|
||||||
*/
|
*/
|
||||||
// for the UI
|
// for the UI
|
||||||
@@ -14,15 +12,14 @@ metadata {
|
|||||||
capability "Switch"
|
capability "Switch"
|
||||||
capability "Refresh"
|
capability "Refresh"
|
||||||
capability "Sensor"
|
capability "Sensor"
|
||||||
capability "Health Check"
|
|
||||||
|
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") {
|
||||||
@@ -34,23 +31,29 @@ metadata {
|
|||||||
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", range:"(0..100)"
|
||||||
}
|
}
|
||||||
|
tileAttribute ("device.level", key: "SECONDARY_CONTROL") {
|
||||||
|
attributeState "level", label: 'Level ${currentValue}%'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
standardTile("switch", "device.switch", width: 2, height: 2, canChangeIcon: true) {
|
||||||
|
state "on", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff"
|
||||||
|
state "off", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||||
|
state "turningOn", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff"
|
||||||
|
state "turningOff", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||||
|
}
|
||||||
|
|
||||||
controlTile("levelSliderControl", "device.level", "slider", height: 1, width: 2, inactiveLabel: false, range:"(0..100)") {
|
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"])
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
void installed() {
|
|
||||||
sendEvent(name: "checkInterval", value: 60 * 12, data: [protocol: "lan", hubHardwareId: device.hub.hardwareID], displayed: false)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// parse events into attributes
|
// parse events into attributes
|
||||||
@@ -71,28 +74,23 @@ def parse(description) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// handle commands
|
// handle commands
|
||||||
void on() {
|
def on() {
|
||||||
log.trace parent.on(this)
|
parent.on(this)
|
||||||
|
sendEvent(name: "switch", value: "on")
|
||||||
}
|
}
|
||||||
|
|
||||||
void off() {
|
def off() {
|
||||||
log.trace parent.off(this)
|
parent.off(this)
|
||||||
|
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)
|
||||||
} else {
|
|
||||||
log.warn "$percent is not 0-100"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void refresh() {
|
def refresh() {
|
||||||
log.debug "Executing 'refresh'"
|
log.debug "Executing 'refresh'"
|
||||||
parent.manualRefresh()
|
parent.manualRefresh()
|
||||||
}
|
}
|
||||||
|
|
||||||
def ping() {
|
|
||||||
log.debug "${parent.ping(this)}"
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,112 +0,0 @@
|
|||||||
/**
|
|
||||||
* Hue White Ambiance Bulb
|
|
||||||
*
|
|
||||||
* Philips Hue Type "Color Temperature Light"
|
|
||||||
*
|
|
||||||
* Author: SmartThings
|
|
||||||
*/
|
|
||||||
|
|
||||||
// for the UI
|
|
||||||
metadata {
|
|
||||||
// Automatically generated. Make future change here.
|
|
||||||
definition (name: "Hue White Ambiance Bulb", namespace: "smartthings", author: "SmartThings") {
|
|
||||||
capability "Switch Level"
|
|
||||||
capability "Actuator"
|
|
||||||
capability "Color Temperature"
|
|
||||||
capability "Switch"
|
|
||||||
capability "Refresh"
|
|
||||||
capability "Health Check"
|
|
||||||
|
|
||||||
command "refresh"
|
|
||||||
}
|
|
||||||
|
|
||||||
simulator {
|
|
||||||
// TODO: define status and reply messages here
|
|
||||||
}
|
|
||||||
|
|
||||||
tiles (scale: 2){
|
|
||||||
multiAttributeTile(name:"rich-control", type: "lighting", width: 6, height: 4, canChangeIcon: true){
|
|
||||||
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
|
|
||||||
attributeState "on", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff"
|
|
||||||
attributeState "off", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
|
|
||||||
attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff"
|
|
||||||
attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
|
|
||||||
}
|
|
||||||
tileAttribute ("device.level", key: "SLIDER_CONTROL") {
|
|
||||||
attributeState "level", action:"switch level.setLevel", range:"(0..100)"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
controlTile("colorTempSliderControl", "device.colorTemperature", "slider", width: 4, height: 2, inactiveLabel: false, range:"(2200..6500)") {
|
|
||||||
state "colorTemperature", action:"color temperature.setColorTemperature"
|
|
||||||
}
|
|
||||||
|
|
||||||
valueTile("colorTemp", "device.colorTemperature", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
|
||||||
state "colorTemperature", label: 'WHITES'
|
|
||||||
}
|
|
||||||
|
|
||||||
standardTile("refresh", "device.refresh", height: 2, width: 2, inactiveLabel: false, decoration: "flat") {
|
|
||||||
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
|
|
||||||
}
|
|
||||||
|
|
||||||
main(["rich-control"])
|
|
||||||
details(["rich-control", "colorTempSliderControl", "colorTemp", "refresh"])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void installed() {
|
|
||||||
sendEvent(name: "checkInterval", value: 60 * 12, data: [protocol: "lan", hubHardwareId: device.hub.hardwareID], displayed: false)
|
|
||||||
}
|
|
||||||
|
|
||||||
// parse events into attributes
|
|
||||||
def parse(description) {
|
|
||||||
log.debug "parse() - $description"
|
|
||||||
def results = []
|
|
||||||
|
|
||||||
def map = description
|
|
||||||
if (description instanceof String) {
|
|
||||||
log.debug "Hue Ambience Bulb stringToMap - ${map}"
|
|
||||||
map = stringToMap(description)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (map?.name && map?.value) {
|
|
||||||
results << createEvent(name: "${map?.name}", value: "${map?.value}")
|
|
||||||
}
|
|
||||||
results
|
|
||||||
}
|
|
||||||
|
|
||||||
// handle commands
|
|
||||||
void on() {
|
|
||||||
log.trace parent.on(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
void off() {
|
|
||||||
log.trace parent.off(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
void setLevel(percent) {
|
|
||||||
log.debug "Executing 'setLevel'"
|
|
||||||
if (percent != null && percent >= 0 && percent <= 100) {
|
|
||||||
log.trace parent.setLevel(this, percent)
|
|
||||||
} else {
|
|
||||||
log.warn "$percent is not 0-100"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void setColorTemperature(value) {
|
|
||||||
if (value) {
|
|
||||||
log.trace "setColorTemperature: ${value}k"
|
|
||||||
log.trace parent.setColorTemperature(this, value)
|
|
||||||
} else {
|
|
||||||
log.warn "Invalid color temperature"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void refresh() {
|
|
||||||
log.debug "Executing 'refresh'"
|
|
||||||
parent.manualRefresh()
|
|
||||||
}
|
|
||||||
|
|
||||||
def ping() {
|
|
||||||
log.debug "${parent.ping(this)}"
|
|
||||||
}
|
|
||||||
@@ -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,22 +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.
|
|
||||||
# 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,19 +1,16 @@
|
|||||||
/*
|
/**
|
||||||
* Copyright 2016 SmartThings
|
* Copyright 2015 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.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
metadata {
|
metadata {
|
||||||
definition (name: "Mobile Presence", namespace: "smartthings", author: "SmartThings") {
|
definition (name: "Mobile Presence", namespace: "smartthings", author: "SmartThings") {
|
||||||
capability "Presence Sensor"
|
capability "Presence Sensor"
|
||||||
@@ -44,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,
|
||||||
@@ -76,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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -88,4 +84,4 @@ private getState(String value) {
|
|||||||
case "not present": return "left"
|
case "not present": return "left"
|
||||||
default: return value
|
default: return value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
import physicalgraph.zigbee.clusters.iaszone.ZoneStatus
|
|
||||||
|
|
||||||
metadata {
|
metadata {
|
||||||
definition (name: "NYCE Motion Sensor", namespace: "smartthings", author: "SmartThings") {
|
definition (name: "NYCE Motion Sensor", namespace: "smartthings", author: "SmartThings") {
|
||||||
@@ -144,14 +143,51 @@ private Map parseReportAttributeMessage(String description) {
|
|||||||
|
|
||||||
|
|
||||||
private Map parseIasMessage(String description) {
|
private Map parseIasMessage(String description) {
|
||||||
ZoneStatus zs = zigbee.parseZoneStatus(description)
|
List parsedMsg = description.split(' ')
|
||||||
Map resultMap = [:]
|
String msgCode = parsedMsg[2]
|
||||||
|
|
||||||
|
Map resultMap = [:]
|
||||||
|
switch(msgCode) {
|
||||||
|
case '0x0030': // Closed/No Motion/Dry
|
||||||
|
log.debug 'no motion'
|
||||||
|
resultMap.name = 'motion'
|
||||||
|
resultMap.value = 'inactive'
|
||||||
|
break
|
||||||
|
|
||||||
resultMap.name = 'motion'
|
case '0x0032': // Open/Motion/Wet
|
||||||
resultMap.value = zs.isAlarm2Set() ? 'active' : 'inactive'
|
log.debug 'motion'
|
||||||
log.debug(zs.isAlarm2Set() ? 'motion' : 'no motion')
|
resultMap.name = 'motion'
|
||||||
|
resultMap.value = 'active'
|
||||||
|
break
|
||||||
|
|
||||||
return resultMap
|
case '0x0032': // Tamper Alarm
|
||||||
|
log.debug 'motion with tamper alarm'
|
||||||
|
resultMap.name = 'motion'
|
||||||
|
resultMap.value = 'active'
|
||||||
|
break
|
||||||
|
|
||||||
|
case '0x0033': // Battery Alarm
|
||||||
|
break
|
||||||
|
|
||||||
|
case '0x0034': // Supervision Report
|
||||||
|
log.debug 'no motion with tamper alarm'
|
||||||
|
resultMap.name = 'motion'
|
||||||
|
resultMap.value = 'inactive'
|
||||||
|
break
|
||||||
|
|
||||||
|
case '0x0035': // Restore Report
|
||||||
|
break
|
||||||
|
|
||||||
|
case '0x0036': // Trouble/Failure
|
||||||
|
log.debug 'motion with failure alarm'
|
||||||
|
resultMap.name = 'motion'
|
||||||
|
resultMap.value = 'active'
|
||||||
|
break
|
||||||
|
|
||||||
|
case '0x0038': // Test Mode
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return resultMap
|
||||||
}
|
}
|
||||||
|
|
||||||
def refresh()
|
def refresh()
|
||||||
|
|||||||
@@ -1,2 +0,0 @@
|
|||||||
.st-ignore
|
|
||||||
README.md
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
# Nyce Door/Window Sensor (Open/Close Sensor)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Works with:
|
|
||||||
|
|
||||||
* [NYCE Door/Window Sensor NCZ-3011](https://support.smartthings.com/hc/en-us/articles/204576764-NYCE-Door-Window-Sensor)
|
|
||||||
|
|
||||||
## Table of contents
|
|
||||||
|
|
||||||
* [Capabilities](#capabilities)
|
|
||||||
* [Health](#device-health)
|
|
||||||
* [Battery](#battery-specification)
|
|
||||||
* [Troubleshooting](#troubleshooting)
|
|
||||||
|
|
||||||
## Capabilities
|
|
||||||
|
|
||||||
* **Configuration** - _configure()_ command called when device is installed or device preferences updated
|
|
||||||
* **Contact Sensor** - can detect contact (with possible values - open/closed)
|
|
||||||
* **Battery** - defines device uses a battery
|
|
||||||
* **Refresh** - _refresh()_ command for status updates
|
|
||||||
* **Health Check** - indicates ability to get device health notifications
|
|
||||||
|
|
||||||
## Device Health
|
|
||||||
|
|
||||||
A Category C2 Nyce Door/Window sensor that has 12min check-in interval
|
|
||||||
|
|
||||||
## Battery Specification
|
|
||||||
|
|
||||||
One 3V CR2032 battery required.
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
If the device doesn't pair when trying from the SmartThings mobile app, it is possible that the sensor is out of range.
|
|
||||||
Pairing needs to be tried again by placing the sensor closer to the hub.
|
|
||||||
Instructions related to pairing, resetting and removing the sensor from SmartThings can be found in the following link:
|
|
||||||
* [Nyce Door/Window Sensor](https://support.smartthings.com/hc/en-us/articles/204576764-NYCE-Door-Window-Sensor)
|
|
||||||
@@ -13,32 +13,27 @@
|
|||||||
* for the specific language governing permissions and limitations under the License.
|
* for the specific language governing permissions and limitations under the License.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import physicalgraph.zigbee.clusters.iaszone.ZoneStatus
|
|
||||||
|
|
||||||
|
|
||||||
metadata {
|
metadata {
|
||||||
definition (name: "NYCE Open/Closed Sensor", namespace: "smartthings", author: "NYCE") {
|
definition (name: "NYCE Open/Closed Sensor", namespace: "smartthings", author: "NYCE") {
|
||||||
capability "Battery"
|
capability "Battery"
|
||||||
capability "Configuration"
|
capability "Configuration"
|
||||||
capability "Contact Sensor"
|
capability "Contact Sensor"
|
||||||
capability "Refresh"
|
capability "Refresh"
|
||||||
capability "Health Check"
|
|
||||||
|
command "enrollResponse"
|
||||||
command "enrollResponse"
|
|
||||||
|
|
||||||
|
|
||||||
fingerprint inClusters: "0000,0001,0003,0500,0020", manufacturer: "NYCE", model: "3010", deviceJoinName: "NYCE Door Hinge Sensor"
|
|
||||||
fingerprint inClusters: "0000,0001,0003,0406,0500,0020", manufacturer: "NYCE", model: "3011", deviceJoinName: "NYCE Door/Window Sensor"
|
fingerprint inClusters: "0000,0001,0003,0406,0500,0020", manufacturer: "NYCE", model: "3011", deviceJoinName: "NYCE Door/Window Sensor"
|
||||||
fingerprint inClusters: "0000,0001,0003,0500,0020", manufacturer: "NYCE", model: "3011", deviceJoinName: "NYCE Door/Window Sensor"
|
fingerprint inClusters: "0000,0001,0003,0500,0020", manufacturer: "NYCE", model: "3011", deviceJoinName: "NYCE Door/Window Sensor"
|
||||||
fingerprint inClusters: "0000,0001,0003,0406,0500,0020", manufacturer: "NYCE", model: "3014", deviceJoinName: "NYCE Tilt Sensor"
|
fingerprint inClusters: "0000,0001,0003,0406,0500,0020", manufacturer: "NYCE", model: "3014", deviceJoinName: "NYCE Tilt Sensor"
|
||||||
fingerprint inClusters: "0000,0001,0003,0500,0020", manufacturer: "NYCE", model: "3014", deviceJoinName: "NYCE Tilt Sensor"
|
fingerprint inClusters: "0000,0001,0003,0500,0020", manufacturer: "NYCE", model: "3014", deviceJoinName: "NYCE Tilt Sensor"
|
||||||
}
|
}
|
||||||
|
|
||||||
simulator {
|
simulator {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tiles {
|
tiles {
|
||||||
standardTile("contact", "device.contact", width: 2, height: 2) {
|
standardTile("contact", "device.contact", width: 2, height: 2) {
|
||||||
state("open", label:'${name}', icon:"st.contact.contact.open", backgroundColor:"#ffa81e")
|
state("open", label:'${name}', icon:"st.contact.contact.open", backgroundColor:"#ffa81e")
|
||||||
@@ -223,33 +218,40 @@ private Map parseReportAttributeMessage(String description) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private List parseIasMessage(String description) {
|
private List parseIasMessage(String description) {
|
||||||
ZoneStatus zs = zigbee.parseZoneStatus(description)
|
List parsedMsg = description.split(" ")
|
||||||
log.debug "parseIasMessage: $description"
|
String msgCode = parsedMsg[2]
|
||||||
|
|
||||||
List resultListMap = []
|
List resultListMap = []
|
||||||
Map resultMap_battery = [:]
|
Map resultMap_battery = [:]
|
||||||
Map resultMap_battery_state = [:]
|
Map resultMap_battery_state = [:]
|
||||||
Map resultMap_sensor = [:]
|
Map resultMap_sensor = [:]
|
||||||
|
|
||||||
resultMap_sensor.name = "contact"
|
// Relevant bit field definitions from ZigBee spec
|
||||||
resultMap_sensor.value = zs.isAlarm1Set() ? "open" : "closed"
|
def BATTERY_BIT = ( 1 << 3 )
|
||||||
|
def TROUBLE_BIT = ( 1 << 6 )
|
||||||
|
def SENSOR_BIT = ( 1 << 0 ) // it's ALARM1 bit from the ZCL spec
|
||||||
|
|
||||||
|
// Convert hex string to integer
|
||||||
|
def zoneStatus = Integer.parseInt(msgCode[-4..-1],16)
|
||||||
|
|
||||||
|
log.debug "parseIasMessage: zoneStatus: ${zoneStatus}"
|
||||||
|
|
||||||
// Check each relevant bit, create map for it, and add to list
|
// Check each relevant bit, create map for it, and add to list
|
||||||
log.debug "parseIasMessage: Battery Status ${zs.battery}"
|
log.debug "parseIasMessage: Battery Status ${zoneStatus & BATTERY_BIT}"
|
||||||
log.debug "parseIasMessage: Trouble Status ${zs.trouble}"
|
log.debug "parseIasMessage: Trouble Status ${zoneStatus & TROUBLE_BIT}"
|
||||||
log.debug "parseIasMessage: Sensor Status ${zs.alarm1}"
|
log.debug "parseIasMessage: Sensor Status ${zoneStatus & SENSOR_BIT}"
|
||||||
|
|
||||||
/* Comment out this path to check the battery state to avoid overwriting the
|
/* Comment out this path to check the battery state to avoid overwriting the
|
||||||
battery value (Change log #2), but keep these conditions for later use
|
battery value (Change log #2), but keep these conditions for later use
|
||||||
resultMap_battery_state.name = "battery_state"
|
resultMap_battery_state.name = "battery_state"
|
||||||
if (zs.isTroubleSet()) {
|
if (zoneStatus & TROUBLE_BIT) {
|
||||||
resultMap_battery_state.value = "failed"
|
resultMap_battery_state.value = "failed"
|
||||||
|
|
||||||
resultMap_battery.name = "battery"
|
resultMap_battery.name = "battery"
|
||||||
resultMap_battery.value = 0
|
resultMap_battery.value = 0
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if (zs.isBatterySet()) {
|
if (zoneStatus & BATTERY_BIT) {
|
||||||
resultMap_battery_state.value = "low"
|
resultMap_battery_state.value = "low"
|
||||||
|
|
||||||
// to generate low battery notification by the platform
|
// to generate low battery notification by the platform
|
||||||
@@ -267,6 +269,9 @@ private List parseIasMessage(String description) {
|
|||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
resultMap_sensor.name = "contact"
|
||||||
|
resultMap_sensor.value = (zoneStatus & SENSOR_BIT) ? "open" : "closed"
|
||||||
|
|
||||||
resultListMap << resultMap_battery_state
|
resultListMap << resultMap_battery_state
|
||||||
resultListMap << resultMap_battery
|
resultListMap << resultMap_battery
|
||||||
resultListMap << resultMap_sensor
|
resultListMap << resultMap_sensor
|
||||||
@@ -274,28 +279,23 @@ private List parseIasMessage(String description) {
|
|||||||
return resultListMap
|
return resultListMap
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* PING is used by Device-Watch in attempt to reach the Device
|
|
||||||
* */
|
|
||||||
def ping() {
|
|
||||||
return zigbee.readAttribute(0x001, 0x0020) // Read the Battery Level
|
|
||||||
}
|
|
||||||
|
|
||||||
def configure() {
|
def configure() {
|
||||||
// Device-Watch allows 2 check-in misses from device
|
|
||||||
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
|
|
||||||
|
|
||||||
String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
|
String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
|
||||||
|
|
||||||
def enrollCmds = [
|
def configCmds = [
|
||||||
|
//battery reporting and heartbeat
|
||||||
|
"zdo bind 0x${device.deviceNetworkId} 1 ${endpointId} 1 {${device.zigbeeId}} {}", "delay 200",
|
||||||
|
"zcl global send-me-a-report 1 0x20 0x20 600 3600 {01}", "delay 200",
|
||||||
|
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 1500",
|
||||||
|
|
||||||
|
|
||||||
// Writes CIE attribute on end device to direct reports to the hub's EUID
|
// Writes CIE attribute on end device to direct reports to the hub's EUID
|
||||||
"zcl global write 0x500 0x10 0xf0 {${zigbeeEui}}", "delay 200",
|
"zcl global write 0x500 0x10 0xf0 {${zigbeeEui}}", "delay 200",
|
||||||
"send 0x${device.deviceNetworkId} 1 1", "delay 500",
|
"send 0x${device.deviceNetworkId} 1 1", "delay 500",
|
||||||
]
|
]
|
||||||
|
|
||||||
log.debug "configure: Write IAS CIE"
|
log.debug "configure: Write IAS CIE"
|
||||||
// battery minReportTime 30 seconds, maxReportTime 5 min. Reporting interval if no activity
|
return configCmds
|
||||||
return enrollCmds + zigbee.batteryConfig(30, 300) + refresh() // send refresh cmds as part of config
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def enrollResponse() {
|
def enrollResponse() {
|
||||||
@@ -340,8 +340,7 @@ Integer convertHexToInt(hex) {
|
|||||||
|
|
||||||
def refresh() {
|
def refresh() {
|
||||||
log.debug "Refreshing Battery"
|
log.debug "Refreshing Battery"
|
||||||
def refreshCmds = [
|
[
|
||||||
"st rattr 0x${device.deviceNetworkId} ${endpointId} 1 0x20", "delay 200"
|
"st rattr 0x${device.deviceNetworkId} ${endpointId} 1 0x20", "delay 200"
|
||||||
]
|
]
|
||||||
return refreshCmds + enrollResponse()
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,9 @@ metadata {
|
|||||||
attribute "colorName", "string"
|
attribute "colorName", "string"
|
||||||
|
|
||||||
command "setAdjustedColor"
|
command "setAdjustedColor"
|
||||||
|
|
||||||
|
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "Gardenspot RGB"
|
||||||
|
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY Gardenspot RGB"
|
||||||
}
|
}
|
||||||
|
|
||||||
// simulator metadata
|
// simulator metadata
|
||||||
@@ -88,7 +91,7 @@ def parse(String description) {
|
|||||||
|
|
||||||
if (descMap.cluster == "0300") {
|
if (descMap.cluster == "0300") {
|
||||||
if(descMap.attrId == "0000"){ //Hue Attribute
|
if(descMap.attrId == "0000"){ //Hue Attribute
|
||||||
def hueValue = Math.round(convertHexToInt(descMap.value) / 255 * 100)
|
def hueValue = Math.round(convertHexToInt(descMap.value) / 255 * 360)
|
||||||
log.debug "Hue value returned is $hueValue"
|
log.debug "Hue value returned is $hueValue"
|
||||||
sendEvent(name: "hue", value: hueValue, displayed:false)
|
sendEvent(name: "hue", value: hueValue, displayed:false)
|
||||||
}
|
}
|
||||||
@@ -200,7 +203,7 @@ def setLevel(value) {
|
|||||||
|
|
||||||
//input Hue Integer values; returns color name for saturation 100%
|
//input Hue Integer values; returns color name for saturation 100%
|
||||||
private getColorName(hueValue){
|
private getColorName(hueValue){
|
||||||
if(hueValue>100 || hueValue<0)
|
if(hueValue>360 || hueValue<0)
|
||||||
return
|
return
|
||||||
|
|
||||||
hueValue = Math.round(hueValue / 100 * 360)
|
hueValue = Math.round(hueValue / 100 * 360)
|
||||||
|
|||||||
@@ -1,14 +1,13 @@
|
|||||||
/*
|
/*
|
||||||
Osram Flex RGBW Light Strip
|
Osram Flex RGBW Light Strip
|
||||||
|
|
||||||
Osram bulbs have a firmware issue causing it to forget its dimming level when turned off (via commands). Handling
|
Osram bulbs have a firmware issue causing it to forget its dimming level when turned off (via commands). Handling
|
||||||
that issue by using state variables
|
that issue by using state variables
|
||||||
*/
|
*/
|
||||||
//DEPRECATED - Using the generic DTH for this device. Users need to be moved before deleting this DTH
|
|
||||||
|
|
||||||
metadata {
|
metadata {
|
||||||
definition (name: "OSRAM LIGHTIFY LED Flexible Strip RGBW", namespace: "smartthings", author: "SmartThings") {
|
definition (name: "OSRAM LIGHTIFY LED Flexible Strip RGBW", namespace: "smartthings", author: "SmartThings") {
|
||||||
|
|
||||||
capability "Color Temperature"
|
capability "Color Temperature"
|
||||||
capability "Actuator"
|
capability "Actuator"
|
||||||
capability "Switch"
|
capability "Switch"
|
||||||
@@ -18,14 +17,14 @@ metadata {
|
|||||||
capability "Refresh"
|
capability "Refresh"
|
||||||
capability "Sensor"
|
capability "Sensor"
|
||||||
capability "Color Control"
|
capability "Color Control"
|
||||||
|
|
||||||
attribute "colorName", "string"
|
attribute "colorName", "string"
|
||||||
|
|
||||||
command "setAdjustedColor"
|
command "setAdjustedColor"
|
||||||
|
|
||||||
|
|
||||||
//fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY Flex RGBW"
|
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY Flex RGBW"
|
||||||
//fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "Flex RGBW"
|
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "Flex RGBW"
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,7 +48,7 @@ metadata {
|
|||||||
standardTile("refresh", "device.switch", 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("colorTempSliderControl", "device.colorTemperature", "slider", height: 1, width: 2, inactiveLabel: false, range:"(2700..6500)") {
|
controlTile("colorTempSliderControl", "device.colorTemperature", "slider", height: 1, width: 2, inactiveLabel: false, range:"(2700..6500)") {
|
||||||
state "colorTemperature", action:"color temperature.setColorTemperature"
|
state "colorTemperature", action:"color temperature.setColorTemperature"
|
||||||
}
|
}
|
||||||
@@ -118,7 +117,7 @@ def parse(String description) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if(descMap.attrId == "0000"){ //Hue Attribute
|
else if(descMap.attrId == "0000"){ //Hue Attribute
|
||||||
def hueValue = Math.round(convertHexToInt(descMap.value) / 255 * 100)
|
def hueValue = Math.round(convertHexToInt(descMap.value) / 255 * 360)
|
||||||
log.debug "Hue value returned is $hueValue"
|
log.debug "Hue value returned is $hueValue"
|
||||||
sendEvent(name: "hue", value: hueValue, displayed:false)
|
sendEvent(name: "hue", value: hueValue, displayed:false)
|
||||||
}
|
}
|
||||||
@@ -274,7 +273,7 @@ private getGenericName(value){
|
|||||||
|
|
||||||
//input Hue Integer values; returns color name for saturation 100%
|
//input Hue Integer values; returns color name for saturation 100%
|
||||||
private getColorName(hueValue){
|
private getColorName(hueValue){
|
||||||
if(hueValue>100 || hueValue<0)
|
if(hueValue>360 || hueValue<0)
|
||||||
return
|
return
|
||||||
|
|
||||||
hueValue = Math.round(hueValue / 100 * 360)
|
hueValue = Math.round(hueValue / 100 * 360)
|
||||||
@@ -449,7 +448,7 @@ def setColor(value){
|
|||||||
def level = hex(value.level * 255 / 100)
|
def level = hex(value.level * 255 / 100)
|
||||||
cmd << zigbeeSetLevel(level)
|
cmd << zigbeeSetLevel(level)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (value.switch == "off") {
|
if (value.switch == "off") {
|
||||||
cmd << "delay 150"
|
cmd << "delay 150"
|
||||||
cmd << off()
|
cmd << off()
|
||||||
|
|||||||
@@ -19,6 +19,11 @@ metadata {
|
|||||||
capability "Sensor"
|
capability "Sensor"
|
||||||
|
|
||||||
attribute "colorName", "string"
|
attribute "colorName", "string"
|
||||||
|
|
||||||
|
// indicates that device keeps track of heartbeat (in state.heartbeat)
|
||||||
|
attribute "heartbeat", "string"
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// simulator metadata
|
// simulator metadata
|
||||||
@@ -70,6 +75,9 @@ metadata {
|
|||||||
def parse(String description) {
|
def parse(String description) {
|
||||||
//log.trace description
|
//log.trace description
|
||||||
|
|
||||||
|
// save heartbeat (i.e. last time we got a message from device)
|
||||||
|
state.heartbeat = Calendar.getInstance().getTimeInMillis()
|
||||||
|
|
||||||
if (description?.startsWith("catchall:")) {
|
if (description?.startsWith("catchall:")) {
|
||||||
if(description?.endsWith("0100") ||description?.endsWith("1001") || description?.matches("on/off\\s*:\\s*1"))
|
if(description?.endsWith("0100") ||description?.endsWith("1001") || description?.matches("on/off\\s*:\\s*1"))
|
||||||
{
|
{
|
||||||
@@ -124,6 +132,7 @@ def off() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
def refresh() {
|
def refresh() {
|
||||||
|
sendEvent(name: "heartbeat", value: "alive", displayed:false)
|
||||||
[
|
[
|
||||||
"st rattr 0x${device.deviceNetworkId} ${endpointId} 6 0", "delay 500",
|
"st rattr 0x${device.deviceNetworkId} ${endpointId} 6 0", "delay 500",
|
||||||
"st rattr 0x${device.deviceNetworkId} ${endpointId} 8 0", "delay 500",
|
"st rattr 0x${device.deviceNetworkId} ${endpointId} 8 0", "delay 500",
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
@@ -133,7 +133,7 @@ def refresh() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
def configure() {
|
def configure() {
|
||||||
refresh() + onOffConfig() + levelConfig() + powerConfig()
|
onOffConfig() + levelConfig() + powerConfig() + refresh()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ metadata {
|
|||||||
capability "Switch"
|
capability "Switch"
|
||||||
capability "Sensor"
|
capability "Sensor"
|
||||||
|
|
||||||
fingerprint profileId: "0104", inClusters: "0006, 0004, 0003, 0000, 0005", outClusters: "0019", manufacturer: "Compacta International, Ltd", model: "ZBMPlug15", deviceJoinName: "SmartPower Outlet V1"
|
fingerprint profileId: "0104", inClusters: "0000,0003,0006", outClusters: "0019"
|
||||||
}
|
}
|
||||||
|
|
||||||
// simulator metadata
|
// simulator metadata
|
||||||
@@ -47,21 +47,9 @@ def parse(String description) {
|
|||||||
|
|
||||||
// Commands to device
|
// Commands to device
|
||||||
def on() {
|
def on() {
|
||||||
[
|
'zcl on-off on'
|
||||||
'zcl on-off on',
|
|
||||||
'delay 200',
|
|
||||||
"send 0x${zigbee.deviceNetworkId} 0x01 0x${zigbee.endpointId}",
|
|
||||||
'delay 500'
|
|
||||||
|
|
||||||
]
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def off() {
|
def off() {
|
||||||
[
|
'zcl on-off off'
|
||||||
'zcl on-off off',
|
|
||||||
'delay 200',
|
|
||||||
"send 0x${zigbee.deviceNetworkId} 0x01 0x${zigbee.endpointId}",
|
|
||||||
'delay 500'
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,2 +0,0 @@
|
|||||||
.st-ignore
|
|
||||||
README.md
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
# SmartPower Outlet
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Works with:
|
|
||||||
|
|
||||||
* [Samsung SmartPower Outlet](https://shop.smartthings.com/#!/products/smartpower-outlet)
|
|
||||||
|
|
||||||
## Table of contents
|
|
||||||
|
|
||||||
* [Capabilities](#capabilities)
|
|
||||||
* [Health](#device-health)
|
|
||||||
|
|
||||||
## Capabilities
|
|
||||||
|
|
||||||
* **Configuration** - _configure()_ command called when device is installed or device preferences updated
|
|
||||||
* **Actuator** - represents that a Device has commands
|
|
||||||
* **Switch** - can detect state (possible values: on/off)
|
|
||||||
* **Refresh** - _refresh()_ command for status updates
|
|
||||||
* **Power Meter** - detects power meter for device in either w or kw.
|
|
||||||
* **Health Check** - indicates ability to get device health notifications
|
|
||||||
* **Sensor** - detects sensor events
|
|
||||||
|
|
||||||
## Device Health
|
|
||||||
|
|
||||||
A Category C1 smart power outlet with maxReportTime of 5 mins.
|
|
||||||
Check-in interval is double the value of maxReportTime.
|
|
||||||
This gives the device twice the amount of time to respond before it is marked as offline.
|
|
||||||
Check-in interval = 12 mins
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
If the device doesn't pair when trying from the SmartThings mobile app, it is possible that the device is out of range.
|
|
||||||
Pairing needs to be tried again by placing the device closer to the hub.
|
|
||||||
Instructions related to pairing, resetting and removing the device from SmartThings can be found in the following links
|
|
||||||
for the different models:
|
|
||||||
* [SmartPower Outlet Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/201084854-SmartPower-Outlet)
|
|
||||||
* [Samsung SmartThings Outlet Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/205957620)
|
|
||||||
@@ -1,26 +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.
|
|
||||||
# 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 }}와트입니다.
|
|
||||||
'''On'''.ko= 켜짐
|
|
||||||
'''Off'''.ko=꺼짐
|
|
||||||
'''Turning On'''.ko=켜는 중
|
|
||||||
'''Turning Off'''.ko=끄는 중
|
|
||||||
#==============================================================================
|
|
||||||
@@ -1,19 +1,20 @@
|
|||||||
/*
|
/**
|
||||||
* Copyright 2016 SmartThings
|
* Copyright 2015 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.
|
* SmartPower Outlet (CentraLite)
|
||||||
|
*
|
||||||
|
* Author: SmartThings
|
||||||
|
* Date: 2015-08-23
|
||||||
*/
|
*/
|
||||||
|
|
||||||
metadata {
|
metadata {
|
||||||
// Automatically generated. Make future change here.
|
// Automatically generated. Make future change here.
|
||||||
definition (name: "SmartPower Outlet", namespace: "smartthings", author: "SmartThings") {
|
definition (name: "SmartPower Outlet", namespace: "smartthings", author: "SmartThings") {
|
||||||
@@ -23,7 +24,9 @@ metadata {
|
|||||||
capability "Configuration"
|
capability "Configuration"
|
||||||
capability "Refresh"
|
capability "Refresh"
|
||||||
capability "Sensor"
|
capability "Sensor"
|
||||||
capability "Health Check"
|
|
||||||
|
// indicates that device keeps track of heartbeat (in state.heartbeat)
|
||||||
|
attribute "heartbeat", "string"
|
||||||
|
|
||||||
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0B04,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3200", deviceJoinName: "Outlet"
|
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0B04,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3200", deviceJoinName: "Outlet"
|
||||||
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0B04,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3200-Sgb", deviceJoinName: "Outlet"
|
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0B04,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3200-Sgb", deviceJoinName: "Outlet"
|
||||||
@@ -55,10 +58,10 @@ metadata {
|
|||||||
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") {
|
||||||
attributeState "on", label: 'On', action: "switch.off", icon: "st.switches.switch.on", backgroundColor: "#79b821", nextState: "turningOff"
|
attributeState "on", label: '${name}', action: "switch.off", icon: "st.switches.switch.on", backgroundColor: "#79b821", nextState: "turningOff"
|
||||||
attributeState "off", label: 'Off', action: "switch.on", icon: "st.switches.switch.off", backgroundColor: "#ffffff", nextState: "turningOn"
|
attributeState "off", label: '${name}', action: "switch.on", icon: "st.switches.switch.off", backgroundColor: "#ffffff", nextState: "turningOn"
|
||||||
attributeState "turningOn", label: 'Turning On', action: "switch.off", icon: "st.switches.switch.on", backgroundColor: "#79b821", nextState: "turningOff"
|
attributeState "turningOn", label: '${name}', action: "switch.off", icon: "st.switches.switch.on", backgroundColor: "#79b821", nextState: "turningOff"
|
||||||
attributeState "turningOff", label: 'Turning Off', action: "switch.on", icon: "st.switches.switch.off", backgroundColor: "#ffffff", nextState: "turningOn"
|
attributeState "turningOff", label: '${name}', action: "switch.on", icon: "st.switches.switch.off", backgroundColor: "#ffffff", nextState: "turningOn"
|
||||||
}
|
}
|
||||||
tileAttribute ("power", key: "SECONDARY_CONTROL") {
|
tileAttribute ("power", key: "SECONDARY_CONTROL") {
|
||||||
attributeState "power", label:'${currentValue} W'
|
attributeState "power", label:'${currentValue} W'
|
||||||
@@ -78,6 +81,9 @@ metadata {
|
|||||||
def parse(String description) {
|
def parse(String description) {
|
||||||
log.debug "description is $description"
|
log.debug "description is $description"
|
||||||
|
|
||||||
|
// save heartbeat (i.e. last time we got a message from device)
|
||||||
|
state.heartbeat = Calendar.getInstance().getTimeInMillis()
|
||||||
|
|
||||||
def finalResult = zigbee.getKnownDescription(description)
|
def finalResult = zigbee.getKnownDescription(description)
|
||||||
|
|
||||||
//TODO: Remove this after getKnownDescription can parse it automatically
|
//TODO: Remove this after getKnownDescription can parse it automatically
|
||||||
@@ -85,40 +91,27 @@ 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 {
|
||||||
def cluster = zigbee.parse(description)
|
log.warn "DID NOT PARSE MESSAGE for description : $description"
|
||||||
|
log.debug zigbee.parseDescriptionAsMap(description)
|
||||||
if (cluster && cluster.clusterId == 0x0006 && cluster.command == 0x07){
|
|
||||||
if (cluster.data[0] == 0x00) {
|
|
||||||
log.debug "ON/OFF REPORTING CONFIG RESPONSE: " + cluster
|
|
||||||
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
log.warn "ON/OFF REPORTING CONFIG FAILED- error code:${cluster.data[0]}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
log.warn "DID NOT PARSE MESSAGE for description : $description"
|
|
||||||
log.debug "${cluster}"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,24 +122,14 @@ def off() {
|
|||||||
def on() {
|
def on() {
|
||||||
zigbee.on()
|
zigbee.on()
|
||||||
}
|
}
|
||||||
/**
|
|
||||||
* PING is used by Device-Watch in attempt to reach the Device
|
|
||||||
* */
|
|
||||||
def ping() {
|
|
||||||
return zigbee.onOffRefresh()
|
|
||||||
}
|
|
||||||
|
|
||||||
def refresh() {
|
def refresh() {
|
||||||
zigbee.onOffRefresh() + zigbee.electricMeasurementPowerRefresh()
|
sendEvent(name: "heartbeat", value: "alive", displayed:false)
|
||||||
|
zigbee.onOffRefresh() + zigbee.refreshData("0x0B04", "0x050B")
|
||||||
}
|
}
|
||||||
|
|
||||||
def configure() {
|
def configure() {
|
||||||
// Device-Watch allows 3 check-in misses from device (plus 1 min lag time)
|
zigbee.onOffConfig() + powerConfig() + refresh()
|
||||||
// enrolls with default periodic reporting until newer 5 min interval is confirmed
|
|
||||||
sendEvent(name: "checkInterval", value: 3 * 60 * 60 + 1 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
|
|
||||||
|
|
||||||
// OnOff minReportTime 0 seconds, maxReportTime 5 min. Reporting interval if no activity
|
|
||||||
refresh() + zigbee.onOffConfig(0, 300) + powerConfig()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//power config for devices with min reporting interval as 1 seconds and reporting interval if no activity as 10min (600s)
|
//power config for devices with min reporting interval as 1 seconds and reporting interval if no activity as 10min (600s)
|
||||||
|
|||||||
@@ -79,8 +79,8 @@ metadata {
|
|||||||
}
|
}
|
||||||
|
|
||||||
preferences {
|
preferences {
|
||||||
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 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: "Temperature Offset", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -88,8 +88,8 @@ metadata {
|
|||||||
}
|
}
|
||||||
|
|
||||||
preferences {
|
preferences {
|
||||||
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 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: "Temperature Offset", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,2 +0,0 @@
|
|||||||
.st-ignore
|
|
||||||
README.md
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
# Smartsense Moisture Sensor
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Works with:
|
|
||||||
|
|
||||||
* [Samsung SmartThings Moisture Sensor](https://shop.smartthings.com/#!/products/samsung-smartthings-water-leak-sensor)
|
|
||||||
|
|
||||||
## Table of contents
|
|
||||||
|
|
||||||
* [Capabilities](#capabilities)
|
|
||||||
* [Health](#device-health)
|
|
||||||
* [Battery](#battery-specification)
|
|
||||||
|
|
||||||
## Capabilities
|
|
||||||
|
|
||||||
* **Configuration** - _configure()_ command called when device is installed or device preferences updated
|
|
||||||
* **Battery** - defines device uses a battery
|
|
||||||
* **Refresh** - _refresh()_ command for status updates
|
|
||||||
* **Temperature Measurement** - defines device measures current temperature
|
|
||||||
* **Water Sensor** - can detect presence of water (dry or wet)
|
|
||||||
* **Health Check** - indicates ability to get device health notifications
|
|
||||||
|
|
||||||
## Device Health
|
|
||||||
|
|
||||||
A Category C2 moisture sensor with maxReportTime of 5 mins.
|
|
||||||
Check-in interval is double the value of maxReportTime.
|
|
||||||
This gives the device twice the amount of time to respond before it is marked as offline.
|
|
||||||
Check-in interval = 12 mins
|
|
||||||
|
|
||||||
## Battery Specification
|
|
||||||
|
|
||||||
One CR2 3V battery required.
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
If the sensor doesn't pair when trying from the SmartThings mobile app, it is possible that the sensor is out of range.
|
|
||||||
Pairing needs to be tried again by placing the sensor closer to the hub.
|
|
||||||
Instructions related to pairing, resetting and removing the different sensors from SmartThings can be found in the following links
|
|
||||||
for the different models:
|
|
||||||
* [SmartSense Moisture Sensor Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/202847044-SmartSense-Moisture-Sensor)
|
|
||||||
* [Samsung SmartThings Water Leak Sensor Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/205957630)
|
|
||||||
Other troubleshooting tips are listed as follows:
|
|
||||||
* [Troubleshooting: Samsung SmartThings Water Leak Sensor won’t pair after removing pull-tab](https://support.smartthings.com/hc/en-us/articles/204966616-Troubleshooting-Samsung-SmartThings-device-won-t-pair-after-removing-pull-tab)
|
|
||||||
@@ -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.
|
|
||||||
# Korean (ko)
|
|
||||||
# Device Preferences
|
|
||||||
'''Dry'''.ko=건조
|
|
||||||
'''Wet'''.ko=누수
|
|
||||||
'''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=누수감지 센서
|
|
||||||
'''${currentValue}% battery'''.ko=${currentValue}% 배터리
|
|
||||||
# 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,21 +1,18 @@
|
|||||||
/*
|
/**
|
||||||
* Copyright 2016 SmartThings
|
* SmartSense Moisture Sensor
|
||||||
*
|
*
|
||||||
* 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.
|
|
||||||
*/
|
*/
|
||||||
import physicalgraph.zigbee.clusters.iaszone.ZoneStatus
|
|
||||||
|
|
||||||
|
|
||||||
metadata {
|
metadata {
|
||||||
definition (name: "SmartSense Moisture Sensor",namespace: "smartthings", author: "SmartThings") {
|
definition (name: "SmartSense Moisture Sensor",namespace: "smartthings", author: "SmartThings") {
|
||||||
capability "Configuration"
|
capability "Configuration"
|
||||||
@@ -23,20 +20,18 @@ metadata {
|
|||||||
capability "Refresh"
|
capability "Refresh"
|
||||||
capability "Temperature Measurement"
|
capability "Temperature Measurement"
|
||||||
capability "Water Sensor"
|
capability "Water Sensor"
|
||||||
capability "Health Check"
|
|
||||||
capability "Sensor"
|
command "enrollResponse"
|
||||||
|
|
||||||
command "enrollResponse"
|
|
||||||
|
|
||||||
|
|
||||||
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3315-S", deviceJoinName: "Water Leak Sensor"
|
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3315-S", deviceJoinName: "Water Leak Sensor"
|
||||||
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3315"
|
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3315"
|
||||||
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3315-Seu", deviceJoinName: "Water Leak Sensor"
|
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3315-Seu", deviceJoinName: "Water Leak Sensor"
|
||||||
fingerprint inClusters: "0000,0001,0003,000F,0020,0402,0500", outClusters: "0019", manufacturer: "SmartThings", model: "moisturev4", deviceJoinName: "Water Leak Sensor"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
simulator {
|
simulator {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
preferences {
|
preferences {
|
||||||
@@ -48,11 +43,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 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: "Temperature Offset", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tiles(scale: 2) {
|
tiles(scale: 2) {
|
||||||
multiAttributeTile(name:"water", type: "generic", width: 6, height: 4){
|
multiAttributeTile(name:"water", type: "generic", width: 6, height: 4){
|
||||||
tileAttribute ("device.water", key: "PRIMARY_CONTROL") {
|
tileAttribute ("device.water", key: "PRIMARY_CONTROL") {
|
||||||
@@ -83,7 +78,7 @@ metadata {
|
|||||||
details(["water", "temperature", "battery", "refresh"])
|
details(["water", "temperature", "battery", "refresh"])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def parse(String description) {
|
def parse(String description) {
|
||||||
log.debug "description: $description"
|
log.debug "description: $description"
|
||||||
|
|
||||||
@@ -97,49 +92,35 @@ def parse(String description) {
|
|||||||
else if (description?.startsWith('temperature: ')) {
|
else if (description?.startsWith('temperature: ')) {
|
||||||
map = parseCustomMessage(description)
|
map = parseCustomMessage(description)
|
||||||
}
|
}
|
||||||
else if (description?.startsWith('zone status')) {
|
else if (description?.startsWith('zone status')) {
|
||||||
map = parseIasMessage(description)
|
map = parseIasMessage(description)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.debug "Parse returned $map"
|
log.debug "Parse returned $map"
|
||||||
def result = map ? createEvent(map) : null
|
def result = map ? createEvent(map) : null
|
||||||
|
|
||||||
if (description?.startsWith('enroll request')) {
|
if (description?.startsWith('enroll request')) {
|
||||||
List cmds = enrollResponse()
|
List cmds = enrollResponse()
|
||||||
log.debug "enroll response: ${cmds}"
|
log.debug "enroll response: ${cmds}"
|
||||||
result = cmds?.collect { new physicalgraph.device.HubAction(it) }
|
result = cmds?.collect { new physicalgraph.device.HubAction(it) }
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
private Map parseCatchAllMessage(String description) {
|
private Map parseCatchAllMessage(String description) {
|
||||||
Map resultMap = [:]
|
Map resultMap = [:]
|
||||||
def cluster = zigbee.parse(description)
|
def cluster = zigbee.parse(description)
|
||||||
if (shouldProcessMessage(cluster)) {
|
if (shouldProcessMessage(cluster)) {
|
||||||
switch(cluster.clusterId) {
|
switch(cluster.clusterId) {
|
||||||
case 0x0001:
|
case 0x0001:
|
||||||
// 0x07 - configure reporting
|
resultMap = getBatteryResult(cluster.data.last())
|
||||||
if (cluster.command != 0x07) {
|
break
|
||||||
resultMap = getBatteryResult(cluster.data.last())
|
|
||||||
}
|
|
||||||
break
|
|
||||||
|
|
||||||
case 0x0402:
|
case 0x0402:
|
||||||
if (cluster.command == 0x07) {
|
// temp is last 2 data values. reverse to swap endian
|
||||||
if (cluster.data[0] == 0x00){
|
String temp = cluster.data[-2..-1].reverse().collect { cluster.hex1(it) }.join()
|
||||||
log.debug "TEMP REPORTING CONFIG RESPONSE" + cluster
|
def value = getTemperature(temp)
|
||||||
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
|
resultMap = getTemperatureResult(value)
|
||||||
}
|
|
||||||
else {
|
|
||||||
log.warn "TEMP REPORTING CONFIG FAILED- error code:${cluster.data[0]}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// temp is last 2 data values. reverse to swap endian
|
|
||||||
String temp = cluster.data[-2..-1].reverse().collect { cluster.hex1(it) }.join()
|
|
||||||
def value = getTemperature(temp)
|
|
||||||
resultMap = getTemperatureResult(value)
|
|
||||||
}
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -148,20 +129,22 @@ private Map parseCatchAllMessage(String description) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private boolean shouldProcessMessage(cluster) {
|
private boolean shouldProcessMessage(cluster) {
|
||||||
// 0x0B is default response indicating message got through
|
// 0x0B is default response indicating message got through
|
||||||
boolean ignoredMessage = cluster.profileId != 0x0104 ||
|
// 0x07 is bind message
|
||||||
cluster.command == 0x0B ||
|
boolean ignoredMessage = cluster.profileId != 0x0104 ||
|
||||||
(cluster.data.size() > 0 && cluster.data.first() == 0x3e)
|
cluster.command == 0x0B ||
|
||||||
return !ignoredMessage
|
cluster.command == 0x07 ||
|
||||||
|
(cluster.data.size() > 0 && cluster.data.first() == 0x3e)
|
||||||
|
return !ignoredMessage
|
||||||
}
|
}
|
||||||
|
|
||||||
private Map parseReportAttributeMessage(String description) {
|
private Map parseReportAttributeMessage(String description) {
|
||||||
Map descMap = (description - "read attr - ").split(",").inject([:]) { map, param ->
|
Map descMap = (description - "read attr - ").split(",").inject([:]) { map, param ->
|
||||||
def nameAndValue = param.split(":")
|
def nameAndValue = param.split(":")
|
||||||
map += [(nameAndValue[0].trim()):nameAndValue[1].trim()]
|
map += [(nameAndValue[0].trim()):nameAndValue[1].trim()]
|
||||||
}
|
}
|
||||||
log.debug "Desc Map: $descMap"
|
log.debug "Desc Map: $descMap"
|
||||||
|
|
||||||
Map resultMap = [:]
|
Map resultMap = [:]
|
||||||
if (descMap.cluster == "0402" && descMap.attrId == "0000") {
|
if (descMap.cluster == "0402" && descMap.attrId == "0000") {
|
||||||
def value = getTemperature(descMap.value)
|
def value = getTemperature(descMap.value)
|
||||||
@@ -170,10 +153,10 @@ private Map parseReportAttributeMessage(String description) {
|
|||||||
else if (descMap.cluster == "0001" && descMap.attrId == "0020") {
|
else if (descMap.cluster == "0001" && descMap.attrId == "0020") {
|
||||||
resultMap = getBatteryResult(Integer.parseInt(descMap.value, 16))
|
resultMap = getBatteryResult(Integer.parseInt(descMap.value, 16))
|
||||||
}
|
}
|
||||||
|
|
||||||
return resultMap
|
return resultMap
|
||||||
}
|
}
|
||||||
|
|
||||||
private Map parseCustomMessage(String description) {
|
private Map parseCustomMessage(String description) {
|
||||||
Map resultMap = [:]
|
Map resultMap = [:]
|
||||||
if (description?.startsWith('temperature: ')) {
|
if (description?.startsWith('temperature: ')) {
|
||||||
@@ -184,66 +167,72 @@ private Map parseCustomMessage(String description) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Map parseIasMessage(String description) {
|
private Map parseIasMessage(String description) {
|
||||||
ZoneStatus zs = zigbee.parseZoneStatus(description)
|
List parsedMsg = description.split(' ')
|
||||||
|
String msgCode = parsedMsg[2]
|
||||||
|
|
||||||
|
Map resultMap = [:]
|
||||||
|
switch(msgCode) {
|
||||||
|
case '0x0020': // Closed/No Motion/Dry
|
||||||
|
resultMap = getMoistureResult('dry')
|
||||||
|
break
|
||||||
|
|
||||||
return zs.isAlarm1Set() ? getMoistureResult('wet') : getMoistureResult('dry')
|
case '0x0021': // Open/Motion/Wet
|
||||||
|
resultMap = getMoistureResult('wet')
|
||||||
|
break
|
||||||
|
|
||||||
|
case '0x0022': // Tamper Alarm
|
||||||
|
break
|
||||||
|
|
||||||
|
case '0x0023': // Battery Alarm
|
||||||
|
break
|
||||||
|
|
||||||
|
case '0x0024': // Supervision Report
|
||||||
|
log.debug 'dry with tamper alarm'
|
||||||
|
resultMap = getMoistureResult('dry')
|
||||||
|
break
|
||||||
|
|
||||||
|
case '0x0025': // Restore Report
|
||||||
|
log.debug 'water with tamper alarm'
|
||||||
|
resultMap = getMoistureResult('wet')
|
||||||
|
break
|
||||||
|
|
||||||
|
case '0x0026': // Trouble/Failure
|
||||||
|
break
|
||||||
|
|
||||||
|
case '0x0028': // Test Mode
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return resultMap
|
||||||
}
|
}
|
||||||
|
|
||||||
def getTemperature(value) {
|
def getTemperature(value) {
|
||||||
def celsius = Integer.parseInt(value, 16).shortValue() / 100
|
def celsius = Integer.parseInt(value, 16).shortValue() / 100
|
||||||
if(getTemperatureScale() == "C"){
|
if(getTemperatureScale() == "C"){
|
||||||
return Math.round(celsius)
|
return celsius
|
||||||
} else {
|
} else {
|
||||||
return Math.round(celsiusToFahrenheit(celsius))
|
return celsiusToFahrenheit(celsius) as Integer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Map getBatteryResult(rawValue) {
|
private Map getBatteryResult(rawValue) {
|
||||||
log.debug "Battery rawValue = ${rawValue}"
|
log.debug 'Battery'
|
||||||
def linkText = getLinkText(device)
|
def linkText = getLinkText(device)
|
||||||
|
|
||||||
def result = [
|
def result = [
|
||||||
name: 'battery',
|
name: 'battery'
|
||||||
value: '--',
|
]
|
||||||
translatable: true
|
|
||||||
]
|
|
||||||
|
|
||||||
def volts = rawValue / 10
|
def volts = rawValue / 10
|
||||||
|
def descriptionText
|
||||||
if (rawValue == 0 || rawValue == 255) {}
|
if (volts > 3.5) {
|
||||||
|
result.descriptionText = "${linkText} battery has too much power (${volts} volts)."
|
||||||
|
}
|
||||||
else {
|
else {
|
||||||
if (volts > 3.5) {
|
def minVolts = 2.1
|
||||||
result.descriptionText = "{{ device.displayName }} battery has too much power: (> 3.5) volts."
|
def maxVolts = 3.0
|
||||||
}
|
def pct = (volts - minVolts) / (maxVolts - minVolts)
|
||||||
else {
|
result.value = Math.min(100, (int) pct * 100)
|
||||||
if (device.getDataValue("manufacturer") == "SmartThings") {
|
result.descriptionText = "${linkText} battery was ${result.value}%"
|
||||||
volts = rawValue // For the batteryMap to work the key needs to be an int
|
|
||||||
def batteryMap = [28:100, 27:100, 26:100, 25:90, 24:90, 23:70,
|
|
||||||
22:70, 21:50, 20:50, 19:30, 18:30, 17:15, 16:1, 15:0]
|
|
||||||
def minVolts = 15
|
|
||||||
def maxVolts = 28
|
|
||||||
|
|
||||||
if (volts < minVolts)
|
|
||||||
volts = minVolts
|
|
||||||
else if (volts > maxVolts)
|
|
||||||
volts = maxVolts
|
|
||||||
def pct = batteryMap[volts]
|
|
||||||
if (pct != null) {
|
|
||||||
result.value = pct
|
|
||||||
result.descriptionText = "{{ device.displayName }} battery was {{ value }}%"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
def minVolts = 2.1
|
|
||||||
def maxVolts = 3.0
|
|
||||||
def pct = (volts - minVolts) / (maxVolts - minVolts)
|
|
||||||
def roundedPct = Math.round(pct * 100)
|
|
||||||
if (roundedPct <= 0)
|
|
||||||
roundedPct = 1
|
|
||||||
result.value = Math.min(100, roundedPct)
|
|
||||||
result.descriptionText = "{{ device.displayName }} battery was {{ value }}%"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return result
|
return result
|
||||||
@@ -251,52 +240,34 @@ 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,
|
|
||||||
unit: temperatureScale
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* PING is used by Device-Watch in attempt to reach the Device
|
|
||||||
* */
|
|
||||||
def ping() {
|
|
||||||
return zigbee.readAttribute(0x001, 0x0020) // Read the Battery Level
|
|
||||||
}
|
|
||||||
|
|
||||||
def refresh() {
|
def refresh() {
|
||||||
log.debug "Refreshing Temperature and Battery"
|
log.debug "Refreshing Temperature and Battery"
|
||||||
def refreshCmds = [
|
def refreshCmds = [
|
||||||
"st rattr 0x${device.deviceNetworkId} 1 0x402 0", "delay 200",
|
"st rattr 0x${device.deviceNetworkId} 1 0x402 0", "delay 200",
|
||||||
"st rattr 0x${device.deviceNetworkId} 1 1 0x20", "delay 200"
|
"st rattr 0x${device.deviceNetworkId} 1 1 0x20", "delay 200"
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -304,26 +275,34 @@ def refresh() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
def configure() {
|
def configure() {
|
||||||
// Device-Watch allows 3 check-in misses from device (plus 1 min lag time)
|
String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
|
||||||
// enrolls with default periodic reporting until newer 5 min interval is confirmed
|
log.debug "Configuring Reporting, IAS CIE, and Bindings."
|
||||||
sendEvent(name: "checkInterval", value: 3 * 60 * 60 + 1 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
|
def configCmds = [
|
||||||
|
"zcl global write 0x500 0x10 0xf0 {${zigbeeEui}}", "delay 200",
|
||||||
|
"send 0x${device.deviceNetworkId} 1 1", "delay 500",
|
||||||
|
|
||||||
// temperature minReportTime 30 seconds, maxReportTime 5 min. Reporting interval if no activity
|
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 1 {${device.zigbeeId}} {}", "delay 500",
|
||||||
// battery minReport 30 seconds, maxReportTime 6 hrs by default
|
"zcl global send-me-a-report 1 0x20 0x20 30 21600 {01}", //checkin time 6 hrs
|
||||||
return refresh() + zigbee.batteryConfig() + zigbee.temperatureConfig(30, 300) // send refresh cmds as part of config
|
"send 0x${device.deviceNetworkId} 1 1", "delay 500",
|
||||||
|
|
||||||
|
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 0x402 {${device.zigbeeId}} {}", "delay 500",
|
||||||
|
"zcl global send-me-a-report 0x402 0 0x29 30 3600 {6400}",
|
||||||
|
"send 0x${device.deviceNetworkId} 1 1", "delay 500"
|
||||||
|
]
|
||||||
|
return configCmds + refresh() // send refresh cmds as part of config
|
||||||
}
|
}
|
||||||
|
|
||||||
def enrollResponse() {
|
def enrollResponse() {
|
||||||
log.debug "Sending enroll response"
|
log.debug "Sending enroll response"
|
||||||
String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
|
String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
|
||||||
[
|
[
|
||||||
//Resending the CIE in case the enroll request is sent before CIE is written
|
//Resending the CIE in case the enroll request is sent before CIE is written
|
||||||
"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}",
|
"raw 0x500 {01 23 00 00 00}",
|
||||||
"send 0x${device.deviceNetworkId} 1 1", "delay 200"
|
"send 0x${device.deviceNetworkId} 1 1", "delay 200"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
private getEndpointId() {
|
private getEndpointId() {
|
||||||
@@ -335,19 +314,19 @@ private hex(value) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private String swapEndianHex(String hex) {
|
private String swapEndianHex(String hex) {
|
||||||
reverseArray(hex.decodeHex()).encodeHex()
|
reverseArray(hex.decodeHex()).encodeHex()
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] reverseArray(byte[] array) {
|
private byte[] reverseArray(byte[] array) {
|
||||||
int i = 0;
|
int i = 0;
|
||||||
int j = array.length - 1;
|
int j = array.length - 1;
|
||||||
byte tmp;
|
byte tmp;
|
||||||
while (j > i) {
|
while (j > i) {
|
||||||
tmp = array[j];
|
tmp = array[j];
|
||||||
array[j] = array[i];
|
array[j] = array[i];
|
||||||
array[i] = tmp;
|
array[i] = tmp;
|
||||||
j--;
|
j--;
|
||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
return array
|
return array
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,2 +0,0 @@
|
|||||||
.st-ignore
|
|
||||||
README.md
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
# Smartsense Motion Sensor
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Works with:
|
|
||||||
|
|
||||||
* [Samsung SmartThings Motion Sensor](https://shop.smartthings.com/#!/products/samsung-smartthings-motion-sensor)
|
|
||||||
|
|
||||||
## Table of contents
|
|
||||||
|
|
||||||
* [Capabilities](#capabilities)
|
|
||||||
* [Health](#device-health)
|
|
||||||
* [Battery](#battery-specification)
|
|
||||||
|
|
||||||
## Capabilities
|
|
||||||
|
|
||||||
* **Configuration** - _configure()_ command called when device is installed or device preferences updated
|
|
||||||
* **Motion Sensor** - can detect motion
|
|
||||||
* **Battery** - defines device uses a battery
|
|
||||||
* **Refresh** - _refresh()_ command for status updates
|
|
||||||
* **Health Check** - indicates ability to get device health notifications
|
|
||||||
|
|
||||||
## Device Health
|
|
||||||
|
|
||||||
A Category C2 motion sensor with maxReportTime of 5 mins.
|
|
||||||
Check-in interval is double the value of maxReportTime.
|
|
||||||
This gives the device twice the amount of time to respond before it is marked as offline.
|
|
||||||
Check-in interval = 12 mins
|
|
||||||
|
|
||||||
## Battery Specification
|
|
||||||
|
|
||||||
One CR2477 (for Samsung SmartThings Motion Sensor) / CR123A (SmartSense Motion Sensor) 3V battery is required.
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
If the device doesn't pair when trying from the SmartThings mobile app, it is possible that the sensor is out of range.
|
|
||||||
Pairing needs to be tried again by placing the sensor closer to the hub.
|
|
||||||
Instructions related to pairing, resetting and removing the different motion sensors from SmartThings can be found in the following links
|
|
||||||
for the different models:
|
|
||||||
* [SmartSense Motion Sensor (original model) Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/200903280-SmartSense-Motion-Sensor-original-model-)
|
|
||||||
* [SmartSense Motion Sensor (2014 model) Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/203077520-SmartSense-Motion-Sensor-2014-model-)
|
|
||||||
* [Samsung SmartThings Motion Sensor (2015 model) Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/205957580-Samsung-SmartThings-Motion-Sensor-2015-model-)
|
|
||||||
Other troubleshooting tips are listed as follows:
|
|
||||||
* [Troubleshooting: Samsung SmartThings Motion Sensor is stuck showing "Motion Detected" or "No Motion"](https://support.smartthings.com/hc/en-us/articles/200961130-Troubleshooting-Samsung-SmartThings-Motion-Sensor-is-stuck-showing-Motion-Detected-or-No-Motion-)
|
|
||||||
* [Troubleshooting: Samsung SmartThings Motion Sensor won’t pair after removing pull-tab](https://support.smartthings.com/hc/en-us/articles/204966616-Troubleshooting-Samsung-SmartThings-device-won-t-pair-after-removing-pull-tab)
|
|
||||||
@@ -1,33 +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.
|
|
||||||
# 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=모션 센서
|
|
||||||
'''motion'''.ko= 동작 감지
|
|
||||||
'''no motion'''.ko=동작 없음
|
|
||||||
'''${currentValue}% battery'''.ko=${currentValue}% 배터리
|
|
||||||
# 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,40 +1,34 @@
|
|||||||
/*
|
/**
|
||||||
* Copyright 2016 SmartThings
|
* SmartSense Motion/Temp Sensor
|
||||||
*
|
*
|
||||||
* 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.
|
|
||||||
*/
|
*/
|
||||||
import physicalgraph.zigbee.clusters.iaszone.ZoneStatus
|
|
||||||
|
|
||||||
|
|
||||||
metadata {
|
metadata {
|
||||||
definition (name: "SmartSense Motion Sensor", namespace: "smartthings", author: "SmartThings") {
|
definition (name: "SmartSense Motion Sensor", namespace: "smartthings", author: "SmartThings") {
|
||||||
capability "Motion Sensor"
|
capability "Motion Sensor"
|
||||||
capability "Configuration"
|
capability "Configuration"
|
||||||
capability "Battery"
|
capability "Battery"
|
||||||
capability "Temperature Measurement"
|
capability "Temperature Measurement"
|
||||||
capability "Refresh"
|
capability "Refresh"
|
||||||
capability "Health Check"
|
|
||||||
capability "Sensor"
|
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-S"
|
||||||
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3325-S", deviceJoinName: "Motion Sensor"
|
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3325-S", deviceJoinName: "Motion Sensor"
|
||||||
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"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
simulator {
|
simulator {
|
||||||
@@ -51,8 +45,8 @@ 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 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: "Temperature Offset", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,7 +84,7 @@ metadata {
|
|||||||
|
|
||||||
def parse(String description) {
|
def parse(String description) {
|
||||||
log.debug "description: $description"
|
log.debug "description: $description"
|
||||||
|
|
||||||
Map map = [:]
|
Map map = [:]
|
||||||
if (description?.startsWith('catchall:')) {
|
if (description?.startsWith('catchall:')) {
|
||||||
map = parseCatchAllMessage(description)
|
map = parseCatchAllMessage(description)
|
||||||
@@ -101,71 +95,55 @@ def parse(String description) {
|
|||||||
else if (description?.startsWith('temperature: ')) {
|
else if (description?.startsWith('temperature: ')) {
|
||||||
map = parseCustomMessage(description)
|
map = parseCustomMessage(description)
|
||||||
}
|
}
|
||||||
else if (description?.startsWith('zone status')) {
|
else if (description?.startsWith('zone status')) {
|
||||||
map = parseIasMessage(description)
|
map = parseIasMessage(description)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.debug "Parse returned $map"
|
log.debug "Parse returned $map"
|
||||||
def result = map ? createEvent(map) : null
|
def result = map ? createEvent(map) : null
|
||||||
|
|
||||||
if (description?.startsWith('enroll request')) {
|
if (description?.startsWith('enroll request')) {
|
||||||
List cmds = enrollResponse()
|
List cmds = enrollResponse()
|
||||||
log.debug "enroll response: ${cmds}"
|
log.debug "enroll response: ${cmds}"
|
||||||
result = cmds?.collect { new physicalgraph.device.HubAction(it) }
|
result = cmds?.collect { new physicalgraph.device.HubAction(it) }
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
private Map parseCatchAllMessage(String description) {
|
private Map parseCatchAllMessage(String description) {
|
||||||
Map resultMap = [:]
|
Map resultMap = [:]
|
||||||
def cluster = zigbee.parse(description)
|
def cluster = zigbee.parse(description)
|
||||||
if (shouldProcessMessage(cluster)) {
|
if (shouldProcessMessage(cluster)) {
|
||||||
switch(cluster.clusterId) {
|
switch(cluster.clusterId) {
|
||||||
case 0x0001:
|
case 0x0001:
|
||||||
// 0x07 - configure reporting
|
resultMap = getBatteryResult(cluster.data.last())
|
||||||
if (cluster.command != 0x07) {
|
break
|
||||||
resultMap = getBatteryResult(cluster.data.last())
|
|
||||||
}
|
|
||||||
break
|
|
||||||
|
|
||||||
case 0x0402:
|
case 0x0402:
|
||||||
if (cluster.command == 0x07) {
|
// temp is last 2 data values. reverse to swap endian
|
||||||
if (cluster.data[0] == 0x00) {
|
String temp = cluster.data[-2..-1].reverse().collect { cluster.hex1(it) }.join()
|
||||||
log.debug "TEMP REPORTING CONFIG RESPONSE" + cluster
|
def value = getTemperature(temp)
|
||||||
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
|
resultMap = getTemperatureResult(value)
|
||||||
}
|
break
|
||||||
else {
|
|
||||||
log.warn "TEMP REPORTING CONFIG FAILED- error code:${cluster.data[0]}"
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// temp is last 2 data values. reverse to swap endian
|
|
||||||
String temp = cluster.data[-2..-1].reverse().collect { cluster.hex1(it) }.join()
|
|
||||||
def value = getTemperature(temp)
|
|
||||||
resultMap = getTemperatureResult(value)
|
|
||||||
}
|
|
||||||
break
|
|
||||||
|
|
||||||
case 0x0406:
|
case 0x0406:
|
||||||
// 0x07 - configure reporting
|
log.debug 'motion'
|
||||||
if (cluster.command != 0x07) {
|
resultMap.name = 'motion'
|
||||||
log.debug 'motion'
|
break
|
||||||
resultMap.name = 'motion'
|
}
|
||||||
}
|
}
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return resultMap
|
return resultMap
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean shouldProcessMessage(cluster) {
|
private boolean shouldProcessMessage(cluster) {
|
||||||
// 0x0B is default response indicating message got through
|
// 0x0B is default response indicating message got through
|
||||||
boolean ignoredMessage = cluster.profileId != 0x0104 ||
|
// 0x07 is bind message
|
||||||
cluster.command == 0x0B ||
|
boolean ignoredMessage = cluster.profileId != 0x0104 ||
|
||||||
(cluster.data.size() > 0 && cluster.data.first() == 0x3e)
|
cluster.command == 0x0B ||
|
||||||
return !ignoredMessage
|
cluster.command == 0x07 ||
|
||||||
|
(cluster.data.size() > 0 && cluster.data.first() == 0x3e)
|
||||||
|
return !ignoredMessage
|
||||||
}
|
}
|
||||||
|
|
||||||
private Map parseReportAttributeMessage(String description) {
|
private Map parseReportAttributeMessage(String description) {
|
||||||
@@ -174,7 +152,7 @@ private Map parseReportAttributeMessage(String description) {
|
|||||||
map += [(nameAndValue[0].trim()):nameAndValue[1].trim()]
|
map += [(nameAndValue[0].trim()):nameAndValue[1].trim()]
|
||||||
}
|
}
|
||||||
log.debug "Desc Map: $descMap"
|
log.debug "Desc Map: $descMap"
|
||||||
|
|
||||||
Map resultMap = [:]
|
Map resultMap = [:]
|
||||||
if (descMap.cluster == "0402" && descMap.attrId == "0000") {
|
if (descMap.cluster == "0402" && descMap.attrId == "0000") {
|
||||||
def value = getTemperature(descMap.value)
|
def value = getTemperature(descMap.value)
|
||||||
@@ -183,14 +161,14 @@ private Map parseReportAttributeMessage(String description) {
|
|||||||
else if (descMap.cluster == "0001" && descMap.attrId == "0020") {
|
else if (descMap.cluster == "0001" && descMap.attrId == "0020") {
|
||||||
resultMap = getBatteryResult(Integer.parseInt(descMap.value, 16))
|
resultMap = getBatteryResult(Integer.parseInt(descMap.value, 16))
|
||||||
}
|
}
|
||||||
else if (descMap.cluster == "0406" && descMap.attrId == "0000") {
|
else if (descMap.cluster == "0406" && descMap.attrId == "0000") {
|
||||||
def value = descMap.value.endsWith("01") ? "active" : "inactive"
|
def value = descMap.value.endsWith("01") ? "active" : "inactive"
|
||||||
resultMap = getMotionResult(value)
|
resultMap = getMotionResult(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
return resultMap
|
return resultMap
|
||||||
}
|
}
|
||||||
|
|
||||||
private Map parseCustomMessage(String description) {
|
private Map parseCustomMessage(String description) {
|
||||||
Map resultMap = [:]
|
Map resultMap = [:]
|
||||||
if (description?.startsWith('temperature: ')) {
|
if (description?.startsWith('temperature: ')) {
|
||||||
@@ -201,67 +179,80 @@ private Map parseCustomMessage(String description) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Map parseIasMessage(String description) {
|
private Map parseIasMessage(String description) {
|
||||||
ZoneStatus zs = zigbee.parseZoneStatus(description)
|
List parsedMsg = description.split(' ')
|
||||||
|
String msgCode = parsedMsg[2]
|
||||||
|
|
||||||
|
Map resultMap = [:]
|
||||||
|
switch(msgCode) {
|
||||||
|
case '0x0020': // Closed/No Motion/Dry
|
||||||
|
resultMap = getMotionResult('inactive')
|
||||||
|
break
|
||||||
|
|
||||||
// Some sensor models that use this DTH use alarm1 and some use alarm2 to signify motion
|
case '0x0021': // Open/Motion/Wet
|
||||||
return (zs.isAlarm1Set() || zs.isAlarm2Set()) ? getMotionResult('active') : getMotionResult('inactive')
|
resultMap = getMotionResult('active')
|
||||||
|
break
|
||||||
|
|
||||||
|
case '0x0022': // Tamper Alarm
|
||||||
|
log.debug 'motion with tamper alarm'
|
||||||
|
resultMap = getMotionResult('active')
|
||||||
|
break
|
||||||
|
|
||||||
|
case '0x0023': // Battery Alarm
|
||||||
|
break
|
||||||
|
|
||||||
|
case '0x0024': // Supervision Report
|
||||||
|
log.debug 'no motion with tamper alarm'
|
||||||
|
resultMap = getMotionResult('inactive')
|
||||||
|
break
|
||||||
|
|
||||||
|
case '0x0025': // Restore Report
|
||||||
|
break
|
||||||
|
|
||||||
|
case '0x0026': // Trouble/Failure
|
||||||
|
log.debug 'motion with failure alarm'
|
||||||
|
resultMap = getMotionResult('active')
|
||||||
|
break
|
||||||
|
|
||||||
|
case '0x0028': // Test Mode
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return resultMap
|
||||||
}
|
}
|
||||||
|
|
||||||
def getTemperature(value) {
|
def getTemperature(value) {
|
||||||
def celsius = Integer.parseInt(value, 16).shortValue() / 100
|
def celsius = Integer.parseInt(value, 16).shortValue() / 100
|
||||||
if(getTemperatureScale() == "C"){
|
if(getTemperatureScale() == "C"){
|
||||||
return Math.round(celsius)
|
return celsius
|
||||||
} else {
|
} else {
|
||||||
return Math.round(celsiusToFahrenheit(celsius))
|
return celsiusToFahrenheit(celsius) as Integer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Map getBatteryResult(rawValue) {
|
private Map getBatteryResult(rawValue) {
|
||||||
log.debug "Battery rawValue = ${rawValue}"
|
log.debug 'Battery'
|
||||||
def linkText = getLinkText(device)
|
def linkText = getLinkText(device)
|
||||||
|
|
||||||
|
log.debug rawValue
|
||||||
|
|
||||||
def result = [
|
def result = [
|
||||||
name: 'battery',
|
name: 'battery',
|
||||||
value: '--',
|
value: '--'
|
||||||
translatable: true
|
|
||||||
]
|
]
|
||||||
|
|
||||||
def volts = rawValue / 10
|
def volts = rawValue / 10
|
||||||
|
def descriptionText
|
||||||
|
|
||||||
if (rawValue == 0 || rawValue == 255) {}
|
if (rawValue == 0) {}
|
||||||
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 (volts > 0){
|
||||||
if (device.getDataValue("manufacturer") == "SmartThings") {
|
def minVolts = 2.1
|
||||||
volts = rawValue // For the batteryMap to work the key needs to be an int
|
def maxVolts = 3.0
|
||||||
def batteryMap = [28:100, 27:100, 26:100, 25:90, 24:90, 23:70,
|
def pct = (volts - minVolts) / (maxVolts - minVolts)
|
||||||
22:70, 21:50, 20:50, 19:30, 18:30, 17:15, 16:1, 15:0]
|
result.value = Math.min(100, (int) pct * 100)
|
||||||
def minVolts = 15
|
result.descriptionText = "${linkText} battery was ${result.value}%"
|
||||||
def maxVolts = 28
|
|
||||||
|
|
||||||
if (volts < minVolts)
|
|
||||||
volts = minVolts
|
|
||||||
else if (volts > maxVolts)
|
|
||||||
volts = maxVolts
|
|
||||||
def pct = batteryMap[volts]
|
|
||||||
if (pct != null) {
|
|
||||||
result.value = pct
|
|
||||||
def value = pct
|
|
||||||
result.descriptionText = "{{ device.displayName }} battery was {{ value }}%"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
def minVolts = 2.1
|
|
||||||
def maxVolts = 3.0
|
|
||||||
def pct = (volts - minVolts) / (maxVolts - minVolts)
|
|
||||||
def roundedPct = Math.round(pct * 100)
|
|
||||||
if (roundedPct <= 0)
|
|
||||||
roundedPct = 1
|
|
||||||
result.value = Math.min(100, roundedPct)
|
|
||||||
result.descriptionText = "{{ device.displayName }} battery was {{ value }}%"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -270,44 +261,31 @@ 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,
|
|
||||||
unit: temperatureScale
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* PING is used by Device-Watch in attempt to reach the Device
|
|
||||||
* */
|
|
||||||
def ping() {
|
|
||||||
return zigbee.readAttribute(0x001, 0x0020) // Read the Battery Level
|
|
||||||
}
|
|
||||||
|
|
||||||
def refresh() {
|
def refresh() {
|
||||||
log.debug "refresh called"
|
log.debug "refresh called"
|
||||||
def refreshCmds = [
|
def refreshCmds = [
|
||||||
@@ -319,13 +297,22 @@ def refresh() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
def configure() {
|
def configure() {
|
||||||
// Device-Watch allows 3 check-in misses from device (plus 1 min lag time)
|
String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
|
||||||
// enrolls with default periodic reporting until newer 5 min interval is confirmed
|
log.debug "Configuring Reporting, IAS CIE, and Bindings."
|
||||||
sendEvent(name: "checkInterval", value: 3 * 60 * 60 + 1 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
|
|
||||||
|
|
||||||
// temperature minReportTime 30 seconds, maxReportTime 5 min. Reporting interval if no activity
|
def configCmds = [
|
||||||
// battery minReport 30 seconds, maxReportTime 6 hrs by default
|
"zcl global write 0x500 0x10 0xf0 {${zigbeeEui}}", "delay 200",
|
||||||
return refresh() + zigbee.batteryConfig() + zigbee.temperatureConfig(30, 300) // send refresh cmds as part of config
|
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
|
||||||
|
|
||||||
|
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 1 {${device.zigbeeId}} {}", "delay 200",
|
||||||
|
"zcl global send-me-a-report 1 0x20 0x20 30 21600 {01}", //checkin time 6 hrs
|
||||||
|
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
|
||||||
|
|
||||||
|
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 0x402 {${device.zigbeeId}} {}", "delay 200",
|
||||||
|
"zcl global send-me-a-report 0x402 0 0x29 300 3600 {6400}",
|
||||||
|
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500"
|
||||||
|
]
|
||||||
|
return configCmds + refresh() // send refresh cmds as part of config
|
||||||
}
|
}
|
||||||
|
|
||||||
def enrollResponse() {
|
def enrollResponse() {
|
||||||
@@ -350,19 +337,19 @@ private hex(value) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private String swapEndianHex(String hex) {
|
private String swapEndianHex(String hex) {
|
||||||
reverseArray(hex.decodeHex()).encodeHex()
|
reverseArray(hex.decodeHex()).encodeHex()
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] reverseArray(byte[] array) {
|
private byte[] reverseArray(byte[] array) {
|
||||||
int i = 0;
|
int i = 0;
|
||||||
int j = array.length - 1;
|
int j = array.length - 1;
|
||||||
byte tmp;
|
byte tmp;
|
||||||
while (j > i) {
|
while (j > i) {
|
||||||
tmp = array[j];
|
tmp = array[j];
|
||||||
array[j] = array[i];
|
array[j] = array[i];
|
||||||
array[i] = tmp;
|
array[i] = tmp;
|
||||||
j--;
|
j--;
|
||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
return array
|
return array
|
||||||
}
|
}
|
||||||
@@ -14,9 +14,6 @@
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
//DEPRECATED - Using the smartsense-motion-sensor.groovy DTH for this device. Users need to be moved before deleting this DTH
|
|
||||||
import physicalgraph.zigbee.clusters.iaszone.ZoneStatus
|
|
||||||
|
|
||||||
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"
|
||||||
@@ -25,9 +22,13 @@ metadata {
|
|||||||
capability "Temperature Measurement"
|
capability "Temperature Measurement"
|
||||||
capability "Refresh"
|
capability "Refresh"
|
||||||
capability "Sensor"
|
capability "Sensor"
|
||||||
|
|
||||||
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 {
|
||||||
@@ -36,8 +37,8 @@ metadata {
|
|||||||
}
|
}
|
||||||
|
|
||||||
preferences {
|
preferences {
|
||||||
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 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: "Temperature Offset", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false
|
||||||
}
|
}
|
||||||
|
|
||||||
tiles(scale: 2) {
|
tiles(scale: 2) {
|
||||||
@@ -74,7 +75,7 @@ metadata {
|
|||||||
|
|
||||||
def parse(String description) {
|
def parse(String description) {
|
||||||
log.debug "description: $description"
|
log.debug "description: $description"
|
||||||
|
|
||||||
Map map = [:]
|
Map map = [:]
|
||||||
if (description?.startsWith('catchall:')) {
|
if (description?.startsWith('catchall:')) {
|
||||||
map = parseCatchAllMessage(description)
|
map = parseCatchAllMessage(description)
|
||||||
@@ -88,10 +89,10 @@ def parse(String description) {
|
|||||||
else if (description?.startsWith('zone status')) {
|
else if (description?.startsWith('zone status')) {
|
||||||
map = parseIasMessage(description)
|
map = parseIasMessage(description)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.debug "Parse returned $map"
|
log.debug "Parse returned $map"
|
||||||
def result = map ? createEvent(map) : null
|
def result = map ? createEvent(map) : null
|
||||||
|
|
||||||
if (description?.startsWith('enroll request')) {
|
if (description?.startsWith('enroll request')) {
|
||||||
List cmds = enrollResponse()
|
List cmds = enrollResponse()
|
||||||
log.debug "enroll response: ${cmds}"
|
log.debug "enroll response: ${cmds}"
|
||||||
@@ -129,7 +130,7 @@ private Map parseCatchAllMessage(String description) {
|
|||||||
private boolean shouldProcessMessage(cluster) {
|
private boolean shouldProcessMessage(cluster) {
|
||||||
// 0x0B is default response indicating message got through
|
// 0x0B is default response indicating message got through
|
||||||
// 0x07 is bind message
|
// 0x07 is bind message
|
||||||
boolean ignoredMessage = cluster.profileId != 0x0104 ||
|
boolean ignoredMessage = cluster.profileId != 0x0104 ||
|
||||||
cluster.command == 0x0B ||
|
cluster.command == 0x0B ||
|
||||||
cluster.command == 0x07 ||
|
cluster.command == 0x07 ||
|
||||||
(cluster.data.size() > 0 && cluster.data.first() == 0x3e)
|
(cluster.data.size() > 0 && cluster.data.first() == 0x3e)
|
||||||
@@ -142,7 +143,7 @@ private Map parseReportAttributeMessage(String description) {
|
|||||||
map += [(nameAndValue[0].trim()):nameAndValue[1].trim()]
|
map += [(nameAndValue[0].trim()):nameAndValue[1].trim()]
|
||||||
}
|
}
|
||||||
log.debug "Desc Map: $descMap"
|
log.debug "Desc Map: $descMap"
|
||||||
|
|
||||||
Map resultMap = [:]
|
Map resultMap = [:]
|
||||||
if (descMap.cluster == "0402" && descMap.attrId == "0000") {
|
if (descMap.cluster == "0402" && descMap.attrId == "0000") {
|
||||||
def value = getTemperature(descMap.value)
|
def value = getTemperature(descMap.value)
|
||||||
@@ -154,11 +155,11 @@ private Map parseReportAttributeMessage(String description) {
|
|||||||
else if (descMap.cluster == "0406" && descMap.attrId == "0000") {
|
else if (descMap.cluster == "0406" && descMap.attrId == "0000") {
|
||||||
def value = descMap.value.endsWith("01") ? "active" : "inactive"
|
def value = descMap.value.endsWith("01") ? "active" : "inactive"
|
||||||
resultMap = getMotionResult(value)
|
resultMap = getMotionResult(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
return resultMap
|
return resultMap
|
||||||
}
|
}
|
||||||
|
|
||||||
private Map parseCustomMessage(String description) {
|
private Map parseCustomMessage(String description) {
|
||||||
Map resultMap = [:]
|
Map resultMap = [:]
|
||||||
if (description?.startsWith('temperature: ')) {
|
if (description?.startsWith('temperature: ')) {
|
||||||
@@ -169,8 +170,44 @@ private Map parseCustomMessage(String description) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Map parseIasMessage(String description) {
|
private Map parseIasMessage(String description) {
|
||||||
ZoneStatus zs = zigbee.parseZoneStatus(description)
|
List parsedMsg = description.split(' ')
|
||||||
return (zs.isAlarm1Set() || zs.isAlarm2Set()) ? getMotionResult('active') : getMotionResult('inactive')
|
String msgCode = parsedMsg[2]
|
||||||
|
|
||||||
|
Map resultMap = [:]
|
||||||
|
switch(msgCode) {
|
||||||
|
case '0x0020': // Closed/No Motion/Dry
|
||||||
|
resultMap = getMotionResult('inactive')
|
||||||
|
break
|
||||||
|
|
||||||
|
case '0x0021': // Open/Motion/Wet
|
||||||
|
resultMap = getMotionResult('active')
|
||||||
|
break
|
||||||
|
|
||||||
|
case '0x0022': // Tamper Alarm
|
||||||
|
log.debug 'motion with tamper alarm'
|
||||||
|
resultMap = getMotionResult('active')
|
||||||
|
break
|
||||||
|
|
||||||
|
case '0x0023': // Battery Alarm
|
||||||
|
break
|
||||||
|
|
||||||
|
case '0x0024': // Supervision Report
|
||||||
|
log.debug 'no motion with tamper alarm'
|
||||||
|
resultMap = getMotionResult('inactive')
|
||||||
|
break
|
||||||
|
|
||||||
|
case '0x0025': // Restore Report
|
||||||
|
break
|
||||||
|
|
||||||
|
case '0x0026': // Trouble/Failure
|
||||||
|
log.debug 'motion with failure alarm'
|
||||||
|
resultMap = getMotionResult('active')
|
||||||
|
break
|
||||||
|
|
||||||
|
case '0x0028': // Test Mode
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return resultMap
|
||||||
}
|
}
|
||||||
|
|
||||||
def getTemperature(value) {
|
def getTemperature(value) {
|
||||||
@@ -196,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)."
|
||||||
@@ -205,10 +242,7 @@ private Map getBatteryResult(rawValue) {
|
|||||||
def minVolts = 2.1
|
def minVolts = 2.1
|
||||||
def maxVolts = 3.0
|
def maxVolts = 3.0
|
||||||
def pct = (volts - minVolts) / (maxVolts - minVolts)
|
def pct = (volts - minVolts) / (maxVolts - minVolts)
|
||||||
def roundedPct = Math.round(pct * 100)
|
result.value = Math.min(100, (int) pct * 100)
|
||||||
if (roundedPct <= 0)
|
|
||||||
roundedPct = 1
|
|
||||||
result.value = Math.min(100, roundedPct)
|
|
||||||
result.descriptionText = "${linkText} battery was ${result.value}%"
|
result.descriptionText = "${linkText} battery was ${result.value}%"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -228,8 +262,7 @@ private Map getTemperatureResult(value) {
|
|||||||
return [
|
return [
|
||||||
name: 'temperature',
|
name: 'temperature',
|
||||||
value: value,
|
value: value,
|
||||||
descriptionText: descriptionText,
|
descriptionText: descriptionText
|
||||||
unit: temperatureScale
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -255,9 +288,13 @@ def refresh() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
def configure() {
|
def configure() {
|
||||||
|
String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
|
||||||
log.debug "Configuring Reporting, IAS CIE, and Bindings."
|
log.debug "Configuring Reporting, IAS CIE, and Bindings."
|
||||||
|
|
||||||
def configCmds = [
|
def configCmds = [
|
||||||
|
"zcl global write 0x500 0x10 0xf0 {${zigbeeEui}}", "delay 200",
|
||||||
|
"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}", //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",
|
||||||
@@ -266,7 +303,7 @@ def configure() {
|
|||||||
"zcl global send-me-a-report 0x402 0 0x29 30 3600 {6400}",
|
"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"
|
||||||
]
|
]
|
||||||
return refresh() + configCmds // send refresh cmds as part of config
|
return configCmds + refresh() // send refresh cmds as part of config
|
||||||
}
|
}
|
||||||
|
|
||||||
def enrollResponse() {
|
def enrollResponse() {
|
||||||
|
|||||||
@@ -1,2 +0,0 @@
|
|||||||
.st-ignore
|
|
||||||
README.md
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
# Smartsense Multi Sensor
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Works with:
|
|
||||||
|
|
||||||
* [Samsung SmartThings Multi Sensor](https://shop.smartthings.com/#!/products/smartsense-multi)
|
|
||||||
|
|
||||||
## Table of contents
|
|
||||||
|
|
||||||
* [Capabilities](#capabilities)
|
|
||||||
* [Health](#device-health)
|
|
||||||
* [Battery](#battery-specification)
|
|
||||||
|
|
||||||
## Capabilities
|
|
||||||
|
|
||||||
* **Three Axis** - monitors the state of a single axis
|
|
||||||
* **Configuration** - _configure()_ command called when device is installed or device preferences updated
|
|
||||||
* **Battery** - defines device uses a battery
|
|
||||||
* **Sensor** - detects sensor events
|
|
||||||
* **Contact Sensor** - can detect contact (possible values: open,closed)
|
|
||||||
* **Acceleration Sensor** - allows for acceleration detection.
|
|
||||||
* **Refresh** - _refresh()_ command for status updates
|
|
||||||
* **Temperature Measurement** - defines device measures current temperature
|
|
||||||
* **Health Check** - indicates ability to get device health notifications
|
|
||||||
|
|
||||||
## Device Health
|
|
||||||
|
|
||||||
A Category C2 multi sensor with maxReportTime of 5 mins.
|
|
||||||
Check-in interval is double the value of maxReportTime.
|
|
||||||
This gives the device twice the amount of time to respond before it is marked as offline.
|
|
||||||
Check-in interval = 12 mins
|
|
||||||
|
|
||||||
## Battery Specification
|
|
||||||
|
|
||||||
One CR2450 (for Samsung SmartThings Multipurpose Sensor) battery / Two AAAA (for SmartSense Multi Sensor) batteries required.
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
If the sensor doesn't pair when trying from the SmartThings mobile app, it is possible that the sensor is out of range.
|
|
||||||
Pairing needs to be tried again by placing the sensor closer to the hub.
|
|
||||||
Other troubleshooting tips are listed as follows:
|
|
||||||
* [Troubleshooting: Samsung SmartThings Multipurpose Sensor is stuck on "open" or "closed"](https://support.smartthings.com/hc/en-us/articles/200955940-Troubleshooting-Samsung-SmartThings-Multipurpose-Sensor-is-stuck-on-open-or-closed-)
|
|
||||||
* [Troubleshooting: Temperature reading for the Samsung SmartThings Multipurpose Sensor is off](https://support.smartthings.com/hc/en-us/articles/200756845-Troubleshooting-Temperature-reading-for-the-Samsung-SmartThings-Multipurpose-Sensor-is-off)
|
|
||||||
* [Troubleshooting: Samsung SmartThings Multipurpose Sensor won’t pair after removing pull-tab](https://support.smartthings.com/hc/en-us/articles/204966616-Troubleshooting-Samsung-SmartThings-device-won-t-pair-after-removing-pull-tab)
|
|
||||||
@@ -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.
|
|
||||||
# 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=기기-열림/닫힘 센서 업데이트 중
|
|
||||||
'''Inactive'''.ko=비활성 상태
|
|
||||||
'''Active'''.ko=활성 상태
|
|
||||||
'''Open'''.ko= 열림이 감지될 때
|
|
||||||
'''Closed'''.ko=닫힘
|
|
||||||
'''${currentValue}% battery'''.ko=${currentValue}% 배터리
|
|
||||||
@@ -1,43 +1,40 @@
|
|||||||
/*
|
/**
|
||||||
* Copyright 2016 SmartThings
|
* SmartSense Multi
|
||||||
*
|
*
|
||||||
* 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.
|
|
||||||
*/
|
*/
|
||||||
import physicalgraph.zigbee.clusters.iaszone.ZoneStatus
|
|
||||||
|
|
||||||
metadata {
|
metadata {
|
||||||
definition (name: "SmartSense Multi Sensor", namespace: "smartthings", author: "SmartThings") {
|
definition (name: "SmartSense Multi Sensor", namespace: "smartthings", author: "SmartThings") {
|
||||||
|
|
||||||
capability "Three Axis"
|
capability "Three Axis"
|
||||||
capability "Battery"
|
capability "Battery"
|
||||||
capability "Configuration"
|
capability "Configuration"
|
||||||
capability "Sensor"
|
capability "Sensor"
|
||||||
capability "Contact Sensor"
|
capability "Contact Sensor"
|
||||||
capability "Acceleration Sensor"
|
capability "Acceleration Sensor"
|
||||||
capability "Refresh"
|
capability "Refresh"
|
||||||
capability "Temperature Measurement"
|
capability "Temperature Measurement"
|
||||||
capability "Health Check"
|
|
||||||
|
|
||||||
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: "3320"
|
||||||
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05,FC02", outClusters: "0019", manufacturer: "CentraLite", model: "3321"
|
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05,FC02", outClusters: "0019", manufacturer: "CentraLite", model: "3321"
|
||||||
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05,FC02", outClusters: "0019", manufacturer: "CentraLite", model: "3321-S", deviceJoinName: "Multipurpose Sensor"
|
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05,FC02", outClusters: "0019", manufacturer: "CentraLite", model: "3321-S", deviceJoinName: "Multipurpose Sensor"
|
||||||
fingerprint inClusters: "0000,0001,0003,000F,0020,0402,0500,FC02", outClusters: "0019", manufacturer: "SmartThings", model: "multiv4", deviceJoinName: "Multipurpose Sensor"
|
|
||||||
|
|
||||||
attribute "status", "string"
|
attribute "status", "string"
|
||||||
}
|
}
|
||||||
|
|
||||||
simulator {
|
simulator {
|
||||||
status "open": "zone report :: type: 19 value: 0031"
|
status "open": "zone report :: type: 19 value: 0031"
|
||||||
status "closed": "zone report :: type: 19 value: 0030"
|
status "closed": "zone report :: type: 19 value: 0030"
|
||||||
|
|
||||||
@@ -54,7 +51,7 @@ metadata {
|
|||||||
status "x,y,z: 0,1000,0": "x: 0, y: 1000, z: 0"
|
status "x,y,z: 0,1000,0": "x: 0, y: 1000, z: 0"
|
||||||
status "x,y,z: 0,0,1000": "x: 0, y: 0, z: 1000"
|
status "x,y,z: 0,0,1000": "x: 0, y: 0, z: 1000"
|
||||||
}
|
}
|
||||||
preferences {
|
preferences {
|
||||||
section {
|
section {
|
||||||
image(name: 'educationalcontent', multiple: true, images: [
|
image(name: 'educationalcontent', multiple: true, images: [
|
||||||
"http://cdn.device-gse.smartthings.com/Multi/Multi1.jpg",
|
"http://cdn.device-gse.smartthings.com/Multi/Multi1.jpg",
|
||||||
@@ -64,30 +61,30 @@ 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 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: "Temperature Offset", 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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tiles(scale: 2) {
|
tiles(scale: 2) {
|
||||||
multiAttributeTile(name:"status", type: "generic", width: 6, height: 4){
|
multiAttributeTile(name:"status", type: "generic", width: 6, height: 4){
|
||||||
tileAttribute ("device.status", key: "PRIMARY_CONTROL") {
|
tileAttribute ("device.status", key: "PRIMARY_CONTROL") {
|
||||||
attributeState "open", label:'Open', icon:"st.contact.contact.open", backgroundColor:"#ffa81e"
|
attributeState "open", label:'${name}', icon:"st.contact.contact.open", backgroundColor:"#ffa81e"
|
||||||
attributeState "closed", label:'Closed', icon:"st.contact.contact.closed", backgroundColor:"#79b821"
|
attributeState "closed", label:'${name}', icon:"st.contact.contact.closed", backgroundColor:"#79b821"
|
||||||
attributeState "garage-open", label:'Open', icon:"st.doors.garage.garage-open", backgroundColor:"#ffa81e"
|
attributeState "garage-open", label:'Open', icon:"st.doors.garage.garage-open", backgroundColor:"#ffa81e"
|
||||||
attributeState "garage-closed", label:'Closed', icon:"st.doors.garage.garage-closed", backgroundColor:"#79b821"
|
attributeState "garage-closed", label:'Closed', icon:"st.doors.garage.garage-closed", backgroundColor:"#79b821"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
standardTile("contact", "device.contact", width: 2, height: 2) {
|
standardTile("contact", "device.contact", width: 2, height: 2) {
|
||||||
state("open", label:'Open', icon:"st.contact.contact.open", backgroundColor:"#ffa81e")
|
state("open", label:'${name}', icon:"st.contact.contact.open", backgroundColor:"#ffa81e")
|
||||||
state("closed", label:'Closed', icon:"st.contact.contact.closed", backgroundColor:"#79b821")
|
state("closed", label:'${name}', icon:"st.contact.contact.closed", backgroundColor:"#79b821")
|
||||||
}
|
}
|
||||||
standardTile("acceleration", "device.acceleration", width: 2, height: 2) {
|
standardTile("acceleration", "device.acceleration", width: 2, height: 2) {
|
||||||
state("active", label:'Active', icon:"st.motion.acceleration.active", backgroundColor:"#53a7c0")
|
state("active", label:'${name}', icon:"st.motion.acceleration.active", backgroundColor:"#53a7c0")
|
||||||
state("inactive", label:'Inactive', icon:"st.motion.acceleration.inactive", backgroundColor:"#ffffff")
|
state("inactive", label:'${name}', icon:"st.motion.acceleration.inactive", backgroundColor:"#ffffff")
|
||||||
}
|
}
|
||||||
valueTile("temperature", "device.temperature", width: 2, height: 2) {
|
valueTile("temperature", "device.temperature", width: 2, height: 2) {
|
||||||
state("temperature", label:'${currentValue}°',
|
state("temperature", label:'${currentValue}°',
|
||||||
@@ -102,128 +99,107 @@ 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:""
|
||||||
}
|
}
|
||||||
standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
||||||
state "default", action:"refresh.refresh", icon:"st.secondary.refresh"
|
state "default", action:"refresh.refresh", icon:"st.secondary.refresh"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
main(["status", "acceleration", "temperature"])
|
main(["status", "acceleration", "temperature"])
|
||||||
details(["status", "acceleration", "temperature", "battery", "refresh"])
|
details(["status", "acceleration", "temperature", "3axis", "battery", "refresh"])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def parse(String description) {
|
def parse(String description) {
|
||||||
Map map = [:]
|
|
||||||
if (description?.startsWith('catchall:')) {
|
Map map = [:]
|
||||||
map = parseCatchAllMessage(description)
|
if (description?.startsWith('catchall:')) {
|
||||||
}
|
map = parseCatchAllMessage(description)
|
||||||
else if (description?.startsWith('temperature: ')) {
|
}
|
||||||
map = parseCustomMessage(description)
|
else if (description?.startsWith('read attr -')) {
|
||||||
}
|
map = parseReportAttributeMessage(description)
|
||||||
else if (description?.startsWith('zone status')) {
|
}
|
||||||
map = parseIasMessage(description)
|
else if (description?.startsWith('temperature: ')) {
|
||||||
}
|
map = parseCustomMessage(description)
|
||||||
|
}
|
||||||
|
else if (description?.startsWith('zone status')) {
|
||||||
|
map = parseIasMessage(description)
|
||||||
|
}
|
||||||
|
|
||||||
def result = map ? createEvent(map) : null
|
def result = map ? createEvent(map) : null
|
||||||
|
|
||||||
if (description?.startsWith('enroll request')) {
|
if (description?.startsWith('enroll request')) {
|
||||||
List cmds = enrollResponse()
|
List cmds = enrollResponse()
|
||||||
log.debug "enroll response: ${cmds}"
|
log.debug "enroll response: ${cmds}"
|
||||||
result = cmds?.collect { new physicalgraph.device.HubAction(it) }
|
result = cmds?.collect { new physicalgraph.device.HubAction(it) }
|
||||||
}
|
}
|
||||||
else if (description?.startsWith('read attr -')) {
|
return result
|
||||||
result = parseReportAttributeMessage(description).each { createEvent(it) }
|
}
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
private Map parseCatchAllMessage(String description) {
|
private Map parseCatchAllMessage(String description) {
|
||||||
Map resultMap = [:]
|
Map resultMap = [:]
|
||||||
def cluster = zigbee.parse(description)
|
def cluster = zigbee.parse(description)
|
||||||
log.debug cluster
|
log.debug cluster
|
||||||
if (shouldProcessMessage(cluster)) {
|
if (shouldProcessMessage(cluster)) {
|
||||||
switch(cluster.clusterId) {
|
switch(cluster.clusterId) {
|
||||||
case 0x0001:
|
case 0x0001:
|
||||||
// 0x07 - configure reporting
|
resultMap = getBatteryResult(cluster.data.last())
|
||||||
if (cluster.command != 0x07) {
|
break
|
||||||
resultMap = getBatteryResult(cluster.data.last())
|
|
||||||
}
|
|
||||||
break
|
|
||||||
|
|
||||||
case 0xFC02:
|
case 0xFC02:
|
||||||
log.debug 'ACCELERATION'
|
log.debug 'ACCELERATION'
|
||||||
break
|
break
|
||||||
|
|
||||||
case 0x0402:
|
case 0x0402:
|
||||||
if (cluster.command == 0x07) {
|
log.debug 'TEMP'
|
||||||
if(cluster.data[0] == 0x00) {
|
// temp is last 2 data values. reverse to swap endian
|
||||||
log.debug "TEMP REPORTING CONFIG RESPONSE" + cluster
|
String temp = cluster.data[-2..-1].reverse().collect { cluster.hex1(it) }.join()
|
||||||
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
|
def value = getTemperature(temp)
|
||||||
}
|
resultMap = getTemperatureResult(value)
|
||||||
else {
|
break
|
||||||
log.warn "TEMP REPORTING CONFIG FAILED- error code:${cluster.data[0]}"
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
else {
|
|
||||||
// temp is last 2 data values. reverse to swap endian
|
|
||||||
String temp = cluster.data[-2..-1].reverse().collect { cluster.hex1(it) }.join()
|
|
||||||
def value = getTemperature(temp)
|
|
||||||
resultMap = getTemperatureResult(value)
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return resultMap
|
return resultMap
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean shouldProcessMessage(cluster) {
|
private boolean shouldProcessMessage(cluster) {
|
||||||
// 0x0B is default response indicating message got through
|
// 0x0B is default response indicating message got through
|
||||||
boolean ignoredMessage = cluster.profileId != 0x0104 ||
|
// 0x07 is bind message
|
||||||
cluster.command == 0x0B ||
|
boolean ignoredMessage = cluster.profileId != 0x0104 ||
|
||||||
(cluster.data.size() > 0 && cluster.data.first() == 0x3e)
|
cluster.command == 0x0B ||
|
||||||
return !ignoredMessage
|
cluster.command == 0x07 ||
|
||||||
|
(cluster.data.size() > 0 && cluster.data.first() == 0x3e)
|
||||||
|
return !ignoredMessage
|
||||||
}
|
}
|
||||||
|
|
||||||
private List parseReportAttributeMessage(String description) {
|
private Map parseReportAttributeMessage(String description) {
|
||||||
Map descMap = (description - "read attr - ").split(",").inject([:]) { map, param ->
|
Map descMap = (description - "read attr - ").split(",").inject([:]) { map, param ->
|
||||||
def nameAndValue = param.split(":")
|
def nameAndValue = param.split(":")
|
||||||
map += [(nameAndValue[0].trim()):nameAndValue[1].trim()]
|
map += [(nameAndValue[0].trim()):nameAndValue[1].trim()]
|
||||||
}
|
}
|
||||||
|
|
||||||
List result = []
|
Map resultMap = [:]
|
||||||
if (descMap.cluster == "0402" && descMap.attrId == "0000") {
|
if (descMap.cluster == "0402" && descMap.attrId == "0000") {
|
||||||
def value = getTemperature(descMap.value)
|
def value = getTemperature(descMap.value)
|
||||||
result << getTemperatureResult(value)
|
resultMap = getTemperatureResult(value)
|
||||||
}
|
}
|
||||||
else if (descMap.cluster == "FC02" && descMap.attrId == "0010") {
|
else if (descMap.cluster == "FC02" && descMap.attrId == "0010") {
|
||||||
if (descMap.value.size() == 32) {
|
resultMap = getAccelerationResult(descMap.value)
|
||||||
// value will look like 00ae29001403e2290013001629001201
|
|
||||||
// breaking this apart and swapping byte order where appropriate, this breaks down to:
|
|
||||||
// X (0x0012) = 0x0016
|
|
||||||
// Y (0x0013) = 0x03E2
|
|
||||||
// Z (0x0014) = 0x00AE
|
|
||||||
// note that there is a known bug in that the x,y,z attributes are interpreted in the wrong order
|
|
||||||
// this will be fixed in a future update
|
|
||||||
def threeAxisAttributes = descMap.value[0..-9]
|
|
||||||
result << parseAxis(threeAxisAttributes)
|
|
||||||
descMap.value = descMap.value[-2..-1]
|
|
||||||
}
|
|
||||||
result << getAccelerationResult(descMap.value)
|
|
||||||
}
|
}
|
||||||
else if (descMap.cluster == "FC02" && descMap.attrId == "0012" && descMap.value.size() == 24) {
|
else if (descMap.cluster == "FC02" && descMap.attrId == "0012") {
|
||||||
// The size is checked to ensure the attribute report contains X, Y and Z values
|
resultMap = parseAxis(descMap.value)
|
||||||
// If all three axis are not included then the attribute report is ignored
|
|
||||||
result << parseAxis(descMap.value)
|
|
||||||
}
|
}
|
||||||
else if (descMap.cluster == "0001" && descMap.attrId == "0020") {
|
else if (descMap.cluster == "0001" && descMap.attrId == "0020") {
|
||||||
result << getBatteryResult(Integer.parseInt(descMap.value, 16))
|
resultMap = getBatteryResult(Integer.parseInt(descMap.value, 16))
|
||||||
}
|
}
|
||||||
|
|
||||||
return result
|
return resultMap
|
||||||
}
|
}
|
||||||
|
|
||||||
private Map parseCustomMessage(String description) {
|
private Map parseCustomMessage(String description) {
|
||||||
@@ -236,14 +212,48 @@ private Map parseCustomMessage(String description) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Map parseIasMessage(String description) {
|
private Map parseIasMessage(String description) {
|
||||||
ZoneStatus zs = zigbee.parseZoneStatus(description)
|
List parsedMsg = description.split(' ')
|
||||||
|
String msgCode = parsedMsg[2]
|
||||||
|
|
||||||
Map resultMap = [:]
|
Map resultMap = [:]
|
||||||
|
switch(msgCode) {
|
||||||
|
case '0x0020': // Closed/No Motion/Dry
|
||||||
if (garageSensor != "Yes"){
|
if (garageSensor != "Yes"){
|
||||||
resultMap = zs.isAlarm1Set() ? getContactResult('open') : getContactResult('closed')
|
resultMap = getContactResult('closed')
|
||||||
}
|
}
|
||||||
|
break
|
||||||
|
|
||||||
return resultMap
|
case '0x0021': // Open/Motion/Wet
|
||||||
|
if (garageSensor != "Yes"){
|
||||||
|
resultMap = getContactResult('open')
|
||||||
|
}
|
||||||
|
break
|
||||||
|
|
||||||
|
case '0x0022': // Tamper Alarm
|
||||||
|
break
|
||||||
|
|
||||||
|
case '0x0023': // Battery Alarm
|
||||||
|
break
|
||||||
|
|
||||||
|
case '0x0024': // Supervision Report
|
||||||
|
if (garageSensor != "Yes"){
|
||||||
|
resultMap = getContactResult('closed')
|
||||||
|
}
|
||||||
|
break
|
||||||
|
|
||||||
|
case '0x0025': // Restore Report
|
||||||
|
if (garageSensor != "Yes"){
|
||||||
|
resultMap = getContactResult('open')
|
||||||
|
}
|
||||||
|
break
|
||||||
|
|
||||||
|
case '0x0026': // Trouble/Failure
|
||||||
|
break
|
||||||
|
|
||||||
|
case '0x0028': // Test Mode
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return resultMap
|
||||||
}
|
}
|
||||||
|
|
||||||
def updated() {
|
def updated() {
|
||||||
@@ -252,19 +262,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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -272,162 +282,152 @@ def updated() {
|
|||||||
def getTemperature(value) {
|
def getTemperature(value) {
|
||||||
def celsius = Integer.parseInt(value, 16).shortValue() / 100
|
def celsius = Integer.parseInt(value, 16).shortValue() / 100
|
||||||
if(getTemperatureScale() == "C"){
|
if(getTemperatureScale() == "C"){
|
||||||
return Math.round(celsius)
|
return celsius
|
||||||
} else {
|
} else {
|
||||||
return Math.round(celsiusToFahrenheit(celsius))
|
return celsiusToFahrenheit(celsius) as Integer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Map getBatteryResult(rawValue) {
|
private Map getBatteryResult(rawValue) {
|
||||||
log.debug "Battery rawValue = ${rawValue}"
|
log.debug "Battery"
|
||||||
|
log.debug 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
|
||||||
|
def descriptionText
|
||||||
if (rawValue == 0 || rawValue == 255) {}
|
|
||||||
else {
|
if (rawValue == 255) {}
|
||||||
if (volts > 3.5) {
|
else {
|
||||||
result.descriptionText = "{{ device.displayName }} battery has too much power: (> 3.5) volts."
|
|
||||||
|
if (volts > 3.5) {
|
||||||
|
result.descriptionText = "${linkText} battery has too much power (${volts} volts)."
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if (device.getDataValue("manufacturer") == "SmartThings") {
|
def minVolts = 2.1
|
||||||
volts = rawValue // For the batteryMap to work the key needs to be an int
|
def maxVolts = 3.0
|
||||||
def batteryMap = [28:100, 27:100, 26:100, 25:90, 24:90, 23:70,
|
def pct = (volts - minVolts) / (maxVolts - minVolts)
|
||||||
22:70, 21:50, 20:50, 19:30, 18:30, 17:15, 16:1, 15:0]
|
result.value = Math.min(100, (int) pct * 100)
|
||||||
def minVolts = 15
|
result.descriptionText = "${linkText} battery was ${result.value}%"
|
||||||
def maxVolts = 28
|
}}
|
||||||
|
|
||||||
if (volts < minVolts)
|
return result
|
||||||
volts = minVolts
|
}
|
||||||
else if (volts > maxVolts)
|
|
||||||
volts = maxVolts
|
private Map getTemperatureResult(value) {
|
||||||
def pct = batteryMap[volts]
|
log.debug "Temperature"
|
||||||
if (pct != null) {
|
def linkText = getLinkText(device)
|
||||||
result.value = pct
|
if (tempOffset) {
|
||||||
result.descriptionText = "{{ device.displayName }} battery was {{ value }}%"
|
def offset = tempOffset as int
|
||||||
}
|
def v = value as int
|
||||||
}
|
value = v + offset
|
||||||
else {
|
|
||||||
def minVolts = 2.1
|
|
||||||
def maxVolts = 3.0
|
|
||||||
def pct = (volts - minVolts) / (maxVolts - minVolts)
|
|
||||||
def roundedPct = Math.round(pct * 100)
|
|
||||||
if (roundedPct <= 0)
|
|
||||||
roundedPct = 1
|
|
||||||
result.value = Math.min(100, roundedPct)
|
|
||||||
result.descriptionText = "{{ device.displayName }} battery was {{ value }}%"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
def descriptionText = "${linkText} was ${value}°${temperatureScale}"
|
||||||
|
return [
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
private Map getTemperatureResult(value) {
|
|
||||||
log.debug "Temperature"
|
|
||||||
if (tempOffset) {
|
|
||||||
def offset = tempOffset as int
|
|
||||||
def v = value as int
|
|
||||||
value = v + offset
|
|
||||||
}
|
|
||||||
def descriptionText = temperatureScale == 'C' ? '{{ device.displayName }} was {{ value }}°C':
|
|
||||||
'{{ device.displayName }} was {{ value }}°F'
|
|
||||||
|
|
||||||
return [
|
|
||||||
name: 'temperature',
|
name: 'temperature',
|
||||||
value: value,
|
value: value,
|
||||||
descriptionText: descriptionText,
|
descriptionText: descriptionText
|
||||||
translatable: true,
|
]
|
||||||
unit: temperatureScale
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
private Map getContactResult(value) {
|
|
||||||
log.debug "Contact: ${device.displayName} value = ${value}"
|
|
||||||
def descriptionText = value == 'open' ? '{{ device.displayName }} was opened' : '{{ device.displayName }} was closed'
|
|
||||||
sendEvent(name: 'contact', value: value, descriptionText: descriptionText, displayed: false, translatable: true)
|
|
||||||
sendEvent(name: 'status', value: value, descriptionText: descriptionText, translatable: true)
|
|
||||||
}
|
|
||||||
|
|
||||||
private getAccelerationResult(numValue) {
|
|
||||||
log.debug "Acceleration"
|
|
||||||
def name = "acceleration"
|
|
||||||
def value
|
|
||||||
def descriptionText
|
|
||||||
|
|
||||||
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)
|
|
||||||
return [
|
|
||||||
name: name,
|
|
||||||
value: value,
|
|
||||||
descriptionText: descriptionText,
|
|
||||||
isStateChange: isStateChange,
|
|
||||||
translatable: true
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* PING is used by Device-Watch in attempt to reach the Device
|
|
||||||
* */
|
|
||||||
def ping() {
|
|
||||||
return zigbee.readAttribute(0x001, 0x0020) // Read the Battery Level
|
|
||||||
}
|
|
||||||
|
|
||||||
def refresh() {
|
|
||||||
log.debug "Refreshing Values "
|
|
||||||
|
|
||||||
def refreshCmds = []
|
|
||||||
|
|
||||||
if (device.getDataValue("manufacturer") == "SmartThings") {
|
|
||||||
log.debug "Refreshing Values for manufacturer: SmartThings "
|
|
||||||
/* These values of Motion Threshold Multiplier(0x01) and Motion Threshold (0x0276)
|
|
||||||
seem to be giving pretty accurate results for the XYZ co-ordinates for this manufacturer.
|
|
||||||
Separating these out in a separate if-else because I do not want to touch Centralite part
|
|
||||||
as of now.
|
|
||||||
*/
|
|
||||||
refreshCmds += zigbee.writeAttribute(0xFC02, 0x0000, 0x20, 0x01, [mfgCode: manufacturerCode])
|
|
||||||
refreshCmds += zigbee.writeAttribute(0xFC02, 0x0002, 0x21, 0x0276, [mfgCode: manufacturerCode])
|
|
||||||
} else {
|
|
||||||
refreshCmds += zigbee.writeAttribute(0xFC02, 0x0000, 0x20, 0x02, [mfgCode: manufacturerCode])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//Common refresh commands
|
private Map getContactResult(value) {
|
||||||
refreshCmds += zigbee.readAttribute(0x0402, 0x0000) +
|
log.debug "Contact"
|
||||||
zigbee.readAttribute(0x0001, 0x0020) +
|
def linkText = getLinkText(device)
|
||||||
zigbee.readAttribute(0xFC02, 0x0010, [mfgCode: manufacturerCode])
|
def descriptionText = "${linkText} was ${value == 'open' ? 'opened' : 'closed'}"
|
||||||
|
sendEvent(name: 'contact', value: value, descriptionText: descriptionText, displayed:false)
|
||||||
|
sendEvent(name: 'status', value: value, descriptionText: descriptionText)
|
||||||
|
}
|
||||||
|
|
||||||
return refreshCmds + enrollResponse()
|
private getAccelerationResult(numValue) {
|
||||||
}
|
log.debug "Acceleration"
|
||||||
|
def name = "acceleration"
|
||||||
|
def value = numValue.endsWith("1") ? "active" : "inactive"
|
||||||
|
def linkText = getLinkText(device)
|
||||||
|
def descriptionText = "$linkText was $value"
|
||||||
|
def isStateChange = isStateChange(device, name, value)
|
||||||
|
[
|
||||||
|
name: name,
|
||||||
|
value: value,
|
||||||
|
descriptionText: descriptionText,
|
||||||
|
isStateChange: isStateChange
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
def configure() {
|
def refresh() {
|
||||||
// Device-Watch allows 3 check-in misses from device (plus 1 min lag time)
|
log.debug "Refreshing Values "
|
||||||
// enrolls with default periodic reporting until newer 5 min interval is confirmed
|
def refreshCmds = [
|
||||||
sendEvent(name: "checkInterval", value: 3 * 60 * 60 + 1 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
|
|
||||||
|
/* sensitivity - default value (8) */
|
||||||
|
|
||||||
|
"zcl mfg-code 0x104E", "delay 200",
|
||||||
|
"zcl global write 0xFC02 0 0x20 {02}", "delay 200",
|
||||||
|
"send 0x${device.deviceNetworkId} 1 1", "delay 400",
|
||||||
|
|
||||||
log.debug "Configuring Reporting"
|
"st rattr 0x${device.deviceNetworkId} 1 0x402 0", "delay 200",
|
||||||
|
"st rattr 0x${device.deviceNetworkId} 1 1 0x20", "delay 200",
|
||||||
|
|
||||||
// temperature minReportTime 30 seconds, maxReportTime 5 min. Reporting interval if no activity
|
"zcl mfg-code 0x104E", "delay 200",
|
||||||
// battery minReport 30 seconds, maxReportTime 6 hrs by default
|
"zcl global read 0xFC02 0x0010",
|
||||||
def configCmds = zigbee.batteryConfig() +
|
"send 0x${device.deviceNetworkId} 1 1","delay 400",
|
||||||
zigbee.temperatureConfig(30, 300) +
|
|
||||||
zigbee.configureReporting(0xFC02, 0x0010, 0x18, 10, 3600, 0x01, [mfgCode: manufacturerCode]) +
|
"zcl mfg-code 0x104E", "delay 200",
|
||||||
zigbee.configureReporting(0xFC02, 0x0012, 0x29, 1, 3600, 0x0001, [mfgCode: manufacturerCode]) +
|
"zcl global read 0xFC02 0x0012",
|
||||||
zigbee.configureReporting(0xFC02, 0x0013, 0x29, 1, 3600, 0x0001, [mfgCode: manufacturerCode]) +
|
"send 0x${device.deviceNetworkId} 1 1","delay 400",
|
||||||
zigbee.configureReporting(0xFC02, 0x0014, 0x29, 1, 3600, 0x0001, [mfgCode: manufacturerCode])
|
|
||||||
|
"zcl mfg-code 0x104E", "delay 200",
|
||||||
|
"zcl global read 0xFC02 0x0013",
|
||||||
|
"send 0x${device.deviceNetworkId} 1 1","delay 400",
|
||||||
|
|
||||||
|
"zcl mfg-code 0x104E", "delay 200",
|
||||||
|
"zcl global read 0xFC02 0x0014",
|
||||||
|
"send 0x${device.deviceNetworkId} 1 1", "delay 400"
|
||||||
|
]
|
||||||
|
|
||||||
return refresh() + configCmds
|
return refreshCmds + enrollResponse()
|
||||||
|
}
|
||||||
|
|
||||||
|
def configure() {
|
||||||
|
|
||||||
|
String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
|
||||||
|
log.debug "Configuring Reporting"
|
||||||
|
|
||||||
|
def configCmds = [
|
||||||
|
|
||||||
|
"zcl global write 0x500 0x10 0xf0 {${zigbeeEui}}", "delay 200",
|
||||||
|
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
|
||||||
|
|
||||||
|
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 1 {${device.zigbeeId}} {}", "delay 200",
|
||||||
|
"zcl global send-me-a-report 1 0x20 0x20 30 21600 {01}", //checkin time 6 hrs
|
||||||
|
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
|
||||||
|
|
||||||
|
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 0x402 {${device.zigbeeId}} {}", "delay 200",
|
||||||
|
"zcl global send-me-a-report 0x402 0 0x29 30 3600 {6400}",
|
||||||
|
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
|
||||||
|
|
||||||
|
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 0xFC02 {${device.zigbeeId}} {}", "delay 200",
|
||||||
|
"zcl mfg-code 0x104E",
|
||||||
|
"zcl global send-me-a-report 0xFC02 0x0010 0x18 10 3600 {01}",
|
||||||
|
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
|
||||||
|
|
||||||
|
"zcl mfg-code 0x104E",
|
||||||
|
"zcl global send-me-a-report 0xFC02 0x0012 0x29 1 3600 {01}",
|
||||||
|
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
|
||||||
|
|
||||||
|
"zcl mfg-code 0x104E",
|
||||||
|
"zcl global send-me-a-report 0xFC02 0x0013 0x29 1 3600 {01}",
|
||||||
|
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
|
||||||
|
|
||||||
|
"zcl mfg-code 0x104E",
|
||||||
|
"zcl global send-me-a-report 0xFC02 0x0014 0x29 1 3600 {01}",
|
||||||
|
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500"
|
||||||
|
|
||||||
|
]
|
||||||
|
|
||||||
|
return configCmds + refresh()
|
||||||
}
|
}
|
||||||
|
|
||||||
private getEndpointId() {
|
private getEndpointId() {
|
||||||
@@ -442,43 +442,44 @@ 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 z = hexToSignedInt(description[0..3])
|
log.debug "parseAxis"
|
||||||
def y = hexToSignedInt(description[10..13])
|
def xyzResults = [x: 0, y: 0, z: 0]
|
||||||
def x = hexToSignedInt(description[20..23])
|
def parts = description.split("2900")
|
||||||
def xyzResults = [x: x, y: y, z: z]
|
parts[0] = "12" + parts[0]
|
||||||
|
parts.each { part ->
|
||||||
if (device.getDataValue("manufacturer") == "SmartThings") {
|
part = part.trim()
|
||||||
// This mapping matches the current behavior of the Device Handler for the Centralite sensors
|
if (part.startsWith("12")) {
|
||||||
xyzResults.x = z
|
def unsignedX = hexToInt(part.split("12")[1].trim())
|
||||||
xyzResults.y = y
|
def signedX = unsignedX > 32767 ? unsignedX - 65536 : unsignedX
|
||||||
xyzResults.z = -x
|
xyzResults.x = signedX
|
||||||
} else {
|
log.debug "X Part: ${signedX}"
|
||||||
// The axises reported by the Device Handler differ from the axises reported by the sensor
|
}
|
||||||
// This may change in the future
|
else if (part.startsWith("13")) {
|
||||||
xyzResults.x = z
|
def unsignedY = hexToInt(part.split("13")[1].trim())
|
||||||
xyzResults.y = x
|
def signedY = unsignedY > 32767 ? unsignedY - 65536 : unsignedY
|
||||||
xyzResults.z = y
|
xyzResults.y = signedY
|
||||||
}
|
log.debug "Y Part: ${signedY}"
|
||||||
|
}
|
||||||
log.debug "parseAxis -- ${xyzResults}"
|
else if (part.startsWith("14")) {
|
||||||
|
def unsignedZ = hexToInt(part.split("14")[1].trim())
|
||||||
if (garageSensor == "Yes")
|
def signedZ = unsignedZ > 32767 ? unsignedZ - 65536 : unsignedZ
|
||||||
garageEvent(xyzResults.z)
|
xyzResults.z = signedZ
|
||||||
|
log.debug "Z Part: ${signedZ}"
|
||||||
|
if (garageSensor == "Yes")
|
||||||
|
garageEvent(signedZ)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
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
|
||||||
@@ -492,9 +493,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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -517,14 +519,6 @@ private Map getXyzResult(results, description) {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
private getManufacturerCode() {
|
|
||||||
if (device.getDataValue("manufacturer") == "SmartThings") {
|
|
||||||
return "0x110A"
|
|
||||||
} else {
|
|
||||||
return "0x104E"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private hexToInt(value) {
|
private hexToInt(value) {
|
||||||
new BigInteger(value, 16)
|
new BigInteger(value, 16)
|
||||||
}
|
}
|
||||||
@@ -550,3 +544,4 @@ private byte[] reverseArray(byte[] array) {
|
|||||||
}
|
}
|
||||||
return array
|
return array
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,8 +22,6 @@ metadata {
|
|||||||
capability "Battery"
|
capability "Battery"
|
||||||
|
|
||||||
fingerprint profileId: "FC01", deviceId: "0139"
|
fingerprint profileId: "FC01", deviceId: "0139"
|
||||||
|
|
||||||
attribute "status", "string"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
simulator {
|
simulator {
|
||||||
@@ -45,8 +43,8 @@ metadata {
|
|||||||
}
|
}
|
||||||
|
|
||||||
preferences {
|
preferences {
|
||||||
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 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: "Temperature Offset", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false
|
||||||
}
|
}
|
||||||
|
|
||||||
tiles(scale: 2) {
|
tiles(scale: 2) {
|
||||||
@@ -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,21 +13,18 @@
|
|||||||
* 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
|
|
||||||
import physicalgraph.zigbee.clusters.iaszone.ZoneStatus
|
|
||||||
|
|
||||||
metadata {
|
metadata {
|
||||||
definition (name: "SmartSense Open/Closed Accelerometer Sensor", namespace: "smartthings", author: "SmartThings", category: "C2") {
|
definition (name: "SmartSense Open/Closed Accelerometer Sensor", namespace: "smartthings", author: "SmartThings") {
|
||||||
capability "Battery"
|
capability "Battery"
|
||||||
capability "Configuration"
|
capability "Configuration"
|
||||||
capability "Contact Sensor"
|
capability "Contact Sensor"
|
||||||
capability "Acceleration Sensor"
|
capability "Acceleration Sensor"
|
||||||
capability "Refresh"
|
capability "Refresh"
|
||||||
capability "Temperature Measurement"
|
capability "Temperature Measurement"
|
||||||
capability "Health Check"
|
command "enrollResponse"
|
||||||
capability "Sensor"
|
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"
|
||||||
command "enrollResponse"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
simulator {
|
simulator {
|
||||||
@@ -35,8 +32,8 @@ import physicalgraph.zigbee.clusters.iaszone.ZoneStatus
|
|||||||
}
|
}
|
||||||
|
|
||||||
preferences {
|
preferences {
|
||||||
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 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: "Temperature Offset", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false
|
||||||
}
|
}
|
||||||
|
|
||||||
tiles(scale: 2) {
|
tiles(scale: 2) {
|
||||||
@@ -173,9 +170,40 @@ private Map parseCustomMessage(String description) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Map parseIasMessage(String description) {
|
private Map parseIasMessage(String description) {
|
||||||
ZoneStatus zs = zigbee.parseZoneStatus(description)
|
List parsedMsg = description.split(' ')
|
||||||
|
String msgCode = parsedMsg[2]
|
||||||
|
|
||||||
return zs.isAlarm1Set() ? getContactResult('open') : getContactResult('closed')
|
Map resultMap = [:]
|
||||||
|
switch(msgCode) {
|
||||||
|
case '0x0020': // Closed/No Motion/Dry
|
||||||
|
resultMap = getContactResult('closed')
|
||||||
|
break
|
||||||
|
|
||||||
|
case '0x0021': // Open/Motion/Wet
|
||||||
|
resultMap = getContactResult('open')
|
||||||
|
break
|
||||||
|
|
||||||
|
case '0x0022': // Tamper Alarm
|
||||||
|
break
|
||||||
|
|
||||||
|
case '0x0023': // Battery Alarm
|
||||||
|
break
|
||||||
|
|
||||||
|
case '0x0024': // Supervision Report
|
||||||
|
resultMap = getContactResult('closed')
|
||||||
|
break
|
||||||
|
|
||||||
|
case '0x0025': // Restore Report
|
||||||
|
resultMap = getContactResult('open')
|
||||||
|
break
|
||||||
|
|
||||||
|
case '0x0026': // Trouble/Failure
|
||||||
|
break
|
||||||
|
|
||||||
|
case '0x0028': // Test Mode
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return resultMap
|
||||||
}
|
}
|
||||||
|
|
||||||
def getTemperature(value) {
|
def getTemperature(value) {
|
||||||
@@ -197,18 +225,14 @@ 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 {
|
||||||
def minVolts = 2.1
|
def minVolts = 2.1
|
||||||
def maxVolts = 3.0
|
def maxVolts = 3.0
|
||||||
def pct = (volts - minVolts) / (maxVolts - minVolts)
|
def pct = (volts - minVolts) / (maxVolts - minVolts)
|
||||||
def roundedPct = Math.round(pct * 100)
|
result.value = Math.min(100, (int) pct * 100)
|
||||||
if (roundedPct <= 0)
|
|
||||||
roundedPct = 1
|
|
||||||
result.value = Math.min(100, roundedPct)
|
|
||||||
result.descriptionText = "${linkText} battery was ${result.value}%"
|
result.descriptionText = "${linkText} battery was ${result.value}%"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -225,10 +249,9 @@ def getTemperature(value) {
|
|||||||
}
|
}
|
||||||
def descriptionText = "${linkText} was ${value}°${temperatureScale}"
|
def descriptionText = "${linkText} was ${value}°${temperatureScale}"
|
||||||
return [
|
return [
|
||||||
name: 'temperature',
|
name: 'temperature',
|
||||||
value: value,
|
value: value,
|
||||||
descriptionText: descriptionText,
|
descriptionText: descriptionText
|
||||||
unit: temperatureScale
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -275,10 +298,13 @@ def getTemperature(value) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
def configure() {
|
def configure() {
|
||||||
sendEvent(name: "checkInterval", value: 7200, displayed: false)
|
|
||||||
|
|
||||||
|
String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
|
||||||
log.debug "Configuring Reporting, IAS CIE, and Bindings."
|
log.debug "Configuring Reporting, IAS CIE, and Bindings."
|
||||||
def configCmds = [
|
def configCmds = [
|
||||||
|
"zcl global write 0x500 0x10 0xf0 {${zigbeeEui}}", "delay 200",
|
||||||
|
"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}", //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",
|
||||||
@@ -291,7 +317,7 @@ def configure() {
|
|||||||
"zcl global send-me-a-report 0xFC02 2 0x18 30 3600 {01}",
|
"zcl global send-me-a-report 0xFC02 2 0x18 30 3600 {01}",
|
||||||
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500"
|
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500"
|
||||||
]
|
]
|
||||||
return refresh() + configCmds // send refresh cmds as part of config
|
return configCmds + refresh() // send refresh cmds as part of config
|
||||||
}
|
}
|
||||||
|
|
||||||
def enrollResponse() {
|
def enrollResponse() {
|
||||||
|
|||||||
@@ -1,2 +0,0 @@
|
|||||||
.st-ignore
|
|
||||||
README.md
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
# Smartsense Open/Closed Sensor
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Works with:
|
|
||||||
|
|
||||||
* [Samsung SmartThings Open/Closed Sensor](https://shop.smartthings.com/#!/packs/smartsense-open-closed-sensor/)
|
|
||||||
|
|
||||||
## Table of contents
|
|
||||||
|
|
||||||
* [Capabilities](#capabilities)
|
|
||||||
* [Health](#device-health)
|
|
||||||
* [Battery](#battery-specification)
|
|
||||||
|
|
||||||
## Capabilities
|
|
||||||
|
|
||||||
* **Configuration** - _configure()_ command called when device is installed or device preferences updated
|
|
||||||
* **Battery** - defines device uses a battery
|
|
||||||
* **Contact Sensor** - can detect contact (possible values: open,closed)
|
|
||||||
* **Refresh** - _refresh()_ command for status updates
|
|
||||||
* **Temperature Measurement** - defines device measures current temperature
|
|
||||||
* **Health Check** - indicates ability to get device health notifications
|
|
||||||
* **Sensor** - detects sensor events
|
|
||||||
|
|
||||||
## Device Health
|
|
||||||
|
|
||||||
A Category C2 open/closed sensor with maxReportTime of 5 mins.
|
|
||||||
Check-in interval is double the value of maxReportTime.
|
|
||||||
This gives the device twice the amount of time to respond before it is marked as offline.
|
|
||||||
Check-in interval = 12 mins
|
|
||||||
|
|
||||||
## Battery Specification
|
|
||||||
|
|
||||||
One CR2 3V battery required.
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
If the sensor doesn't pair when trying from the SmartThings mobile app, it is possible that the sensor is out of range.
|
|
||||||
Pairing needs to be tried again by placing the sensor closer to the hub.
|
|
||||||
Instructions related to pairing, resetting and removing the sensor from SmartThings can be found in the following link:
|
|
||||||
* [SmartSense Open/Closed Sensor Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/202836844-SmartSense-Open-Closed-Sensor)
|
|
||||||
@@ -13,35 +13,31 @@
|
|||||||
* for the specific language governing permissions and limitations under the License.
|
* for the specific language governing permissions and limitations under the License.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
import physicalgraph.zigbee.clusters.iaszone.ZoneStatus
|
|
||||||
|
|
||||||
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"
|
||||||
capability "Health Check"
|
|
||||||
capability "Sensor"
|
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 {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
preferences {
|
preferences {
|
||||||
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 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: "Temperature Offset", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false
|
||||||
}
|
}
|
||||||
|
|
||||||
tiles(scale: 2) {
|
tiles(scale: 2) {
|
||||||
multiAttributeTile(name:"contact", type: "generic", width: 6, height: 4){
|
multiAttributeTile(name:"contact", type: "generic", width: 6, height: 4){
|
||||||
tileAttribute ("device.contact", key: "PRIMARY_CONTROL") {
|
tileAttribute ("device.contact", key: "PRIMARY_CONTROL") {
|
||||||
@@ -74,10 +70,10 @@ metadata {
|
|||||||
details(["contact","temperature","battery","refresh"])
|
details(["contact","temperature","battery","refresh"])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def parse(String description) {
|
def parse(String description) {
|
||||||
log.debug "description: $description"
|
log.debug "description: $description"
|
||||||
|
|
||||||
Map map = [:]
|
Map map = [:]
|
||||||
if (description?.startsWith('catchall:')) {
|
if (description?.startsWith('catchall:')) {
|
||||||
map = parseCatchAllMessage(description)
|
map = parseCatchAllMessage(description)
|
||||||
@@ -91,10 +87,10 @@ def parse(String description) {
|
|||||||
else if (description?.startsWith('zone status')) {
|
else if (description?.startsWith('zone status')) {
|
||||||
map = parseIasMessage(description)
|
map = parseIasMessage(description)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.debug "Parse returned $map"
|
log.debug "Parse returned $map"
|
||||||
def result = map ? createEvent(map) : null
|
def result = map ? createEvent(map) : null
|
||||||
|
|
||||||
if (description?.startsWith('enroll request')) {
|
if (description?.startsWith('enroll request')) {
|
||||||
List cmds = enrollResponse()
|
List cmds = enrollResponse()
|
||||||
log.debug "enroll response: ${cmds}"
|
log.debug "enroll response: ${cmds}"
|
||||||
@@ -102,35 +98,22 @@ def parse(String description) {
|
|||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
private Map parseCatchAllMessage(String description) {
|
private Map parseCatchAllMessage(String description) {
|
||||||
Map resultMap = [:]
|
Map resultMap = [:]
|
||||||
def cluster = zigbee.parse(description)
|
def cluster = zigbee.parse(description)
|
||||||
if (shouldProcessMessage(cluster)) {
|
if (shouldProcessMessage(cluster)) {
|
||||||
switch(cluster.clusterId) {
|
switch(cluster.clusterId) {
|
||||||
case 0x0001:
|
case 0x0001:
|
||||||
// 0x07 - configure reporting
|
resultMap = getBatteryResult(cluster.data.last())
|
||||||
if (cluster.command != 0x07) {
|
|
||||||
resultMap = getBatteryResult(cluster.data.last())
|
|
||||||
}
|
|
||||||
break
|
break
|
||||||
|
|
||||||
case 0x0402:
|
case 0x0402:
|
||||||
if (cluster.command == 0x07){
|
log.debug 'TEMP'
|
||||||
if (cluster.data[0] == 0x00) {
|
// temp is last 2 data values. reverse to swap endian
|
||||||
log.debug "TEMP REPORTING CONFIG RESPONSE" + cluster
|
String temp = cluster.data[-2..-1].reverse().collect { cluster.hex1(it) }.join()
|
||||||
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
|
def value = getTemperature(temp)
|
||||||
}
|
resultMap = getTemperatureResult(value)
|
||||||
else {
|
|
||||||
log.warn "TEMP REPORTING CONFIG FAILED- error code:${cluster.data[0]}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// temp is last 2 data values. reverse to swap endian
|
|
||||||
String temp = cluster.data[-2..-1].reverse().collect { cluster.hex1(it) }.join()
|
|
||||||
def value = getTemperature(temp)
|
|
||||||
resultMap = getTemperatureResult(value)
|
|
||||||
}
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -140,8 +123,10 @@ private Map parseCatchAllMessage(String description) {
|
|||||||
|
|
||||||
private boolean shouldProcessMessage(cluster) {
|
private boolean shouldProcessMessage(cluster) {
|
||||||
// 0x0B is default response indicating message got through
|
// 0x0B is default response indicating message got through
|
||||||
boolean ignoredMessage = cluster.profileId != 0x0104 ||
|
// 0x07 is bind message
|
||||||
|
boolean ignoredMessage = cluster.profileId != 0x0104 ||
|
||||||
cluster.command == 0x0B ||
|
cluster.command == 0x0B ||
|
||||||
|
cluster.command == 0x07 ||
|
||||||
(cluster.data.size() > 0 && cluster.data.first() == 0x3e)
|
(cluster.data.size() > 0 && cluster.data.first() == 0x3e)
|
||||||
return !ignoredMessage
|
return !ignoredMessage
|
||||||
}
|
}
|
||||||
@@ -149,14 +134,14 @@ private boolean shouldProcessMessage(cluster) {
|
|||||||
private int getHumidity(value) {
|
private int getHumidity(value) {
|
||||||
return Math.round(Double.parseDouble(value))
|
return Math.round(Double.parseDouble(value))
|
||||||
}
|
}
|
||||||
|
|
||||||
private Map parseReportAttributeMessage(String description) {
|
private Map parseReportAttributeMessage(String description) {
|
||||||
Map descMap = (description - "read attr - ").split(",").inject([:]) { map, param ->
|
Map descMap = (description - "read attr - ").split(",").inject([:]) { map, param ->
|
||||||
def nameAndValue = param.split(":")
|
def nameAndValue = param.split(":")
|
||||||
map += [(nameAndValue[0].trim()):nameAndValue[1].trim()]
|
map += [(nameAndValue[0].trim()):nameAndValue[1].trim()]
|
||||||
}
|
}
|
||||||
log.debug "Desc Map: $descMap"
|
log.debug "Desc Map: $descMap"
|
||||||
|
|
||||||
Map resultMap = [:]
|
Map resultMap = [:]
|
||||||
if (descMap.cluster == "0402" && descMap.attrId == "0000") {
|
if (descMap.cluster == "0402" && descMap.attrId == "0000") {
|
||||||
def value = getTemperature(descMap.value)
|
def value = getTemperature(descMap.value)
|
||||||
@@ -165,10 +150,10 @@ private Map parseReportAttributeMessage(String description) {
|
|||||||
else if (descMap.cluster == "0001" && descMap.attrId == "0020") {
|
else if (descMap.cluster == "0001" && descMap.attrId == "0020") {
|
||||||
resultMap = getBatteryResult(Integer.parseInt(descMap.value, 16))
|
resultMap = getBatteryResult(Integer.parseInt(descMap.value, 16))
|
||||||
}
|
}
|
||||||
|
|
||||||
return resultMap
|
return resultMap
|
||||||
}
|
}
|
||||||
|
|
||||||
private Map parseCustomMessage(String description) {
|
private Map parseCustomMessage(String description) {
|
||||||
Map resultMap = [:]
|
Map resultMap = [:]
|
||||||
if (description?.startsWith('temperature: ')) {
|
if (description?.startsWith('temperature: ')) {
|
||||||
@@ -179,10 +164,42 @@ private Map parseCustomMessage(String description) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Map parseIasMessage(String description) {
|
private Map parseIasMessage(String description) {
|
||||||
ZoneStatus zs = zigbee.parseZoneStatus(description)
|
List parsedMsg = description.split(' ')
|
||||||
return zs.isAlarm1Set() ? getContactResult('open') : getContactResult('closed')
|
String msgCode = parsedMsg[2]
|
||||||
}
|
|
||||||
|
Map resultMap = [:]
|
||||||
|
switch(msgCode) {
|
||||||
|
case '0x0020': // Closed/No Motion/Dry
|
||||||
|
resultMap = getContactResult('closed')
|
||||||
|
break
|
||||||
|
|
||||||
|
case '0x0021': // Open/Motion/Wet
|
||||||
|
resultMap = getContactResult('open')
|
||||||
|
break
|
||||||
|
|
||||||
|
case '0x0022': // Tamper Alarm
|
||||||
|
break
|
||||||
|
|
||||||
|
case '0x0023': // Battery Alarm
|
||||||
|
break
|
||||||
|
|
||||||
|
case '0x0024': // Supervision Report
|
||||||
|
resultMap = getContactResult('closed')
|
||||||
|
break
|
||||||
|
|
||||||
|
case '0x0025': // Restore Report
|
||||||
|
resultMap = getContactResult('open')
|
||||||
|
break
|
||||||
|
|
||||||
|
case '0x0026': // Trouble/Failure
|
||||||
|
break
|
||||||
|
|
||||||
|
case '0x0028': // Test Mode
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return resultMap
|
||||||
|
}
|
||||||
|
|
||||||
def getTemperature(value) {
|
def getTemperature(value) {
|
||||||
def celsius = Integer.parseInt(value, 16).shortValue() / 100
|
def celsius = Integer.parseInt(value, 16).shortValue() / 100
|
||||||
if(getTemperatureScale() == "C"){
|
if(getTemperatureScale() == "C"){
|
||||||
@@ -195,25 +212,21 @@ def getTemperature(value) {
|
|||||||
private Map getBatteryResult(rawValue) {
|
private Map getBatteryResult(rawValue) {
|
||||||
log.debug 'Battery'
|
log.debug 'Battery'
|
||||||
def linkText = getLinkText(device)
|
def linkText = getLinkText(device)
|
||||||
|
|
||||||
def result = [
|
def result = [
|
||||||
name: 'battery'
|
name: 'battery'
|
||||||
]
|
]
|
||||||
|
|
||||||
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 {
|
||||||
def minVolts = 2.1
|
def minVolts = 2.1
|
||||||
def maxVolts = 3.0
|
def maxVolts = 3.0
|
||||||
def pct = (volts - minVolts) / (maxVolts - minVolts)
|
def pct = (volts - minVolts) / (maxVolts - minVolts)
|
||||||
def roundedPct = Math.round(pct * 100)
|
result.value = Math.min(100, (int) pct * 100)
|
||||||
if (roundedPct <= 0)
|
|
||||||
roundedPct = 1
|
|
||||||
result.value = Math.min(100, roundedPct)
|
|
||||||
result.descriptionText = "${linkText} battery was ${result.value}%"
|
result.descriptionText = "${linkText} battery was ${result.value}%"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -232,8 +245,7 @@ private Map getTemperatureResult(value) {
|
|||||||
return [
|
return [
|
||||||
name: 'temperature',
|
name: 'temperature',
|
||||||
value: value,
|
value: value,
|
||||||
descriptionText: descriptionText,
|
descriptionText: descriptionText
|
||||||
unit: temperatureScale
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -248,13 +260,6 @@ private Map getContactResult(value) {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* PING is used by Device-Watch in attempt to reach the Device
|
|
||||||
* */
|
|
||||||
def ping() {
|
|
||||||
return zigbee.readAttribute(0x001, 0x0020) // Read the Battery Level
|
|
||||||
}
|
|
||||||
|
|
||||||
def refresh() {
|
def refresh() {
|
||||||
log.debug "Refreshing Temperature and Battery"
|
log.debug "Refreshing Temperature and Battery"
|
||||||
def refreshCmds = [
|
def refreshCmds = [
|
||||||
@@ -266,15 +271,22 @@ def refresh() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
def configure() {
|
def configure() {
|
||||||
// Device-Watch allows 3 check-in misses from device (plus 1 min lag time)
|
|
||||||
// enrolls with default periodic reporting until newer 5 min interval is confirmed
|
|
||||||
sendEvent(name: "checkInterval", value: 3 * 60 * 60 + 1 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
|
|
||||||
|
|
||||||
|
String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
|
||||||
log.debug "Configuring Reporting, IAS CIE, and Bindings."
|
log.debug "Configuring Reporting, IAS CIE, and Bindings."
|
||||||
|
def configCmds = [
|
||||||
|
"zcl global write 0x500 0x10 0xf0 {${zigbeeEui}}", "delay 200",
|
||||||
|
"send 0x${device.deviceNetworkId} 1 1", "delay 500",
|
||||||
|
|
||||||
// temperature minReportTime 30 seconds, maxReportTime 5 min. Reporting interval if no activity
|
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 1 {${device.zigbeeId}} {}", "delay 500",
|
||||||
// battery minReport 30 seconds, maxReportTime 6 hrs by default
|
"zcl global send-me-a-report 1 0x20 0x20 30 21600 {01}", //checkin time 6 hrs
|
||||||
return refresh() + zigbee.batteryConfig() + zigbee.temperatureConfig(30, 300) // send refresh cmds as part of config
|
"send 0x${device.deviceNetworkId} 1 1", "delay 500",
|
||||||
|
|
||||||
|
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 0x402 {${device.zigbeeId}} {}", "delay 500",
|
||||||
|
"zcl global send-me-a-report 0x402 0 0x29 30 3600 {6400}",
|
||||||
|
"send 0x${device.deviceNetworkId} 1 1", "delay 500"
|
||||||
|
]
|
||||||
|
return configCmds + refresh() // send refresh cmds as part of config
|
||||||
}
|
}
|
||||||
|
|
||||||
def enrollResponse() {
|
def enrollResponse() {
|
||||||
|
|||||||
@@ -1,2 +0,0 @@
|
|||||||
.st-ignore
|
|
||||||
README.md
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
# SmartSense Temp/Humidity Sensor
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Works with:
|
|
||||||
|
|
||||||
* [Samsung SmartSense Temp/Humidity Sensor](https://shop.smartthings.com/#!/products/smartsense-temp-humidity-sensor)
|
|
||||||
|
|
||||||
## Table of contents
|
|
||||||
|
|
||||||
* [Capabilities](#capabilities)
|
|
||||||
* [Health](#device-health)
|
|
||||||
* [Battery](#battery-specification)
|
|
||||||
|
|
||||||
## Capabilities
|
|
||||||
|
|
||||||
* **Configuration** - _configure()_ command called when device is installed or device preferences updated
|
|
||||||
* **Battery** - defines device uses a battery
|
|
||||||
* **Relative Humidity Measurement** - defines device measures relative humidity
|
|
||||||
* **Refresh** - _refresh()_ command for status updates
|
|
||||||
* **Temperature Measurement** - defines device measures current temperature
|
|
||||||
* **Health Check** - indicates ability to get device health notifications
|
|
||||||
* **Sensor** - detects sensor events
|
|
||||||
|
|
||||||
## Device Health
|
|
||||||
|
|
||||||
A Category C2 SmartSense Temp/Humidity Sensor with maxReportTime of 5 mins.
|
|
||||||
Check-in interval is double the value of maxReportTime.
|
|
||||||
This gives the device twice the amount of time to respond before it is marked as offline.
|
|
||||||
Check-in interval = 12 mins
|
|
||||||
|
|
||||||
## Battery Specification
|
|
||||||
|
|
||||||
One CR2 battery is required.
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
If the sensor doesn't pair when trying from the SmartThings mobile app, it is possible that the sensor is out of range.
|
|
||||||
Pairing needs to be tried by placing the sensor closer to the hub.
|
|
||||||
Instructions related to pairing, resetting and removing the sensor from SmartThings can be found in the following link:
|
|
||||||
* [Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/203040294)
|
|
||||||
@@ -20,8 +20,6 @@ metadata {
|
|||||||
capability "Refresh"
|
capability "Refresh"
|
||||||
capability "Temperature Measurement"
|
capability "Temperature Measurement"
|
||||||
capability "Relative Humidity Measurement"
|
capability "Relative Humidity Measurement"
|
||||||
capability "Health Check"
|
|
||||||
capability "Sensor"
|
|
||||||
|
|
||||||
fingerprint endpointId: "01", inClusters: "0001,0003,0020,0402,0B05,FC45", outClusters: "0019,0003"
|
fingerprint endpointId: "01", inClusters: "0001,0003,0020,0402,0B05,FC45", outClusters: "0019,0003"
|
||||||
}
|
}
|
||||||
@@ -35,8 +33,8 @@ metadata {
|
|||||||
}
|
}
|
||||||
|
|
||||||
preferences {
|
preferences {
|
||||||
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 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: "Temperature Offset", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false
|
||||||
}
|
}
|
||||||
|
|
||||||
tiles(scale: 2) {
|
tiles(scale: 2) {
|
||||||
@@ -93,37 +91,20 @@ private Map parseCatchAllMessage(String description) {
|
|||||||
if (shouldProcessMessage(cluster)) {
|
if (shouldProcessMessage(cluster)) {
|
||||||
switch(cluster.clusterId) {
|
switch(cluster.clusterId) {
|
||||||
case 0x0001:
|
case 0x0001:
|
||||||
// 0x07 - configure reporting
|
resultMap = getBatteryResult(cluster.data.last())
|
||||||
if (cluster.command != 0x07) {
|
|
||||||
resultMap = getBatteryResult(cluster.data.last())
|
|
||||||
}
|
|
||||||
break
|
break
|
||||||
|
|
||||||
case 0x0402:
|
case 0x0402:
|
||||||
if (cluster.command == 0x07) {
|
// temp is last 2 data values. reverse to swap endian
|
||||||
if (cluster.data[0] == 0x00){
|
String temp = cluster.data[-2..-1].reverse().collect { cluster.hex1(it) }.join()
|
||||||
log.debug "TEMP REPORTING CONFIG RESPONSE" + cluster
|
def value = getTemperature(temp)
|
||||||
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
|
resultMap = getTemperatureResult(value)
|
||||||
}
|
break
|
||||||
else {
|
|
||||||
log.warn "TEMP REPORTING CONFIG FAILED- error code:${cluster.data[0]}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// temp is last 2 data values. reverse to swap endian
|
|
||||||
String temp = cluster.data[-2..-1].reverse().collect { cluster.hex1(it) }.join()
|
|
||||||
def value = getTemperature(temp)
|
|
||||||
resultMap = getTemperatureResult(value)
|
|
||||||
}
|
|
||||||
break
|
|
||||||
|
|
||||||
case 0xFC45:
|
case 0xFC45:
|
||||||
// 0x07 - configure reporting
|
String pctStr = cluster.data[-1, -2].collect { Integer.toHexString(it) }.join('')
|
||||||
if (cluster.command != 0x07) {
|
String display = Math.round(Integer.valueOf(pctStr, 16) / 100)
|
||||||
String pctStr = cluster.data[-1, -2].collect { Integer.toHexString(it) }.join('')
|
resultMap = getHumidityResult(display)
|
||||||
String display = Math.round(Integer.valueOf(pctStr, 16) / 100)
|
|
||||||
resultMap = getHumidityResult(display)
|
|
||||||
}
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -133,8 +114,10 @@ private Map parseCatchAllMessage(String description) {
|
|||||||
|
|
||||||
private boolean shouldProcessMessage(cluster) {
|
private boolean shouldProcessMessage(cluster) {
|
||||||
// 0x0B is default response indicating message got through
|
// 0x0B is default response indicating message got through
|
||||||
|
// 0x07 is bind message
|
||||||
boolean ignoredMessage = cluster.profileId != 0x0104 ||
|
boolean ignoredMessage = cluster.profileId != 0x0104 ||
|
||||||
cluster.command == 0x0B ||
|
cluster.command == 0x0B ||
|
||||||
|
cluster.command == 0x07 ||
|
||||||
(cluster.data.size() > 0 && cluster.data.first() == 0x3e)
|
(cluster.data.size() > 0 && cluster.data.first() == 0x3e)
|
||||||
return !ignoredMessage
|
return !ignoredMessage
|
||||||
}
|
}
|
||||||
@@ -213,18 +196,14 @@ 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 {
|
||||||
def minVolts = 2.1
|
def minVolts = 2.1
|
||||||
def maxVolts = 3.0
|
def maxVolts = 3.0
|
||||||
def pct = (volts - minVolts) / (maxVolts - minVolts)
|
def pct = (volts - minVolts) / (maxVolts - minVolts)
|
||||||
def roundedPct = Math.round(pct * 100)
|
result.value = Math.min(100, (int) pct * 100)
|
||||||
if (roundedPct <= 0)
|
|
||||||
roundedPct = 1
|
|
||||||
result.value = Math.min(100, roundedPct)
|
|
||||||
result.descriptionText = "${linkText} battery was ${result.value}%"
|
result.descriptionText = "${linkText} battery was ${result.value}%"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -243,47 +222,50 @@ private Map getTemperatureResult(value) {
|
|||||||
return [
|
return [
|
||||||
name: 'temperature',
|
name: 'temperature',
|
||||||
value: value,
|
value: value,
|
||||||
descriptionText: descriptionText,
|
descriptionText: descriptionText
|
||||||
unit: temperatureScale
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
private Map getHumidityResult(value) {
|
private Map getHumidityResult(value) {
|
||||||
log.debug 'Humidity'
|
log.debug 'Humidity'
|
||||||
return value ? [name: 'humidity', value: value, unit: '%'] : [:]
|
return [
|
||||||
}
|
name: 'humidity',
|
||||||
|
value: value,
|
||||||
/**
|
unit: '%'
|
||||||
* PING is used by Device-Watch in attempt to reach the Device
|
]
|
||||||
* */
|
|
||||||
def ping() {
|
|
||||||
return zigbee.readAttribute(0x001, 0x0020) // Read the Battery Level
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def refresh()
|
def refresh()
|
||||||
{
|
{
|
||||||
log.debug "refresh temperature, humidity, and battery"
|
log.debug "refresh temperature, humidity, and battery"
|
||||||
return zigbee.readAttribute(0xFC45, 0x0000, ["mfgCode": 0xC2DF]) + // Original firmware
|
[
|
||||||
zigbee.readAttribute(0xFC45, 0x0000, ["mfgCode": 0x104E]) + // New firmware
|
|
||||||
zigbee.readAttribute(0x0402, 0x0000) +
|
"zcl mfg-code 0xC2DF", "delay 1000",
|
||||||
zigbee.readAttribute(0x0001, 0x0020)
|
"zcl global read 0xFC45 0", "delay 1000",
|
||||||
|
"send 0x${device.deviceNetworkId} 1 1", "delay 1000",
|
||||||
|
"st rattr 0x${device.deviceNetworkId} 1 0x402 0", "delay 200",
|
||||||
|
"st rattr 0x${device.deviceNetworkId} 1 1 0x20"
|
||||||
|
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
def configure() {
|
def configure() {
|
||||||
// Device-Watch allows 3 check-in misses from device (plus 1 min lag time)
|
|
||||||
// enrolls with default periodic reporting until newer 5 min interval is confirmed
|
|
||||||
sendEvent(name: "checkInterval", value: 3 * 60 * 60 + 1 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
|
|
||||||
|
|
||||||
log.debug "Configuring Reporting and Bindings."
|
log.debug "Configuring Reporting and Bindings."
|
||||||
def humidityConfigCmds = [
|
def configCmds = [
|
||||||
|
"zdo bind 0x${device.deviceNetworkId} 1 1 1 {${device.zigbeeId}} {}", "delay 500",
|
||||||
|
"zcl global send-me-a-report 1 0x20 0x20 30 21600 {01}", //checkin time 6 hrs
|
||||||
|
"send 0x${device.deviceNetworkId} 1 1", "delay 500",
|
||||||
|
|
||||||
|
"zdo bind 0x${device.deviceNetworkId} 1 1 0x402 {${device.zigbeeId}} {}", "delay 500",
|
||||||
|
"zcl global send-me-a-report 0x402 0 0x29 30 3600 {6400}",
|
||||||
|
"send 0x${device.deviceNetworkId} 1 1", "delay 500",
|
||||||
|
|
||||||
"zdo bind 0x${device.deviceNetworkId} 1 1 0xFC45 {${device.zigbeeId}} {}", "delay 500",
|
"zdo bind 0x${device.deviceNetworkId} 1 1 0xFC45 {${device.zigbeeId}} {}", "delay 500",
|
||||||
"zcl global send-me-a-report 0xFC45 0 0x29 30 3600 {6400}",
|
"zcl global send-me-a-report 0xFC45 0 0x29 30 3600 {6400}",
|
||||||
"send 0x${device.deviceNetworkId} 1 1", "delay 500"
|
"send 0x${device.deviceNetworkId} 1 1", "delay 500"
|
||||||
]
|
]
|
||||||
|
return configCmds + refresh() // send refresh cmds as part of config
|
||||||
// temperature minReportTime 30 seconds, maxReportTime 5 min. Reporting interval if no activity
|
|
||||||
// battery minReport 30 seconds, maxReportTime 6 hrs by default
|
|
||||||
return refresh() + humidityConfigCmds + zigbee.batteryConfig() + zigbee.temperatureConfig(30, 300) // send refresh cmds as part of config
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private hex(value) {
|
private hex(value) {
|
||||||
|
|||||||
@@ -45,8 +45,8 @@ metadata {
|
|||||||
}
|
}
|
||||||
|
|
||||||
preferences {
|
preferences {
|
||||||
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 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: "Temperature Offset", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false
|
||||||
}
|
}
|
||||||
|
|
||||||
tiles {
|
tiles {
|
||||||
|
|||||||
@@ -16,8 +16,6 @@
|
|||||||
metadata {
|
metadata {
|
||||||
definition (name: "Simulated Alarm", namespace: "smartthings/testing", author: "SmartThings") {
|
definition (name: "Simulated Alarm", namespace: "smartthings/testing", author: "SmartThings") {
|
||||||
capability "Alarm"
|
capability "Alarm"
|
||||||
capability "Sensor"
|
|
||||||
capability "Actuator"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
simulator {
|
simulator {
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
metadata {
|
metadata {
|
||||||
definition (name: "Simulated Color Control", namespace: "smartthings/testing", author: "SmartThings") {
|
definition (name: "Simulated Color Control", namespace: "smartthings/testing", author: "SmartThings") {
|
||||||
capability "Color Control"
|
capability "Color Control"
|
||||||
capability "Sensor"
|
|
||||||
capability "Actuator"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
simulator {
|
simulator {
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ metadata {
|
|||||||
// Automatically generated. Make future change here.
|
// Automatically generated. Make future change here.
|
||||||
definition (name: "Simulated Contact Sensor", namespace: "smartthings/testing", author: "bob") {
|
definition (name: "Simulated Contact Sensor", namespace: "smartthings/testing", author: "bob") {
|
||||||
capability "Contact Sensor"
|
capability "Contact Sensor"
|
||||||
capability "Sensor"
|
|
||||||
|
|
||||||
command "open"
|
command "open"
|
||||||
command "close"
|
command "close"
|
||||||
|
|||||||
@@ -15,8 +15,6 @@ metadata {
|
|||||||
// Automatically generated. Make future change here.
|
// Automatically generated. Make future change here.
|
||||||
definition (name: "Simulated Lock", namespace: "smartthings/testing", author: "bob") {
|
definition (name: "Simulated Lock", namespace: "smartthings/testing", author: "bob") {
|
||||||
capability "Lock"
|
capability "Lock"
|
||||||
capability "Sensor"
|
|
||||||
capability "Actuator"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Simulated lock
|
// Simulated lock
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ metadata {
|
|||||||
// Automatically generated. Make future change here.
|
// Automatically generated. Make future change here.
|
||||||
definition (name: "Simulated Motion Sensor", namespace: "smartthings/testing", author: "bob") {
|
definition (name: "Simulated Motion Sensor", namespace: "smartthings/testing", author: "bob") {
|
||||||
capability "Motion Sensor"
|
capability "Motion Sensor"
|
||||||
capability "Sensor"
|
|
||||||
|
|
||||||
command "active"
|
command "active"
|
||||||
command "inactive"
|
command "inactive"
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ metadata {
|
|||||||
// Automatically generated. Make future change here.
|
// Automatically generated. Make future change here.
|
||||||
definition (name: "Simulated Presence Sensor", namespace: "smartthings/testing", author: "bob") {
|
definition (name: "Simulated Presence Sensor", namespace: "smartthings/testing", author: "bob") {
|
||||||
capability "Presence Sensor"
|
capability "Presence Sensor"
|
||||||
capability "Sensor"
|
|
||||||
|
|
||||||
command "arrived"
|
command "arrived"
|
||||||
command "departed"
|
command "departed"
|
||||||
|
|||||||
@@ -16,8 +16,6 @@ metadata {
|
|||||||
definition (name: "Simulated Switch", namespace: "smartthings/testing", author: "bob") {
|
definition (name: "Simulated Switch", namespace: "smartthings/testing", author: "bob") {
|
||||||
capability "Switch"
|
capability "Switch"
|
||||||
capability "Relay Switch"
|
capability "Relay Switch"
|
||||||
capability "Sensor"
|
|
||||||
capability "Actuator"
|
|
||||||
|
|
||||||
command "onPhysical"
|
command "onPhysical"
|
||||||
command "offPhysical"
|
command "offPhysical"
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ metadata {
|
|||||||
definition (name: "Simulated Temperature Sensor", namespace: "smartthings/testing", author: "SmartThings") {
|
definition (name: "Simulated Temperature Sensor", namespace: "smartthings/testing", author: "SmartThings") {
|
||||||
capability "Temperature Measurement"
|
capability "Temperature Measurement"
|
||||||
capability "Switch Level"
|
capability "Switch Level"
|
||||||
capability "Sensor"
|
|
||||||
|
|
||||||
command "up"
|
command "up"
|
||||||
command "down"
|
command "down"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* Copyright 2015 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,9 +15,6 @@ metadata {
|
|||||||
// Automatically generated. Make future change here.
|
// Automatically generated. Make future change here.
|
||||||
definition (name: "Simulated Thermostat", namespace: "smartthings/testing", author: "SmartThings") {
|
definition (name: "Simulated Thermostat", namespace: "smartthings/testing", author: "SmartThings") {
|
||||||
capability "Thermostat"
|
capability "Thermostat"
|
||||||
capability "Relative Humidity Measurement"
|
|
||||||
capability "Sensor"
|
|
||||||
capability "Actuator"
|
|
||||||
|
|
||||||
command "tempUp"
|
command "tempUp"
|
||||||
command "tempDown"
|
command "tempDown"
|
||||||
@@ -25,41 +22,11 @@ metadata {
|
|||||||
command "heatDown"
|
command "heatDown"
|
||||||
command "coolUp"
|
command "coolUp"
|
||||||
command "coolDown"
|
command "coolDown"
|
||||||
command "setTemperature", ["number"]
|
command "setTemperature", ["number"]
|
||||||
}
|
}
|
||||||
|
|
||||||
tiles(scale: 2) {
|
tiles {
|
||||||
multiAttributeTile(name:"thermostatMulti", type:"thermostat", width:6, height:4) {
|
valueTile("temperature", "device.temperature", width: 1, height: 1) {
|
||||||
tileAttribute("device.temperature", key: "PRIMARY_CONTROL") {
|
|
||||||
attributeState("default", label:'${currentValue}', unit:"dF")
|
|
||||||
}
|
|
||||||
tileAttribute("device.temperature", key: "VALUE_CONTROL") {
|
|
||||||
attributeState("VALUE_UP", action: "tempUp")
|
|
||||||
attributeState("VALUE_DOWN", action: "tempDown")
|
|
||||||
}
|
|
||||||
tileAttribute("device.humidity", key: "SECONDARY_CONTROL") {
|
|
||||||
attributeState("default", label:'${currentValue}%', unit:"%")
|
|
||||||
}
|
|
||||||
tileAttribute("device.thermostatOperatingState", key: "OPERATING_STATE") {
|
|
||||||
attributeState("idle", backgroundColor:"#44b621")
|
|
||||||
attributeState("heating", backgroundColor:"#ea5462")
|
|
||||||
attributeState("cooling", backgroundColor:"#269bd2")
|
|
||||||
}
|
|
||||||
tileAttribute("device.thermostatMode", key: "THERMOSTAT_MODE") {
|
|
||||||
attributeState("off", label:'${name}')
|
|
||||||
attributeState("heat", label:'${name}')
|
|
||||||
attributeState("cool", label:'${name}')
|
|
||||||
attributeState("auto", label:'${name}')
|
|
||||||
}
|
|
||||||
tileAttribute("device.heatingSetpoint", key: "HEATING_SETPOINT") {
|
|
||||||
attributeState("default", label:'${currentValue}', unit:"dF")
|
|
||||||
}
|
|
||||||
tileAttribute("device.coolingSetpoint", key: "COOLING_SETPOINT") {
|
|
||||||
attributeState("default", label:'${currentValue}', unit:"dF")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
valueTile("temperature", "device.temperature", width: 2, height: 2) {
|
|
||||||
state("temperature", label:'${currentValue}', unit:"dF",
|
state("temperature", label:'${currentValue}', unit:"dF",
|
||||||
backgroundColors:[
|
backgroundColors:[
|
||||||
[value: 31, color: "#153591"],
|
[value: 31, color: "#153591"],
|
||||||
@@ -72,51 +39,51 @@ metadata {
|
|||||||
]
|
]
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
standardTile("tempDown", "device.temperature", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
|
standardTile("tempDown", "device.temperature", inactiveLabel: false, decoration: "flat") {
|
||||||
state "default", label:'down', action:"tempDown"
|
state "default", label:'down', action:"tempDown"
|
||||||
}
|
}
|
||||||
standardTile("tempUp", "device.temperature", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
|
standardTile("tempUp", "device.temperature", inactiveLabel: false, decoration: "flat") {
|
||||||
state "default", label:'up', action:"tempUp"
|
state "default", label:'up', action:"tempUp"
|
||||||
}
|
}
|
||||||
|
|
||||||
valueTile("heatingSetpoint", "device.heatingSetpoint", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
|
valueTile("heatingSetpoint", "device.heatingSetpoint", inactiveLabel: false, decoration: "flat") {
|
||||||
state "heat", label:'${currentValue} heat', unit: "F", backgroundColor:"#ffffff"
|
state "heat", label:'${currentValue} heat', unit: "F", backgroundColor:"#ffffff"
|
||||||
}
|
}
|
||||||
standardTile("heatDown", "device.temperature", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
|
standardTile("heatDown", "device.temperature", inactiveLabel: false, decoration: "flat") {
|
||||||
state "default", label:'down', action:"heatDown"
|
state "default", label:'down', action:"heatDown"
|
||||||
}
|
}
|
||||||
standardTile("heatUp", "device.temperature", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
|
standardTile("heatUp", "device.temperature", inactiveLabel: false, decoration: "flat") {
|
||||||
state "default", label:'up', action:"heatUp"
|
state "default", label:'up', action:"heatUp"
|
||||||
}
|
}
|
||||||
|
|
||||||
valueTile("coolingSetpoint", "device.coolingSetpoint", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
|
valueTile("coolingSetpoint", "device.coolingSetpoint", inactiveLabel: false, decoration: "flat") {
|
||||||
state "cool", label:'${currentValue} cool', unit:"F", backgroundColor:"#ffffff"
|
state "cool", label:'${currentValue} cool', unit:"F", backgroundColor:"#ffffff"
|
||||||
}
|
}
|
||||||
standardTile("coolDown", "device.temperature", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
|
standardTile("coolDown", "device.temperature", inactiveLabel: false, decoration: "flat") {
|
||||||
state "default", label:'down', action:"coolDown"
|
state "default", label:'down', action:"coolDown"
|
||||||
}
|
}
|
||||||
standardTile("coolUp", "device.temperature", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
|
standardTile("coolUp", "device.temperature", inactiveLabel: false, decoration: "flat") {
|
||||||
state "default", label:'up', action:"coolUp"
|
state "default", label:'up', action:"coolUp"
|
||||||
}
|
}
|
||||||
|
|
||||||
standardTile("mode", "device.thermostatMode", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
|
standardTile("mode", "device.thermostatMode", inactiveLabel: false, decoration: "flat") {
|
||||||
state "off", label:'${name}', action:"thermostat.heat", backgroundColor:"#ffffff"
|
state "off", label:'${name}', action:"thermostat.heat", backgroundColor:"#ffffff"
|
||||||
state "heat", label:'${name}', action:"thermostat.cool", backgroundColor:"#ffa81e"
|
state "heat", label:'${name}', action:"thermostat.cool", backgroundColor:"#ffa81e"
|
||||||
state "cool", label:'${name}', action:"thermostat.auto", backgroundColor:"#269bd2"
|
state "cool", label:'${name}', action:"thermostat.auto", backgroundColor:"#269bd2"
|
||||||
state "auto", label:'${name}', action:"thermostat.off", backgroundColor:"#79b821"
|
state "auto", label:'${name}', action:"thermostat.off", backgroundColor:"#79b821"
|
||||||
}
|
}
|
||||||
standardTile("fanMode", "device.thermostatFanMode", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
|
standardTile("fanMode", "device.thermostatFanMode", inactiveLabel: false, decoration: "flat") {
|
||||||
state "fanAuto", label:'${name}', action:"thermostat.fanOn", backgroundColor:"#ffffff"
|
state "fanAuto", label:'${name}', action:"thermostat.fanOn", backgroundColor:"#ffffff"
|
||||||
state "fanOn", label:'${name}', action:"thermostat.fanCirculate", backgroundColor:"#ffffff"
|
state "fanOn", label:'${name}', action:"thermostat.fanCirculate", backgroundColor:"#ffffff"
|
||||||
state "fanCirculate", label:'${name}', action:"thermostat.fanAuto", backgroundColor:"#ffffff"
|
state "fanCirculate", label:'${name}', action:"thermostat.fanAuto", backgroundColor:"#ffffff"
|
||||||
}
|
}
|
||||||
standardTile("operatingState", "device.thermostatOperatingState", width: 2, height: 2) {
|
standardTile("operatingState", "device.thermostatOperatingState") {
|
||||||
state "idle", label:'${name}', backgroundColor:"#ffffff"
|
state "idle", label:'${name}', backgroundColor:"#ffffff"
|
||||||
state "heating", label:'${name}', backgroundColor:"#ffa81e"
|
state "heating", label:'${name}', backgroundColor:"#ffa81e"
|
||||||
state "cooling", label:'${name}', backgroundColor:"#269bd2"
|
state "cooling", label:'${name}', backgroundColor:"#269bd2"
|
||||||
}
|
}
|
||||||
|
|
||||||
main("thermostatMulti")
|
main("temperature","operatingState")
|
||||||
details([
|
details([
|
||||||
"temperature","tempDown","tempUp",
|
"temperature","tempDown","tempUp",
|
||||||
"mode", "fanMode", "operatingState",
|
"mode", "fanMode", "operatingState",
|
||||||
@@ -134,7 +101,6 @@ def installed() {
|
|||||||
sendEvent(name: "thermostatMode", value: "off")
|
sendEvent(name: "thermostatMode", value: "off")
|
||||||
sendEvent(name: "thermostatFanMode", value: "fanAuto")
|
sendEvent(name: "thermostatFanMode", value: "fanAuto")
|
||||||
sendEvent(name: "thermostatOperatingState", value: "idle")
|
sendEvent(name: "thermostatOperatingState", value: "idle")
|
||||||
sendEvent(name: "humidity", value: 53, unit: "%")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def parse(String description) {
|
def parse(String description) {
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ metadata {
|
|||||||
// Automatically generated. Make future change here.
|
// Automatically generated. Make future change here.
|
||||||
definition (name: "Simulated Water Sensor", namespace: "smartthings/testing", author: "SmartThings") {
|
definition (name: "Simulated Water Sensor", namespace: "smartthings/testing", author: "SmartThings") {
|
||||||
capability "Water Sensor"
|
capability "Water Sensor"
|
||||||
capability "Sensor"
|
|
||||||
|
|
||||||
command "wet"
|
command "wet"
|
||||||
command "dry"
|
command "dry"
|
||||||
|
|||||||
@@ -1,42 +0,0 @@
|
|||||||
# Device Tiles Examples and Reference
|
|
||||||
|
|
||||||
This package contains examples of Device tiles, organized by tile type.
|
|
||||||
|
|
||||||
## Purpose
|
|
||||||
|
|
||||||
Each Device Handler shows example usages of a specific tile, and is meant to represent the variety of permutations that a tile can be configured.
|
|
||||||
|
|
||||||
The various tiles can be used by QA to test tiles on all supported mobile devices, and by developers as a reference implementation.
|
|
||||||
|
|
||||||
## Installation
|
|
||||||
|
|
||||||
1. Self-publish the Device Handlers in this package.
|
|
||||||
2. Self-publish the Device Tile Controller SmartApp. The SmartApp can be found [here](https://github.com/SmartThingsCommunity/SmartThingsPublic/blob/master/smartapps/smartthings/tile-ux/device-tile-controller.src/device-tile-controller.groovy).
|
|
||||||
3. Install the SmartApp from the Marketplace, under "My Apps".
|
|
||||||
4. Select the simulated devices you want to install and press "Done".
|
|
||||||
|
|
||||||
The simulated devices can then be found in the "Things" view of "My Home" in the mobile app.
|
|
||||||
You may wish to create a new room for these simulated devices for easy access.
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
Each simulated device can be interacted with like other devices.
|
|
||||||
You can use the mobile app to interact with the tiles to see how they look and behave.
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
If you get an error when installing the simulated devices using the controller SmartApp, ensure that you have published all the Device Handlers for yourself.
|
|
||||||
Also check live logging to see if there is a specific tile that is causing installation issues.
|
|
||||||
|
|
||||||
## FAQ
|
|
||||||
|
|
||||||
*Question: A tile isn't behaving as expected. What should I do?*
|
|
||||||
|
|
||||||
QA should create a JIRA ticket for any issues or inconsistencies of tiles across devices.
|
|
||||||
|
|
||||||
Developers may file a support ticket, and reference the specific tile and issue observed.
|
|
||||||
|
|
||||||
*Question: I'd like to contribute an example tile usage that would be helpful for testing and reference purposes. Can I do that?*
|
|
||||||
|
|
||||||
We recommend that you open an issue in the SmartThingsPublic repository describing the example tile and usage.
|
|
||||||
That way we can discuss with you the proposed change, and then if appropriate you can create a PR associated to the issue.
|
|
||||||
@@ -1,225 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright 2016 SmartThings, Inc.
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
metadata {
|
|
||||||
definition (
|
|
||||||
name: "carouselDeviceTile",
|
|
||||||
namespace: "smartthings/tile-ux",
|
|
||||||
author: "SmartThings") {
|
|
||||||
|
|
||||||
capability "Thermostat"
|
|
||||||
capability "Relative Humidity Measurement"
|
|
||||||
|
|
||||||
command "tempUp"
|
|
||||||
command "tempDown"
|
|
||||||
command "heatUp"
|
|
||||||
command "heatDown"
|
|
||||||
command "coolUp"
|
|
||||||
command "coolDown"
|
|
||||||
command "setTemperature", ["number"]
|
|
||||||
}
|
|
||||||
|
|
||||||
tiles(scale: 2) {
|
|
||||||
multiAttributeTile(name:"thermostatMulti", type:"thermostat", width:6, height:4) {
|
|
||||||
tileAttribute("device.temperature", key: "PRIMARY_CONTROL") {
|
|
||||||
attributeState("default", label:'${currentValue}', unit:"dF")
|
|
||||||
}
|
|
||||||
tileAttribute("device.temperature", key: "VALUE_CONTROL") {
|
|
||||||
attributeState("default", action: "setTemperature")
|
|
||||||
}
|
|
||||||
tileAttribute("device.humidity", key: "SECONDARY_CONTROL") {
|
|
||||||
attributeState("default", label:'${currentValue}%', unit:"%")
|
|
||||||
}
|
|
||||||
tileAttribute("device.thermostatOperatingState", key: "OPERATING_STATE") {
|
|
||||||
attributeState("idle", backgroundColor:"#44b621")
|
|
||||||
attributeState("heating", backgroundColor:"#ffa81e")
|
|
||||||
attributeState("cooling", backgroundColor:"#269bd2")
|
|
||||||
}
|
|
||||||
tileAttribute("device.thermostatMode", key: "THERMOSTAT_MODE") {
|
|
||||||
attributeState("off", label:'${name}')
|
|
||||||
attributeState("heat", label:'${name}')
|
|
||||||
attributeState("cool", label:'${name}')
|
|
||||||
attributeState("auto", label:'${name}')
|
|
||||||
}
|
|
||||||
tileAttribute("device.heatingSetpoint", key: "HEATING_SETPOINT") {
|
|
||||||
attributeState("default", label:'${currentValue}', unit:"dF")
|
|
||||||
}
|
|
||||||
tileAttribute("device.coolingSetpoint", key: "COOLING_SETPOINT") {
|
|
||||||
attributeState("default", label:'${currentValue}', unit:"dF")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
main("thermostatMulti")
|
|
||||||
details([
|
|
||||||
"thermostatMulti"
|
|
||||||
])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def installed() {
|
|
||||||
sendEvent(name: "temperature", value: 72, unit: "F")
|
|
||||||
sendEvent(name: "heatingSetpoint", value: 70, unit: "F")
|
|
||||||
sendEvent(name: "thermostatSetpoint", value: 70, unit: "F")
|
|
||||||
sendEvent(name: "coolingSetpoint", value: 76, unit: "F")
|
|
||||||
sendEvent(name: "thermostatMode", value: "off")
|
|
||||||
sendEvent(name: "thermostatFanMode", value: "fanAuto")
|
|
||||||
sendEvent(name: "thermostatOperatingState", value: "idle")
|
|
||||||
sendEvent(name: "humidity", value: 53, unit: "%")
|
|
||||||
}
|
|
||||||
|
|
||||||
def parse(String description) {
|
|
||||||
}
|
|
||||||
|
|
||||||
def evaluate(temp, heatingSetpoint, coolingSetpoint) {
|
|
||||||
log.debug "evaluate($temp, $heatingSetpoint, $coolingSetpoint"
|
|
||||||
def threshold = 1.0
|
|
||||||
def current = device.currentValue("thermostatOperatingState")
|
|
||||||
def mode = device.currentValue("thermostatMode")
|
|
||||||
|
|
||||||
def heating = false
|
|
||||||
def cooling = false
|
|
||||||
def idle = false
|
|
||||||
if (mode in ["heat","emergency heat","auto"]) {
|
|
||||||
if (heatingSetpoint - temp >= threshold) {
|
|
||||||
heating = true
|
|
||||||
sendEvent(name: "thermostatOperatingState", value: "heating")
|
|
||||||
}
|
|
||||||
else if (temp - heatingSetpoint >= threshold) {
|
|
||||||
idle = true
|
|
||||||
}
|
|
||||||
sendEvent(name: "thermostatSetpoint", value: heatingSetpoint)
|
|
||||||
}
|
|
||||||
if (mode in ["cool","auto"]) {
|
|
||||||
if (temp - coolingSetpoint >= threshold) {
|
|
||||||
cooling = true
|
|
||||||
sendEvent(name: "thermostatOperatingState", value: "cooling")
|
|
||||||
}
|
|
||||||
else if (coolingSetpoint - temp >= threshold && !heating) {
|
|
||||||
idle = true
|
|
||||||
}
|
|
||||||
sendEvent(name: "thermostatSetpoint", value: coolingSetpoint)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
sendEvent(name: "thermostatSetpoint", value: heatingSetpoint)
|
|
||||||
}
|
|
||||||
if (idle && !heating && !cooling) {
|
|
||||||
sendEvent(name: "thermostatOperatingState", value: "idle")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def setHeatingSetpoint(Double degreesF) {
|
|
||||||
log.debug "setHeatingSetpoint($degreesF)"
|
|
||||||
sendEvent(name: "heatingSetpoint", value: degreesF)
|
|
||||||
evaluate(device.currentValue("temperature"), degreesF, device.currentValue("coolingSetpoint"))
|
|
||||||
}
|
|
||||||
|
|
||||||
def setCoolingSetpoint(Double degreesF) {
|
|
||||||
log.debug "setCoolingSetpoint($degreesF)"
|
|
||||||
sendEvent(name: "coolingSetpoint", value: degreesF)
|
|
||||||
evaluate(device.currentValue("temperature"), device.currentValue("heatingSetpoint"), degreesF)
|
|
||||||
}
|
|
||||||
|
|
||||||
def setThermostatMode(String value) {
|
|
||||||
sendEvent(name: "thermostatMode", value: value)
|
|
||||||
evaluate(device.currentValue("temperature"), device.currentValue("heatingSetpoint"), device.currentValue("coolingSetpoint"))
|
|
||||||
}
|
|
||||||
|
|
||||||
def setThermostatFanMode(String value) {
|
|
||||||
sendEvent(name: "thermostatFanMode", value: value)
|
|
||||||
}
|
|
||||||
|
|
||||||
def off() {
|
|
||||||
sendEvent(name: "thermostatMode", value: "off")
|
|
||||||
evaluate(device.currentValue("temperature"), device.currentValue("heatingSetpoint"), device.currentValue("coolingSetpoint"))
|
|
||||||
}
|
|
||||||
|
|
||||||
def heat() {
|
|
||||||
sendEvent(name: "thermostatMode", value: "heat")
|
|
||||||
evaluate(device.currentValue("temperature"), device.currentValue("heatingSetpoint"), device.currentValue("coolingSetpoint"))
|
|
||||||
}
|
|
||||||
|
|
||||||
def auto() {
|
|
||||||
sendEvent(name: "thermostatMode", value: "auto")
|
|
||||||
evaluate(device.currentValue("temperature"), device.currentValue("heatingSetpoint"), device.currentValue("coolingSetpoint"))
|
|
||||||
}
|
|
||||||
|
|
||||||
def emergencyHeat() {
|
|
||||||
sendEvent(name: "thermostatMode", value: "emergency heat")
|
|
||||||
evaluate(device.currentValue("temperature"), device.currentValue("heatingSetpoint"), device.currentValue("coolingSetpoint"))
|
|
||||||
}
|
|
||||||
|
|
||||||
def cool() {
|
|
||||||
sendEvent(name: "thermostatMode", value: "cool")
|
|
||||||
evaluate(device.currentValue("temperature"), device.currentValue("heatingSetpoint"), device.currentValue("coolingSetpoint"))
|
|
||||||
}
|
|
||||||
|
|
||||||
def fanOn() {
|
|
||||||
sendEvent(name: "thermostatFanMode", value: "fanOn")
|
|
||||||
}
|
|
||||||
|
|
||||||
def fanAuto() {
|
|
||||||
sendEvent(name: "thermostatFanMode", value: "fanAuto")
|
|
||||||
}
|
|
||||||
|
|
||||||
def fanCirculate() {
|
|
||||||
sendEvent(name: "thermostatFanMode", value: "fanCirculate")
|
|
||||||
}
|
|
||||||
|
|
||||||
def tempUp() {
|
|
||||||
def ts = device.currentState("temperature")
|
|
||||||
def value = ts ? ts.integerValue + 1 : 72
|
|
||||||
sendEvent(name:"temperature", value: value)
|
|
||||||
evaluate(value, device.currentValue("heatingSetpoint"), device.currentValue("coolingSetpoint"))
|
|
||||||
}
|
|
||||||
|
|
||||||
def tempDown() {
|
|
||||||
def ts = device.currentState("temperature")
|
|
||||||
def value = ts ? ts.integerValue - 1 : 72
|
|
||||||
sendEvent(name:"temperature", value: value)
|
|
||||||
evaluate(value, device.currentValue("heatingSetpoint"), device.currentValue("coolingSetpoint"))
|
|
||||||
}
|
|
||||||
|
|
||||||
def setTemperature(value) {
|
|
||||||
def ts = device.currentState("temperature")
|
|
||||||
sendEvent(name:"temperature", value: value)
|
|
||||||
evaluate(value, device.currentValue("heatingSetpoint"), device.currentValue("coolingSetpoint"))
|
|
||||||
}
|
|
||||||
|
|
||||||
def heatUp() {
|
|
||||||
def ts = device.currentState("heatingSetpoint")
|
|
||||||
def value = ts ? ts.integerValue + 1 : 68
|
|
||||||
sendEvent(name:"heatingSetpoint", value: value)
|
|
||||||
evaluate(device.currentValue("temperature"), value, device.currentValue("coolingSetpoint"))
|
|
||||||
}
|
|
||||||
|
|
||||||
def heatDown() {
|
|
||||||
def ts = device.currentState("heatingSetpoint")
|
|
||||||
def value = ts ? ts.integerValue - 1 : 68
|
|
||||||
sendEvent(name:"heatingSetpoint", value: value)
|
|
||||||
evaluate(device.currentValue("temperature"), value, device.currentValue("coolingSetpoint"))
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def coolUp() {
|
|
||||||
def ts = device.currentState("coolingSetpoint")
|
|
||||||
def value = ts ? ts.integerValue + 1 : 76
|
|
||||||
sendEvent(name:"coolingSetpoint", value: value)
|
|
||||||
evaluate(device.currentValue("temperature"), device.currentValue("heatingSetpoint"), value)
|
|
||||||
}
|
|
||||||
|
|
||||||
def coolDown() {
|
|
||||||
def ts = device.currentState("coolingSetpoint")
|
|
||||||
def value = ts ? ts.integerValue - 1 : 76
|
|
||||||
sendEvent(name:"coolingSetpoint", value: value)
|
|
||||||
evaluate(device.currentValue("temperature"), device.currentValue("heatingSetpoint"), value)
|
|
||||||
}
|
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright 2016 SmartThings, Inc.
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
metadata {
|
|
||||||
definition (
|
|
||||||
name: "colorWheelDeviceTile",
|
|
||||||
namespace: "smartthings/tile-ux",
|
|
||||||
author: "SmartThings") {
|
|
||||||
|
|
||||||
capability "Color Control"
|
|
||||||
}
|
|
||||||
|
|
||||||
tiles(scale: 2) {
|
|
||||||
valueTile("currentColor", "device.color") {
|
|
||||||
state "color", label: '${currentValue}', defaultState: true
|
|
||||||
}
|
|
||||||
|
|
||||||
controlTile("rgbSelector", "device.color", "color", height: 6, width: 6, inactiveLabel: false) {
|
|
||||||
state "color", action: "color control.setColor"
|
|
||||||
}
|
|
||||||
|
|
||||||
main("currentColor")
|
|
||||||
details([
|
|
||||||
"rgbSelector"
|
|
||||||
])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// parse events into attributes
|
|
||||||
def parse(String description) {
|
|
||||||
log.debug "Parsing '${description}'"
|
|
||||||
}
|
|
||||||
|
|
||||||
def setColor(value) {
|
|
||||||
log.debug "setting color: $value"
|
|
||||||
if (value.hex) { sendEvent(name: "color", value: value.hex) }
|
|
||||||
if (value.hue) { sendEvent(name: "hue", value: value.hue) }
|
|
||||||
if (value.saturation) { sendEvent(name: "saturation", value: value.saturation) }
|
|
||||||
}
|
|
||||||
|
|
||||||
def setSaturation(percent) {
|
|
||||||
log.debug "Executing 'setSaturation'"
|
|
||||||
sendEvent(name: "saturation", value: percent)
|
|
||||||
}
|
|
||||||
|
|
||||||
def setHue(percent) {
|
|
||||||
log.debug "Executing 'setHue'"
|
|
||||||
sendEvent(name: "hue", value: percent)
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user