mirror of
https://github.com/mtan93/SmartThingsPublic.git
synced 2026-03-09 05:11:52 +00:00
Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b363725165 | ||
|
|
3b89368d45 | ||
|
|
12f6039de5 | ||
|
|
65c9da32e7 | ||
|
|
7147770e2d | ||
|
|
5eb33eca19 | ||
|
|
ec321ce85d | ||
|
|
7669bec0bc | ||
|
|
a20a58bd48 | ||
|
|
efabd07dea | ||
|
|
1e55f62048 | ||
|
|
12b18eae08 | ||
|
|
aad82e10ef | ||
|
|
b11b362c60 | ||
|
|
e1853b8e50 | ||
|
|
968834e33e | ||
|
|
e51a38eb28 | ||
|
|
553b45a3f3 | ||
|
|
d9ab3bca00 | ||
|
|
3a7abd6169 | ||
|
|
c20026b376 | ||
|
|
7def620f04 |
@@ -0,0 +1,104 @@
|
||||
/**
|
||||
* 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>
|
||||
"""
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -76,6 +76,8 @@ def parse(String description) {
|
||||
if (description?.startsWith('read attr -')) {
|
||||
handleReportAttributeMessage(description)
|
||||
}
|
||||
|
||||
return []
|
||||
}
|
||||
|
||||
private handleReportAttributeMessage(String description) {
|
||||
@@ -86,31 +88,37 @@ private handleReportAttributeMessage(String description) {
|
||||
}
|
||||
}
|
||||
|
||||
private handleBatteryEvent(rawValue) {
|
||||
def linkText = getLinkText(device)
|
||||
|
||||
def eventMap = [
|
||||
name: 'battery',
|
||||
value: '--'
|
||||
]
|
||||
|
||||
def volts = rawValue / 10
|
||||
if (volts > 0){
|
||||
def minVolts = 2.0
|
||||
def maxVolts = 2.8
|
||||
/**
|
||||
* Create battery event from reported battery voltage.
|
||||
*
|
||||
* @param volts Battery voltage in .1V increments
|
||||
*/
|
||||
private handleBatteryEvent(volts) {
|
||||
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 pct = (volts - minVolts) / (maxVolts - minVolts)
|
||||
|
||||
eventMap.value = Math.round(pct * 100)
|
||||
eventMap.descriptionText = "${linkText} battery was ${eventMap.value}%"
|
||||
def pct = batteryMap[volts]
|
||||
if (pct != null) {
|
||||
def linkText = getLinkText(device)
|
||||
def eventMap = [
|
||||
name: 'battery',
|
||||
value: pct,
|
||||
descriptionText: "${linkText} battery was ${pct}%"
|
||||
]
|
||||
log.debug "Creating battery event for voltage=${volts/10}V: ${eventMap}"
|
||||
sendEvent(eventMap)
|
||||
}
|
||||
}
|
||||
|
||||
log.debug "Creating battery event: ${eventMap}"
|
||||
sendEvent(eventMap)
|
||||
}
|
||||
|
||||
private handlePresenceEvent(present) {
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
/**
|
||||
* 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)
|
||||
}
|
||||
@@ -4,6 +4,7 @@
|
||||
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
|
||||
*/
|
||||
//DEPRECATED - Using the generic DTH for this device. Users need to be moved before deleting this DTH
|
||||
|
||||
metadata {
|
||||
definition (name: "OSRAM LIGHTIFY LED Flexible Strip RGBW", namespace: "smartthings", author: "SmartThings") {
|
||||
@@ -23,8 +24,8 @@ metadata {
|
||||
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: "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"
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ metadata {
|
||||
capability "Switch"
|
||||
capability "Sensor"
|
||||
|
||||
fingerprint profileId: "0104", inClusters: "0000,0003,0006", outClusters: "0019"
|
||||
fingerprint profileId: "0104", inClusters: "0006, 0004, 0003, 0000, 0005", outClusters: "0019", manufacturer: "Compacta International, Ltd", model: "ZBMPlug15", deviceJoinName: "SmartPower Outlet V1"
|
||||
}
|
||||
|
||||
// simulator metadata
|
||||
|
||||
@@ -20,8 +20,8 @@ metadata {
|
||||
capability "Refresh"
|
||||
capability "Temperature Measurement"
|
||||
capability "Water 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"
|
||||
@@ -29,9 +29,9 @@ metadata {
|
||||
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 {
|
||||
|
||||
|
||||
}
|
||||
|
||||
preferences {
|
||||
@@ -47,7 +47,7 @@ metadata {
|
||||
input "tempOffset", "number", title: "Degrees", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
tiles(scale: 2) {
|
||||
multiAttributeTile(name:"water", type: "generic", width: 6, height: 4){
|
||||
tileAttribute ("device.water", key: "PRIMARY_CONTROL") {
|
||||
@@ -78,7 +78,7 @@ metadata {
|
||||
details(["water", "temperature", "battery", "refresh"])
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def parse(String description) {
|
||||
log.debug "description: $description"
|
||||
|
||||
@@ -92,59 +92,59 @@ def parse(String description) {
|
||||
else if (description?.startsWith('temperature: ')) {
|
||||
map = parseCustomMessage(description)
|
||||
}
|
||||
else if (description?.startsWith('zone status')) {
|
||||
map = parseIasMessage(description)
|
||||
}
|
||||
|
||||
else if (description?.startsWith('zone status')) {
|
||||
map = parseIasMessage(description)
|
||||
}
|
||||
|
||||
log.debug "Parse returned $map"
|
||||
def result = map ? createEvent(map) : null
|
||||
|
||||
if (description?.startsWith('enroll request')) {
|
||||
List cmds = enrollResponse()
|
||||
log.debug "enroll response: ${cmds}"
|
||||
result = cmds?.collect { new physicalgraph.device.HubAction(it) }
|
||||
}
|
||||
return result
|
||||
|
||||
if (description?.startsWith('enroll request')) {
|
||||
List cmds = enrollResponse()
|
||||
log.debug "enroll response: ${cmds}"
|
||||
result = cmds?.collect { new physicalgraph.device.HubAction(it) }
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
private Map parseCatchAllMessage(String description) {
|
||||
Map resultMap = [:]
|
||||
def cluster = zigbee.parse(description)
|
||||
if (shouldProcessMessage(cluster)) {
|
||||
switch(cluster.clusterId) {
|
||||
case 0x0001:
|
||||
resultMap = getBatteryResult(cluster.data.last())
|
||||
break
|
||||
Map resultMap = [:]
|
||||
def cluster = zigbee.parse(description)
|
||||
if (shouldProcessMessage(cluster)) {
|
||||
switch(cluster.clusterId) {
|
||||
case 0x0001:
|
||||
resultMap = getBatteryResult(cluster.data.last())
|
||||
break
|
||||
|
||||
case 0x0402:
|
||||
// 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 0x0402:
|
||||
// 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) {
|
||||
// 0x0B is default response indicating message got through
|
||||
// 0x07 is bind message
|
||||
boolean ignoredMessage = cluster.profileId != 0x0104 ||
|
||||
cluster.command == 0x0B ||
|
||||
cluster.command == 0x07 ||
|
||||
(cluster.data.size() > 0 && cluster.data.first() == 0x3e)
|
||||
return !ignoredMessage
|
||||
// 0x0B is default response indicating message got through
|
||||
// 0x07 is bind message
|
||||
boolean ignoredMessage = cluster.profileId != 0x0104 ||
|
||||
cluster.command == 0x0B ||
|
||||
cluster.command == 0x07 ||
|
||||
(cluster.data.size() > 0 && cluster.data.first() == 0x3e)
|
||||
return !ignoredMessage
|
||||
}
|
||||
|
||||
|
||||
private Map parseReportAttributeMessage(String description) {
|
||||
Map descMap = (description - "read attr - ").split(",").inject([:]) { map, param ->
|
||||
def nameAndValue = param.split(":")
|
||||
map += [(nameAndValue[0].trim()):nameAndValue[1].trim()]
|
||||
}
|
||||
log.debug "Desc Map: $descMap"
|
||||
|
||||
|
||||
Map resultMap = [:]
|
||||
if (descMap.cluster == "0402" && descMap.attrId == "0000") {
|
||||
def value = getTemperature(descMap.value)
|
||||
@@ -153,10 +153,10 @@ private Map parseReportAttributeMessage(String description) {
|
||||
else if (descMap.cluster == "0001" && descMap.attrId == "0020") {
|
||||
resultMap = getBatteryResult(Integer.parseInt(descMap.value, 16))
|
||||
}
|
||||
|
||||
|
||||
return resultMap
|
||||
}
|
||||
|
||||
|
||||
private Map parseCustomMessage(String description) {
|
||||
Map resultMap = [:]
|
||||
if (description?.startsWith('temperature: ')) {
|
||||
@@ -167,42 +167,42 @@ private Map parseCustomMessage(String description) {
|
||||
}
|
||||
|
||||
private Map parseIasMessage(String description) {
|
||||
List parsedMsg = description.split(' ')
|
||||
String msgCode = parsedMsg[2]
|
||||
|
||||
Map resultMap = [:]
|
||||
switch(msgCode) {
|
||||
case '0x0020': // Closed/No Motion/Dry
|
||||
resultMap = getMoistureResult('dry')
|
||||
break
|
||||
List parsedMsg = description.split(' ')
|
||||
String msgCode = parsedMsg[2]
|
||||
|
||||
case '0x0021': // Open/Motion/Wet
|
||||
resultMap = getMoistureResult('wet')
|
||||
break
|
||||
Map resultMap = [:]
|
||||
switch(msgCode) {
|
||||
case '0x0020': // Closed/No Motion/Dry
|
||||
resultMap = getMoistureResult('dry')
|
||||
break
|
||||
|
||||
case '0x0022': // Tamper Alarm
|
||||
break
|
||||
case '0x0021': // Open/Motion/Wet
|
||||
resultMap = getMoistureResult('wet')
|
||||
break
|
||||
|
||||
case '0x0023': // Battery Alarm
|
||||
break
|
||||
case '0x0022': // Tamper Alarm
|
||||
break
|
||||
|
||||
case '0x0024': // Supervision Report
|
||||
log.debug 'dry with tamper alarm'
|
||||
resultMap = getMoistureResult('dry')
|
||||
break
|
||||
case '0x0023': // Battery Alarm
|
||||
break
|
||||
|
||||
case '0x0025': // Restore Report
|
||||
log.debug 'water with tamper alarm'
|
||||
resultMap = getMoistureResult('wet')
|
||||
break
|
||||
case '0x0024': // Supervision Report
|
||||
log.debug 'dry with tamper alarm'
|
||||
resultMap = getMoistureResult('dry')
|
||||
break
|
||||
|
||||
case '0x0026': // Trouble/Failure
|
||||
break
|
||||
case '0x0025': // Restore Report
|
||||
log.debug 'water with tamper alarm'
|
||||
resultMap = getMoistureResult('wet')
|
||||
break
|
||||
|
||||
case '0x0028': // Test Mode
|
||||
break
|
||||
}
|
||||
return resultMap
|
||||
case '0x0026': // Trouble/Failure
|
||||
break
|
||||
|
||||
case '0x0028': // Test Mode
|
||||
break
|
||||
}
|
||||
return resultMap
|
||||
}
|
||||
|
||||
def getTemperature(value) {
|
||||
@@ -215,24 +215,47 @@ def getTemperature(value) {
|
||||
}
|
||||
|
||||
private Map getBatteryResult(rawValue) {
|
||||
log.debug 'Battery'
|
||||
log.debug "Battery rawValue = ${rawValue}"
|
||||
def linkText = getLinkText(device)
|
||||
|
||||
def result = [
|
||||
name: 'battery'
|
||||
]
|
||||
|
||||
|
||||
def result = [
|
||||
name: 'battery',
|
||||
value: '--'
|
||||
]
|
||||
|
||||
def volts = rawValue / 10
|
||||
def descriptionText
|
||||
if (volts > 3.5) {
|
||||
result.descriptionText = "${linkText} battery has too much power (${volts} volts)."
|
||||
}
|
||||
|
||||
if (rawValue == 0 || rawValue == 255) {}
|
||||
else {
|
||||
def minVolts = 2.1
|
||||
def maxVolts = 3.0
|
||||
def pct = (volts - minVolts) / (maxVolts - minVolts)
|
||||
result.value = Math.min(100, (int) pct * 100)
|
||||
result.descriptionText = "${linkText} battery was ${result.value}%"
|
||||
if (volts > 3.5) {
|
||||
result.descriptionText = "${linkText} battery has too much power (${volts} volts)."
|
||||
}
|
||||
else {
|
||||
if (device.getDataValue("manufacturer") == "SmartThings") {
|
||||
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 = "${linkText} battery was ${result.value}%"
|
||||
}
|
||||
}
|
||||
else {
|
||||
def minVolts = 2.1
|
||||
def maxVolts = 3.0
|
||||
def pct = (volts - minVolts) / (maxVolts - minVolts)
|
||||
result.value = Math.min(100, (int) pct * 100)
|
||||
result.descriptionText = "${linkText} battery was ${result.value}%"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
@@ -267,7 +290,7 @@ private Map getMoistureResult(value) {
|
||||
def refresh() {
|
||||
log.debug "Refreshing Temperature and Battery"
|
||||
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"
|
||||
]
|
||||
|
||||
@@ -277,32 +300,32 @@ def refresh() {
|
||||
def configure() {
|
||||
String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
|
||||
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 1", "delay 500",
|
||||
|
||||
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 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",
|
||||
"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"
|
||||
"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
|
||||
return configCmds + refresh() // send refresh cmds as part of config
|
||||
}
|
||||
|
||||
def enrollResponse() {
|
||||
log.debug "Sending enroll response"
|
||||
String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
|
||||
[
|
||||
[
|
||||
//Resending the CIE in case the enroll request is sent before CIE is written
|
||||
"zcl global write 0x500 0x10 0xf0 {${zigbeeEui}}", "delay 200",
|
||||
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
|
||||
//Enroll Response
|
||||
"raw 0x500 {01 23 00 00 00}",
|
||||
"send 0x${device.deviceNetworkId} 1 1", "delay 200"
|
||||
]
|
||||
]
|
||||
}
|
||||
|
||||
private getEndpointId() {
|
||||
@@ -314,19 +337,19 @@ private hex(value) {
|
||||
}
|
||||
|
||||
private String swapEndianHex(String hex) {
|
||||
reverseArray(hex.decodeHex()).encodeHex()
|
||||
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
|
||||
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
|
||||
}
|
||||
|
||||
@@ -19,17 +19,17 @@ metadata {
|
||||
capability "Motion Sensor"
|
||||
capability "Configuration"
|
||||
capability "Battery"
|
||||
capability "Temperature Measurement"
|
||||
capability "Temperature Measurement"
|
||||
capability "Refresh"
|
||||
|
||||
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: "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: "3325"
|
||||
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3326"
|
||||
fingerprint inClusters: "0000,0001,0003,000F,0020,0402,0500", outClusters: "0019", manufacturer: "SmartThings", model: "motionv4", 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: "3325"
|
||||
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3326"
|
||||
fingerprint inClusters: "0000,0001,0003,000F,0020,0402,0500", outClusters: "0019", manufacturer: "SmartThings", model: "motionv4", deviceJoinName: "Motion Sensor"
|
||||
}
|
||||
|
||||
simulator {
|
||||
@@ -85,7 +85,7 @@ metadata {
|
||||
|
||||
def parse(String description) {
|
||||
log.debug "description: $description"
|
||||
|
||||
|
||||
Map map = [:]
|
||||
if (description?.startsWith('catchall:')) {
|
||||
map = parseCatchAllMessage(description)
|
||||
@@ -96,55 +96,55 @@ def parse(String description) {
|
||||
else if (description?.startsWith('temperature: ')) {
|
||||
map = parseCustomMessage(description)
|
||||
}
|
||||
else if (description?.startsWith('zone status')) {
|
||||
map = parseIasMessage(description)
|
||||
}
|
||||
|
||||
else if (description?.startsWith('zone status')) {
|
||||
map = parseIasMessage(description)
|
||||
}
|
||||
|
||||
log.debug "Parse returned $map"
|
||||
def result = map ? createEvent(map) : null
|
||||
|
||||
if (description?.startsWith('enroll request')) {
|
||||
List cmds = enrollResponse()
|
||||
log.debug "enroll response: ${cmds}"
|
||||
result = cmds?.collect { new physicalgraph.device.HubAction(it) }
|
||||
}
|
||||
return result
|
||||
|
||||
if (description?.startsWith('enroll request')) {
|
||||
List cmds = enrollResponse()
|
||||
log.debug "enroll response: ${cmds}"
|
||||
result = cmds?.collect { new physicalgraph.device.HubAction(it) }
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
private Map parseCatchAllMessage(String description) {
|
||||
Map resultMap = [:]
|
||||
def cluster = zigbee.parse(description)
|
||||
if (shouldProcessMessage(cluster)) {
|
||||
switch(cluster.clusterId) {
|
||||
case 0x0001:
|
||||
resultMap = getBatteryResult(cluster.data.last())
|
||||
break
|
||||
Map resultMap = [:]
|
||||
def cluster = zigbee.parse(description)
|
||||
if (shouldProcessMessage(cluster)) {
|
||||
switch(cluster.clusterId) {
|
||||
case 0x0001:
|
||||
resultMap = getBatteryResult(cluster.data.last())
|
||||
break
|
||||
|
||||
case 0x0402:
|
||||
// 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 0x0402:
|
||||
// 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:
|
||||
log.debug 'motion'
|
||||
resultMap.name = 'motion'
|
||||
break
|
||||
}
|
||||
}
|
||||
log.debug 'motion'
|
||||
resultMap.name = 'motion'
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return resultMap
|
||||
return resultMap
|
||||
}
|
||||
|
||||
private boolean shouldProcessMessage(cluster) {
|
||||
// 0x0B is default response indicating message got through
|
||||
// 0x07 is bind message
|
||||
boolean ignoredMessage = cluster.profileId != 0x0104 ||
|
||||
cluster.command == 0x0B ||
|
||||
cluster.command == 0x07 ||
|
||||
(cluster.data.size() > 0 && cluster.data.first() == 0x3e)
|
||||
return !ignoredMessage
|
||||
// 0x0B is default response indicating message got through
|
||||
// 0x07 is bind message
|
||||
boolean ignoredMessage = cluster.profileId != 0x0104 ||
|
||||
cluster.command == 0x0B ||
|
||||
cluster.command == 0x07 ||
|
||||
(cluster.data.size() > 0 && cluster.data.first() == 0x3e)
|
||||
return !ignoredMessage
|
||||
}
|
||||
|
||||
private Map parseReportAttributeMessage(String description) {
|
||||
@@ -153,7 +153,7 @@ private Map parseReportAttributeMessage(String description) {
|
||||
map += [(nameAndValue[0].trim()):nameAndValue[1].trim()]
|
||||
}
|
||||
log.debug "Desc Map: $descMap"
|
||||
|
||||
|
||||
Map resultMap = [:]
|
||||
if (descMap.cluster == "0402" && descMap.attrId == "0000") {
|
||||
def value = getTemperature(descMap.value)
|
||||
@@ -162,14 +162,14 @@ private Map parseReportAttributeMessage(String description) {
|
||||
else if (descMap.cluster == "0001" && descMap.attrId == "0020") {
|
||||
resultMap = getBatteryResult(Integer.parseInt(descMap.value, 16))
|
||||
}
|
||||
else if (descMap.cluster == "0406" && descMap.attrId == "0000") {
|
||||
def value = descMap.value.endsWith("01") ? "active" : "inactive"
|
||||
resultMap = getMotionResult(value)
|
||||
}
|
||||
|
||||
else if (descMap.cluster == "0406" && descMap.attrId == "0000") {
|
||||
def value = descMap.value.endsWith("01") ? "active" : "inactive"
|
||||
resultMap = getMotionResult(value)
|
||||
}
|
||||
|
||||
return resultMap
|
||||
}
|
||||
|
||||
|
||||
private Map parseCustomMessage(String description) {
|
||||
Map resultMap = [:]
|
||||
if (description?.startsWith('temperature: ')) {
|
||||
@@ -180,44 +180,44 @@ private Map parseCustomMessage(String description) {
|
||||
}
|
||||
|
||||
private Map parseIasMessage(String description) {
|
||||
List parsedMsg = description.split(' ')
|
||||
String msgCode = parsedMsg[2]
|
||||
|
||||
Map resultMap = [:]
|
||||
switch(msgCode) {
|
||||
case '0x0020': // Closed/No Motion/Dry
|
||||
resultMap = getMotionResult('inactive')
|
||||
break
|
||||
List parsedMsg = description.split(' ')
|
||||
String msgCode = parsedMsg[2]
|
||||
|
||||
case '0x0021': // Open/Motion/Wet
|
||||
resultMap = getMotionResult('active')
|
||||
break
|
||||
Map resultMap = [:]
|
||||
switch(msgCode) {
|
||||
case '0x0020': // Closed/No Motion/Dry
|
||||
resultMap = getMotionResult('inactive')
|
||||
break
|
||||
|
||||
case '0x0022': // Tamper Alarm
|
||||
log.debug 'motion with tamper alarm'
|
||||
resultMap = getMotionResult('active')
|
||||
break
|
||||
case '0x0021': // Open/Motion/Wet
|
||||
resultMap = getMotionResult('active')
|
||||
break
|
||||
|
||||
case '0x0023': // Battery Alarm
|
||||
break
|
||||
case '0x0022': // Tamper Alarm
|
||||
log.debug 'motion with tamper alarm'
|
||||
resultMap = getMotionResult('active')
|
||||
break
|
||||
|
||||
case '0x0024': // Supervision Report
|
||||
log.debug 'no motion with tamper alarm'
|
||||
resultMap = getMotionResult('inactive')
|
||||
break
|
||||
case '0x0023': // Battery Alarm
|
||||
break
|
||||
|
||||
case '0x0025': // Restore Report
|
||||
break
|
||||
case '0x0024': // Supervision Report
|
||||
log.debug 'no motion with tamper alarm'
|
||||
resultMap = getMotionResult('inactive')
|
||||
break
|
||||
|
||||
case '0x0026': // Trouble/Failure
|
||||
log.debug 'motion with failure alarm'
|
||||
resultMap = getMotionResult('active')
|
||||
break
|
||||
case '0x0025': // Restore Report
|
||||
break
|
||||
|
||||
case '0x0028': // Test Mode
|
||||
break
|
||||
}
|
||||
return resultMap
|
||||
case '0x0026': // Trouble/Failure
|
||||
log.debug 'motion with failure alarm'
|
||||
resultMap = getMotionResult('active')
|
||||
break
|
||||
|
||||
case '0x0028': // Test Mode
|
||||
break
|
||||
}
|
||||
return resultMap
|
||||
}
|
||||
|
||||
def getTemperature(value) {
|
||||
@@ -230,30 +230,46 @@ def getTemperature(value) {
|
||||
}
|
||||
|
||||
private Map getBatteryResult(rawValue) {
|
||||
log.debug 'Battery'
|
||||
log.debug "Battery rawValue = ${rawValue}"
|
||||
def linkText = getLinkText(device)
|
||||
|
||||
log.debug rawValue
|
||||
|
||||
def result = [
|
||||
name: 'battery',
|
||||
value: '--'
|
||||
]
|
||||
|
||||
def volts = rawValue / 10
|
||||
def descriptionText
|
||||
|
||||
if (rawValue == 0) {}
|
||||
if (rawValue == 0 || rawValue == 255) {}
|
||||
else {
|
||||
if (volts > 3.5) {
|
||||
result.descriptionText = "${linkText} battery has too much power (${volts} volts)."
|
||||
}
|
||||
else if (volts > 0){
|
||||
def minVolts = 2.1
|
||||
def maxVolts = 3.0
|
||||
def pct = (volts - minVolts) / (maxVolts - minVolts)
|
||||
result.value = Math.min(100, (int) pct * 100)
|
||||
result.descriptionText = "${linkText} battery was ${result.value}%"
|
||||
else {
|
||||
if (device.getDataValue("manufacturer") == "SmartThings") {
|
||||
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 = "${linkText} battery was ${result.value}%"
|
||||
}
|
||||
}
|
||||
else {
|
||||
def minVolts = 2.1
|
||||
def maxVolts = 3.0
|
||||
def pct = (volts - minVolts) / (maxVolts - minVolts)
|
||||
result.value = Math.min(100, (int) pct * 100)
|
||||
result.descriptionText = "${linkText} battery was ${result.value}%"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -338,19 +354,19 @@ private hex(value) {
|
||||
}
|
||||
|
||||
private String swapEndianHex(String hex) {
|
||||
reverseArray(hex.decodeHex()).encodeHex()
|
||||
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
|
||||
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
|
||||
}
|
||||
|
||||
@@ -14,28 +14,28 @@
|
||||
*
|
||||
*/
|
||||
|
||||
metadata {
|
||||
definition (name: "SmartSense Multi Sensor", namespace: "smartthings", author: "SmartThings") {
|
||||
|
||||
capability "Three Axis"
|
||||
metadata {
|
||||
definition (name: "SmartSense Multi Sensor", namespace: "smartthings", author: "SmartThings") {
|
||||
|
||||
capability "Three Axis"
|
||||
capability "Battery"
|
||||
capability "Configuration"
|
||||
capability "Sensor"
|
||||
capability "Configuration"
|
||||
capability "Sensor"
|
||||
capability "Contact Sensor"
|
||||
capability "Acceleration Sensor"
|
||||
capability "Refresh"
|
||||
capability "Temperature Measurement"
|
||||
|
||||
|
||||
command "enrollResponse"
|
||||
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05,FC02", outClusters: "0019", manufacturer: "CentraLite", model: "3320"
|
||||
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05,FC02", outClusters: "0019", manufacturer: "CentraLite", model: "3321"
|
||||
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"
|
||||
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"
|
||||
}
|
||||
}
|
||||
|
||||
simulator {
|
||||
simulator {
|
||||
status "open": "zone report :: type: 19 value: 0031"
|
||||
status "closed": "zone report :: type: 19 value: 0030"
|
||||
|
||||
@@ -52,7 +52,7 @@
|
||||
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"
|
||||
}
|
||||
preferences {
|
||||
preferences {
|
||||
section {
|
||||
image(name: 'educationalcontent', multiple: true, images: [
|
||||
"http://cdn.device-gse.smartthings.com/Multi/Multi1.jpg",
|
||||
@@ -62,13 +62,13 @@
|
||||
])
|
||||
}
|
||||
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 "tempOffset", "number", title: "Degrees", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false
|
||||
}
|
||||
section {
|
||||
input("garageSensor", "enum", title: "Do you want to use this sensor on a garage door?", options: ["Yes", "No"], defaultValue: "No", required: false, displayDuringSetup: false)
|
||||
input title: "Temperature Offset", description: "This feature allows you to correct any temperature variations by selecting an offset. Ex: If your sensor consistently reports a temp that's 5 degrees too warm, you'd enter \"-5\". If 3 degrees too cold, enter \"+3\".", displayDuringSetup: false, type: "paragraph", element: "paragraph"
|
||||
input "tempOffset", "number", title: "Degrees", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false
|
||||
}
|
||||
}
|
||||
section {
|
||||
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) {
|
||||
multiAttributeTile(name:"status", type: "generic", width: 6, height: 4){
|
||||
@@ -106,9 +106,9 @@
|
||||
valueTile("battery", "device.battery", decoration: "flat", inactiveLabel: false, width: 2, height: 2) {
|
||||
state "battery", label:'${currentValue}% battery', unit:""
|
||||
}
|
||||
standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
||||
state "default", action:"refresh.refresh", icon:"st.secondary.refresh"
|
||||
}
|
||||
standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
||||
state "default", action:"refresh.refresh", icon:"st.secondary.refresh"
|
||||
}
|
||||
|
||||
|
||||
main(["status", "acceleration", "temperature"])
|
||||
@@ -121,61 +121,61 @@ def parse(String description) {
|
||||
if (description?.startsWith('catchall:')) {
|
||||
map = parseCatchAllMessage(description)
|
||||
}
|
||||
else if (description?.startsWith('temperature: ')) {
|
||||
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')) {
|
||||
List cmds = enrollResponse()
|
||||
log.debug "enroll response: ${cmds}"
|
||||
result = cmds?.collect { new physicalgraph.device.HubAction(it) }
|
||||
}
|
||||
else if (description?.startsWith('read attr -')) {
|
||||
result = parseReportAttributeMessage(description).each { createEvent(it) }
|
||||
}
|
||||
else if (description?.startsWith('read attr -')) {
|
||||
result = parseReportAttributeMessage(description).each { createEvent(it) }
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
private Map parseCatchAllMessage(String description) {
|
||||
Map resultMap = [:]
|
||||
def cluster = zigbee.parse(description)
|
||||
log.debug cluster
|
||||
if (shouldProcessMessage(cluster)) {
|
||||
switch(cluster.clusterId) {
|
||||
case 0x0001:
|
||||
resultMap = getBatteryResult(cluster.data.last())
|
||||
break
|
||||
private Map parseCatchAllMessage(String description) {
|
||||
Map resultMap = [:]
|
||||
def cluster = zigbee.parse(description)
|
||||
log.debug cluster
|
||||
if (shouldProcessMessage(cluster)) {
|
||||
switch(cluster.clusterId) {
|
||||
case 0x0001:
|
||||
resultMap = getBatteryResult(cluster.data.last())
|
||||
break
|
||||
|
||||
case 0xFC02:
|
||||
log.debug 'ACCELERATION'
|
||||
break
|
||||
case 0xFC02:
|
||||
log.debug 'ACCELERATION'
|
||||
break
|
||||
|
||||
case 0x0402:
|
||||
log.debug 'TEMP'
|
||||
// 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 0x0402:
|
||||
log.debug 'TEMP'
|
||||
// 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) {
|
||||
// 0x0B is default response indicating message got through
|
||||
// 0x07 is bind message
|
||||
boolean ignoredMessage = cluster.profileId != 0x0104 ||
|
||||
cluster.command == 0x0B ||
|
||||
cluster.command == 0x07 ||
|
||||
(cluster.data.size() > 0 && cluster.data.first() == 0x3e)
|
||||
return !ignoredMessage
|
||||
// 0x0B is default response indicating message got through
|
||||
// 0x07 is bind message
|
||||
boolean ignoredMessage = cluster.profileId != 0x0104 ||
|
||||
cluster.command == 0x0B ||
|
||||
cluster.command == 0x07 ||
|
||||
(cluster.data.size() > 0 && cluster.data.first() == 0x3e)
|
||||
return !ignoredMessage
|
||||
}
|
||||
|
||||
private List parseReportAttributeMessage(String description) {
|
||||
@@ -202,7 +202,7 @@ private List parseReportAttributeMessage(String description) {
|
||||
result << parseAxis(threeAxisAttributes)
|
||||
descMap.value = descMap.value[-2..-1]
|
||||
}
|
||||
result << getAccelerationResult(descMap.value)
|
||||
result << getAccelerationResult(descMap.value)
|
||||
}
|
||||
else if (descMap.cluster == "FC02" && descMap.attrId == "0012" && descMap.value.size() == 24) {
|
||||
// The size is checked to ensure the attribute report contains X, Y and Z values
|
||||
@@ -231,43 +231,43 @@ private Map parseIasMessage(String description) {
|
||||
|
||||
Map resultMap = [:]
|
||||
switch(msgCode) {
|
||||
case '0x0020': // Closed/No Motion/Dry
|
||||
case '0x0020': // Closed/No Motion/Dry
|
||||
if (garageSensor != "Yes"){
|
||||
resultMap = getContactResult('closed')
|
||||
}
|
||||
break
|
||||
break
|
||||
|
||||
case '0x0021': // Open/Motion/Wet
|
||||
case '0x0021': // Open/Motion/Wet
|
||||
if (garageSensor != "Yes"){
|
||||
resultMap = getContactResult('open')
|
||||
}
|
||||
break
|
||||
break
|
||||
|
||||
case '0x0022': // Tamper Alarm
|
||||
break
|
||||
case '0x0022': // Tamper Alarm
|
||||
break
|
||||
|
||||
case '0x0023': // Battery Alarm
|
||||
break
|
||||
case '0x0023': // Battery Alarm
|
||||
break
|
||||
|
||||
case '0x0024': // Supervision Report
|
||||
case '0x0024': // Supervision Report
|
||||
if (garageSensor != "Yes"){
|
||||
resultMap = getContactResult('closed')
|
||||
}
|
||||
break
|
||||
break
|
||||
|
||||
case '0x0025': // Restore Report
|
||||
case '0x0025': // Restore Report
|
||||
if (garageSensor != "Yes"){
|
||||
resultMap = getContactResult('open')
|
||||
}
|
||||
break
|
||||
break
|
||||
|
||||
case '0x0026': // Trouble/Failure
|
||||
break
|
||||
case '0x0026': // Trouble/Failure
|
||||
break
|
||||
|
||||
case '0x0028': // Test Mode
|
||||
break
|
||||
}
|
||||
return resultMap
|
||||
case '0x0028': // Test Mode
|
||||
break
|
||||
}
|
||||
return resultMap
|
||||
}
|
||||
|
||||
def updated() {
|
||||
@@ -302,132 +302,141 @@ def getTemperature(value) {
|
||||
}
|
||||
}
|
||||
|
||||
private Map getBatteryResult(rawValue) {
|
||||
log.debug "Battery"
|
||||
log.debug rawValue
|
||||
def linkText = getLinkText(device)
|
||||
private Map getBatteryResult(rawValue) {
|
||||
log.debug "Battery rawValue = ${rawValue}"
|
||||
def linkText = getLinkText(device)
|
||||
|
||||
def result = [
|
||||
def result = [
|
||||
name: 'battery',
|
||||
value: '--'
|
||||
]
|
||||
value: '--'
|
||||
]
|
||||
|
||||
def volts = rawValue / 10
|
||||
def descriptionText
|
||||
|
||||
if (rawValue == 255) {}
|
||||
else {
|
||||
|
||||
if (volts > 3.5) {
|
||||
def volts = rawValue / 10
|
||||
|
||||
if (rawValue == 0 || rawValue == 255) {}
|
||||
else {
|
||||
if (volts > 3.5) {
|
||||
result.descriptionText = "${linkText} battery has too much power (${volts} volts)."
|
||||
}
|
||||
else {
|
||||
def minVolts = 2.1
|
||||
def maxVolts = 3.0
|
||||
def pct = (volts - minVolts) / (maxVolts - minVolts)
|
||||
result.value = Math.min(100, (int) pct * 100)
|
||||
result.descriptionText = "${linkText} battery was ${result.value}%"
|
||||
}}
|
||||
if (device.getDataValue("manufacturer") == "SmartThings") {
|
||||
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
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
private Map getTemperatureResult(value) {
|
||||
log.debug "Temperature"
|
||||
def linkText = getLinkText(device)
|
||||
if (tempOffset) {
|
||||
def offset = tempOffset as int
|
||||
def v = value as int
|
||||
value = v + offset
|
||||
if (volts < minVolts)
|
||||
volts = minVolts
|
||||
else if (volts > maxVolts)
|
||||
volts = maxVolts
|
||||
def pct = batteryMap[volts]
|
||||
if (pct != null) {
|
||||
result.value = pct
|
||||
result.descriptionText = "${linkText} battery was ${result.value}%"
|
||||
}
|
||||
}
|
||||
else {
|
||||
def minVolts = 2.1
|
||||
def maxVolts = 3.0
|
||||
def pct = (volts - minVolts) / (maxVolts - minVolts)
|
||||
result.value = Math.min(100, (int) pct * 100)
|
||||
result.descriptionText = "${linkText} battery was ${result.value}%"
|
||||
}
|
||||
}
|
||||
def descriptionText = "${linkText} was ${value}°${temperatureScale}"
|
||||
return [
|
||||
name: 'temperature',
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
private Map getTemperatureResult(value) {
|
||||
log.debug "Temperature"
|
||||
def linkText = getLinkText(device)
|
||||
if (tempOffset) {
|
||||
def offset = tempOffset as int
|
||||
def v = value as int
|
||||
value = v + offset
|
||||
}
|
||||
def descriptionText = "${linkText} was ${value}°${temperatureScale}"
|
||||
return [
|
||||
name: 'temperature',
|
||||
value: value,
|
||||
descriptionText: descriptionText
|
||||
]
|
||||
}
|
||||
|
||||
private Map getContactResult(value) {
|
||||
log.debug "Contact"
|
||||
def linkText = getLinkText(device)
|
||||
def descriptionText = "${linkText} was ${value == 'open' ? 'opened' : 'closed'}"
|
||||
sendEvent(name: 'contact', value: value, descriptionText: descriptionText, displayed:false)
|
||||
sendEvent(name: 'status', value: value, descriptionText: descriptionText)
|
||||
}
|
||||
|
||||
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
|
||||
descriptionText: descriptionText,
|
||||
isStateChange: isStateChange
|
||||
]
|
||||
}
|
||||
|
||||
def refresh() {
|
||||
log.debug "Refreshing Values "
|
||||
|
||||
def refreshCmds = []
|
||||
|
||||
if (device.getDataValue("manufacturer") == "SmartThings") {
|
||||
log.debug "Refreshing Values for manufacturer: SmartThings "
|
||||
refreshCmds = refreshCmds + [
|
||||
/* These values of Motion Threshold Multiplier(01) and Motion Threshold (7602)
|
||||
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.
|
||||
*/
|
||||
|
||||
"zcl mfg-code ${manufacturerCode}", "delay 200",
|
||||
"zcl global write 0xFC02 0 0x20 {01}", "delay 200",
|
||||
"send 0x${device.deviceNetworkId} 1 1", "delay 400",
|
||||
|
||||
"zcl mfg-code ${manufacturerCode}", "delay 200",
|
||||
"zcl global write 0xFC02 2 0x21 {7602}", "delay 200",
|
||||
"send 0x${device.deviceNetworkId} 1 1", "delay 400",
|
||||
]
|
||||
} else {
|
||||
refreshCmds = refreshCmds + [
|
||||
/* sensitivity - default value (8) */
|
||||
"zcl mfg-code ${manufacturerCode}", "delay 200",
|
||||
"zcl global write 0xFC02 0 0x20 {02}", "delay 200",
|
||||
"send 0x${device.deviceNetworkId} 1 1", "delay 400",
|
||||
]
|
||||
}
|
||||
|
||||
private Map getContactResult(value) {
|
||||
log.debug "Contact"
|
||||
def linkText = getLinkText(device)
|
||||
def descriptionText = "${linkText} was ${value == 'open' ? 'opened' : 'closed'}"
|
||||
sendEvent(name: 'contact', value: value, descriptionText: descriptionText, displayed:false)
|
||||
sendEvent(name: 'status', value: value, descriptionText: descriptionText)
|
||||
}
|
||||
//Common refresh commands
|
||||
refreshCmds = refreshCmds + [
|
||||
"st rattr 0x${device.deviceNetworkId} 1 0x402 0", "delay 200",
|
||||
"st rattr 0x${device.deviceNetworkId} 1 1 0x20", "delay 200",
|
||||
|
||||
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
|
||||
]
|
||||
}
|
||||
"zcl mfg-code ${manufacturerCode}", "delay 200",
|
||||
"zcl global read 0xFC02 0x0010",
|
||||
"send 0x${device.deviceNetworkId} 1 1","delay 400"
|
||||
]
|
||||
|
||||
def refresh() {
|
||||
log.debug "Refreshing Values "
|
||||
|
||||
def refreshCmds = []
|
||||
|
||||
if (device.getDataValue("manufacturer") == "SmartThings") {
|
||||
|
||||
log.debug "Refreshing Values for manufacturer: SmartThings "
|
||||
refreshCmds = refreshCmds + [
|
||||
return refreshCmds + enrollResponse()
|
||||
}
|
||||
|
||||
/* These values of Motion Threshold Multiplier(01) and Motion Threshold (7602)
|
||||
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.
|
||||
*/
|
||||
|
||||
"zcl mfg-code ${manufacturerCode}", "delay 200",
|
||||
"zcl global write 0xFC02 0 0x20 {01}", "delay 200",
|
||||
"send 0x${device.deviceNetworkId} 1 1", "delay 400",
|
||||
|
||||
"zcl mfg-code ${manufacturerCode}", "delay 200",
|
||||
"zcl global write 0xFC02 2 0x21 {7602}", "delay 200",
|
||||
"send 0x${device.deviceNetworkId} 1 1", "delay 400",
|
||||
|
||||
]
|
||||
|
||||
|
||||
} else {
|
||||
refreshCmds = refreshCmds + [
|
||||
|
||||
/* sensitivity - default value (8) */
|
||||
"zcl mfg-code ${manufacturerCode}", "delay 200",
|
||||
"zcl global write 0xFC02 0 0x20 {02}", "delay 200",
|
||||
"send 0x${device.deviceNetworkId} 1 1", "delay 400",
|
||||
]
|
||||
}
|
||||
|
||||
//Common refresh commands
|
||||
refreshCmds = refreshCmds + [
|
||||
"st rattr 0x${device.deviceNetworkId} 1 0x402 0", "delay 200",
|
||||
"st rattr 0x${device.deviceNetworkId} 1 1 0x20", "delay 200",
|
||||
|
||||
"zcl mfg-code ${manufacturerCode}", "delay 200",
|
||||
"zcl global read 0xFC02 0x0010",
|
||||
"send 0x${device.deviceNetworkId} 1 1","delay 400"
|
||||
]
|
||||
|
||||
return refreshCmds + enrollResponse()
|
||||
}
|
||||
|
||||
def configure() {
|
||||
|
||||
String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
|
||||
log.debug "Configuring Reporting"
|
||||
|
||||
def configCmds = [
|
||||
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",
|
||||
|
||||
@@ -455,10 +464,9 @@ def getTemperature(value) {
|
||||
"zcl mfg-code ${manufacturerCode}",
|
||||
"zcl global send-me-a-report 0xFC02 0x0014 0x29 1 3600 {01}",
|
||||
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500"
|
||||
|
||||
]
|
||||
|
||||
return configCmds + refresh()
|
||||
|
||||
return configCmds + refresh()
|
||||
}
|
||||
|
||||
private getEndpointId() {
|
||||
@@ -582,5 +590,3 @@ private byte[] reverseArray(byte[] array) {
|
||||
}
|
||||
return array
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,144 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* Author: SmartThings
|
||||
* Date: 2016-01-19
|
||||
*
|
||||
* This DTH should serve as the generic DTH to handle RGBW ZigBee HA devices
|
||||
*/
|
||||
|
||||
metadata {
|
||||
definition (name: "ZigBee RGBW Bulb", namespace: "smartthings", author: "SmartThings") {
|
||||
|
||||
capability "Actuator"
|
||||
capability "Color Control"
|
||||
capability "Color Temperature"
|
||||
capability "Configuration"
|
||||
capability "Polling"
|
||||
capability "Refresh"
|
||||
capability "Switch"
|
||||
capability "Switch Level"
|
||||
|
||||
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY Flex RGBW", deviceJoinName: "OSRAM LIGHTIFY LED FLEXIBLE STRIP RGBW"
|
||||
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "Flex RGBW", deviceJoinName: "OSRAM LIGHTIFY LED FLEXIBLE STRIP RGBW"
|
||||
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY A19 RGBW", deviceJoinName: "OSRAM LIGHTIFY LED A19 RGBW"
|
||||
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY BR RGBW", deviceJoinName: "OSRAM LIGHTIFY LED BR30 RGBW"
|
||||
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY RT RGBW", deviceJoinName: "OSRAM LIGHTIFY LED RT 5/6 RGBW"
|
||||
}
|
||||
|
||||
// UI tile definitions
|
||||
tiles(scale: 2) {
|
||||
multiAttributeTile(name:"switch", 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"
|
||||
}
|
||||
tileAttribute ("device.color", key: "COLOR_CONTROL") {
|
||||
attributeState "color", action:"color control.setColor"
|
||||
}
|
||||
}
|
||||
controlTile("colorTempSliderControl", "device.colorTemperature", "slider", width: 4, height: 2, inactiveLabel: false, range:"(2700..6500)") {
|
||||
state "colorTemperature", action:"color temperature.setColorTemperature"
|
||||
}
|
||||
valueTile("colorTemp", "device.colorTemperature", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
||||
state "colorTemperature", label: '${currentValue} K'
|
||||
}
|
||||
standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
||||
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
|
||||
}
|
||||
|
||||
main(["switch"])
|
||||
details(["switch", "colorTempSliderControl", "colorTemp", "refresh"])
|
||||
}
|
||||
}
|
||||
|
||||
//Globals
|
||||
private getATTRIBUTE_HUE() { 0x0000 }
|
||||
private getATTRIBUTE_SATURATION() { 0x0001 }
|
||||
private getHUE_COMMAND() { 0x00 }
|
||||
private getSATURATION_COMMAND() { 0x03 }
|
||||
private getCOLOR_CONTROL_CLUSTER() { 0x0300 }
|
||||
private getATTRIBUTE_COLOR_TEMPERATURE() { 0x0007 }
|
||||
|
||||
// Parse incoming device messages to generate events
|
||||
def parse(String description) {
|
||||
log.debug "description is $description"
|
||||
|
||||
def finalResult = zigbee.getEvent(description)
|
||||
if (finalResult) {
|
||||
log.debug finalResult
|
||||
sendEvent(finalResult)
|
||||
}
|
||||
else {
|
||||
def zigbeeMap = zigbee.parseDescriptionAsMap(description)
|
||||
log.trace "zigbeeMap : $zigbeeMap"
|
||||
|
||||
if (zigbeeMap?.clusterInt == COLOR_CONTROL_CLUSTER) {
|
||||
if(zigbeeMap.attrInt == ATTRIBUTE_HUE){ //Hue Attribute
|
||||
def hueValue = Math.round(zigbee.convertHexToInt(zigbeeMap.value) / 255 * 360)
|
||||
sendEvent(name: "hue", value: hueValue, displayed:false)
|
||||
}
|
||||
else if(zigbeeMap.attrInt == ATTRIBUTE_SATURATION){ //Saturation Attribute
|
||||
def saturationValue = Math.round(zigbee.convertHexToInt(zigbeeMap.value) / 255 * 100)
|
||||
sendEvent(name: "saturation", value: saturationValue, displayed:false)
|
||||
}
|
||||
}
|
||||
else {
|
||||
log.info "DID NOT PARSE MESSAGE for description : $description"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def on() {
|
||||
zigbee.on()
|
||||
}
|
||||
|
||||
def off() {
|
||||
zigbee.off()
|
||||
}
|
||||
|
||||
def refresh() {
|
||||
zigbee.readAttribute(0x0006, 0x00) + zigbee.readAttribute(0x0008, 0x00) + zigbee.readAttribute(0x0300, 0x00) + zigbee.readAttribute(0x0300, ATTRIBUTE_COLOR_TEMPERATURE) + zigbee.readAttribute(0x0300, ATTRIBUTE_HUE) + zigbee.readAttribute(0x0300, ATTRIBUTE_SATURATION) + zigbee.onOffConfig() + zigbee.levelConfig() + zigbee.colorTemperatureConfig() + zigbee.configureReporting(COLOR_CONTROL_CLUSTER, ATTRIBUTE_HUE, 0x20, 1, 3600, 0x01) + zigbee.configureReporting(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION, 0x20, 1, 3600, 0x01)
|
||||
}
|
||||
|
||||
def configure() {
|
||||
log.debug "Configuring Reporting and Bindings."
|
||||
zigbee.onOffConfig() + zigbee.levelConfig() + zigbee.colorTemperatureConfig() + zigbee.configureReporting(COLOR_CONTROL_CLUSTER, ATTRIBUTE_HUE, 0x20, 1, 3600, 0x01) + zigbee.configureReporting(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION, 0x20, 1, 3600, 0x01) + zigbee.readAttribute(0x0006, 0x00) + zigbee.readAttribute(0x0008, 0x00) + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, 0x00) + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_COLOR_TEMPERATURE) + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_HUE) + zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION)
|
||||
}
|
||||
|
||||
def setColorTemperature(value) {
|
||||
zigbee.setColorTemperature(value)
|
||||
}
|
||||
|
||||
def setLevel(value) {
|
||||
zigbee.setLevel(value)
|
||||
}
|
||||
|
||||
def setColor(value){
|
||||
log.trace "setColor($value)"
|
||||
zigbee.on() + setHue(value.hue) + "delay 300" + setSaturation(value.saturation)
|
||||
}
|
||||
|
||||
def setHue(value) {
|
||||
def scaledHueValue = zigbee.convertToHexString(Math.round(value * 0xfe / 100.0), 2)
|
||||
zigbee.command(COLOR_CONTROL_CLUSTER, HUE_COMMAND, scaledHueValue, "00", "0500") //payload-> hue value, direction (00-> shortest distance), transition time (1/10th second) (0500 in U16 reads 5)
|
||||
}
|
||||
|
||||
def setSaturation(value) {
|
||||
def scaledSatValue = zigbee.convertToHexString(Math.round(value * 0xfe / 100.0), 2)
|
||||
zigbee.command(COLOR_CONTROL_CLUSTER, SATURATION_COMMAND, scaledSatValue, "0500") //payload-> sat value, transition time
|
||||
}
|
||||
@@ -0,0 +1,243 @@
|
||||
metadata {
|
||||
definition (name: "Timevalve Smart", namespace: "timevalve.gaslock.t-08", author: "ruinnel") {
|
||||
capability "Valve"
|
||||
capability "Refresh"
|
||||
capability "Battery"
|
||||
capability "Temperature Measurement"
|
||||
|
||||
command "setRemaining"
|
||||
command "setTimeout"
|
||||
command "setTimeout10"
|
||||
command "setTimeout20"
|
||||
command "setTimeout30"
|
||||
command "setTimeout40"
|
||||
|
||||
command "remainingLevel"
|
||||
|
||||
attribute "remaining", "number"
|
||||
attribute "remainingText", "String"
|
||||
attribute "timeout", "number"
|
||||
|
||||
//raw desc : 0 0 0x1006 0 0 0 7 0x5E 0x86 0x72 0x5A 0x73 0x98 0x80
|
||||
//fingerprint deviceId:"0x1006", inClusters:"0x5E, 0x86, 0x72, 0x5A, 0x73, 0x98, 0x80"
|
||||
}
|
||||
|
||||
tiles (scale: 2) {
|
||||
multiAttributeTile(name:"statusTile", type:"generic", width:6, height:4) {
|
||||
tileAttribute("device.contact", key: "PRIMARY_CONTROL") {
|
||||
attributeState "open", label: '${name}', action: "close", icon:"st.contact.contact.open", backgroundColor:"#ffa81e"
|
||||
attributeState "closed", label:'${name}', action: "", icon:"st.contact.contact.closed", backgroundColor:"#79b821"
|
||||
}
|
||||
tileAttribute("device.remainingText", key: "SECONDARY_CONTROL") {
|
||||
attributeState "open", label: '${currentValue}', icon:"st.contact.contact.open", backgroundColor:"#ffa81e"
|
||||
attributeState "closed", label:'', icon:"st.contact.contact.closed", backgroundColor:"#79b821"
|
||||
}
|
||||
}
|
||||
|
||||
standardTile("refreshTile", "command.refresh", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
|
||||
state "default", label:'', action:"refresh.refresh", icon:"st.secondary.refresh"
|
||||
}
|
||||
|
||||
controlTile("remainingSliderTile", "device.remaining", "slider", inactiveLabel: false, range:"(0..590)", height: 2, width: 4) {
|
||||
state "level", action:"remainingLevel"
|
||||
}
|
||||
valueTile("setRemaining", "device.remainingText", inactiveLabel: false, decoration: "flat", height: 2, width: 2){
|
||||
state "remainingText", label:'${currentValue}\nRemaining'//, action: "setRemaining"//, icon: "st.Office.office6"
|
||||
}
|
||||
|
||||
standardTile("setTimeout10", "device.remaining", inactiveLabel: false, decoration: "flat") {
|
||||
state "default", label:'10Min', action: "setTimeout10", icon:"st.Health & Wellness.health7", defaultState: true
|
||||
state "10", label:'10Min', action: "setTimeout10", icon:"st.Office.office13"
|
||||
}
|
||||
standardTile("setTimeout20", "device.remaining", inactiveLabel: false, decoration: "flat") {
|
||||
state "default", label:'20Min', action: "setTimeout20", icon:"st.Health & Wellness.health7", defaultState: true
|
||||
state "20", label:'20Min', action: "setTimeout20", icon:"st.Office.office13"
|
||||
}
|
||||
standardTile("setTimeout30", "device.remaining", inactiveLabel: false, decoration: "flat") {
|
||||
state "default", label:'30Min', action: "setTimeout30", icon:"st.Health & Wellness.health7", defaultState: true
|
||||
state "30", label:'30Min', action: "setTimeout30", icon:"st.Office.office13"
|
||||
}
|
||||
standardTile("setTimeout40", "device.remaining", inactiveLabel: false, decoration: "flat") {
|
||||
state "default", label:'40Min', action: "setTimeout40", icon:"st.Health & Wellness.health7", defaultState: true
|
||||
state "40", label:'40Min', action: "setTimeout40", icon:"st.Office.office13"
|
||||
}
|
||||
|
||||
valueTile("batteryTile", "device.battery", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
|
||||
state "battery", label:'${currentValue}% battery', unit:""
|
||||
}
|
||||
|
||||
main (["statusTile"])
|
||||
// details (["statusTile", "remainingSliderTile", "setRemaining", "setTimeout10", "setTimeout20", "batteryTile", "refreshTile", "setTimeout30", "setTimeout40"])
|
||||
// details (["statusTile", "batteryTile", "setRemaining", "refreshTile"])
|
||||
details (["statusTile", "batteryTile", "refreshTile"])
|
||||
}
|
||||
}
|
||||
|
||||
def parse(description) {
|
||||
// log.debug "parse - " + description
|
||||
def result = null
|
||||
if (description.startsWith("Err 106")) {
|
||||
state.sec = 0
|
||||
result = createEvent(descriptionText: description, isStateChange: true)
|
||||
} else if (description != "updated") {
|
||||
def cmd = zwave.parse(description, [0x20: 1, 0x25: 1, 0x70: 1, 0x71: 1, 0x98: 1])
|
||||
if (cmd) {
|
||||
log.debug "parsed cmd = " + cmd
|
||||
result = zwaveEvent(cmd)
|
||||
//log.debug("'$description' parsed to $result")
|
||||
} else {
|
||||
log.debug("Couldn't zwave.parse '$description'")
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// 복호화 후 zwaveEvent() 호출
|
||||
def zwaveEvent(physicalgraph.zwave.commands.securityv1.SecurityMessageEncapsulation cmd) {
|
||||
//log.debug "SecurityMessageEncapsulation - " + cmd
|
||||
def encapsulatedCommand = cmd.encapsulatedCommand([0x20: 1, 0x25: 1, 0x70: 1, 0x71: 1, 0x98: 1])
|
||||
if (encapsulatedCommand) {
|
||||
state.sec = 1
|
||||
log.debug "encapsulatedCommand = " + encapsulatedCommand
|
||||
zwaveEvent(encapsulatedCommand)
|
||||
}
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.switchbinaryv1.SwitchBinaryReport cmd) {
|
||||
//log.debug "switch status - " + cmd.value
|
||||
createEvent(name:"contact", value: cmd.value ? "open" : "closed")
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.batteryv1.BatteryReport cmd) {
|
||||
def map = [ name: "battery", unit: "%" ]
|
||||
if (cmd.batteryLevel == 0xFF) { // Special value for low battery alert
|
||||
map.value = 1
|
||||
map.descriptionText = "${device.displayName} has a low battery"
|
||||
map.isStateChange = true
|
||||
} else {
|
||||
map.value = cmd.batteryLevel
|
||||
}
|
||||
|
||||
log.debug "battery - ${map.value}${map.unit}"
|
||||
// Store time of last battery update so we don't ask every wakeup, see WakeUpNotification handler
|
||||
state.lastbatt = new Date().time
|
||||
createEvent(map)
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.Command cmd) {
|
||||
//log.debug "zwaveEvent - ${device.displayName}: ${cmd}"
|
||||
createEvent(descriptionText: "${device.displayName}: ${cmd}")
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.configurationv1.ConfigurationReport cmd) {
|
||||
def result = []
|
||||
log.info "zwave.configurationV1.configurationGet - " + cmd
|
||||
def array = cmd.configurationValue
|
||||
def value = ( (array[0] * 0x1000000) + (array[1] * 0x10000) + (array[2] * 0x100) + array[3] ).intdiv(60)
|
||||
if (device.currentValue("contact") == "open") {
|
||||
value = ( (array[0] * 0x1000000) + (array[1] * 0x10000) + (array[2] * 0x100) + array[3] ).intdiv(60)
|
||||
} else {
|
||||
value = 0
|
||||
}
|
||||
|
||||
if (device.currentValue('contact') == 'open') {
|
||||
def hour = value.intdiv(60);
|
||||
def min = (value % 60).toString().padLeft(2, '0');
|
||||
def text = "${hour}:${min}M"
|
||||
|
||||
log.info "remain - " + text
|
||||
result.add( createEvent(name: "remaining", value: value, displayed: false, isStateChange: true) )
|
||||
result.add( createEvent(name: "remainingText", value: text, displayed: false, isStateChange: true) )
|
||||
} else {
|
||||
result.add( createEvent(name: "timeout", value: value, displayed: false, isStateChange: true) )
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.notificationv3.NotificationReport cmd) {
|
||||
def type = cmd.notificationType
|
||||
if (type == cmd.NOTIFICATION_TYPE_HEAT) {
|
||||
log.info "NotificationReport - ${type}"
|
||||
createEvent(name: "temperature", value: 999, unit: "C", descriptionText: "${device.displayName} is over heat!", displayed: true, isStateChange: true)
|
||||
}
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.alarmv1.AlarmReport cmd) {
|
||||
def type = cmd.alarmType
|
||||
def level = cmd.alarmLevel
|
||||
|
||||
log.info "AlarmReport - type : ${type}, level : ${level}"
|
||||
def msg = "${device.displayName} is over heat!"
|
||||
def result = createEvent(name: "temperature", value: 999, unit: "C", descriptionText: msg, displayed: true, isStateChange: true)
|
||||
if (sendPushMessage) {
|
||||
sendPushMessage(msg)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// remote open not allow
|
||||
def open() {}
|
||||
|
||||
def close() {
|
||||
// log.debug 'cmd - close()'
|
||||
commands([
|
||||
zwave.switchBinaryV1.switchBinarySet(switchValue: 0x00),
|
||||
zwave.switchBinaryV1.switchBinaryGet()
|
||||
])
|
||||
}
|
||||
|
||||
def setTimeout10() { setTimeout(10) }
|
||||
def setTimeout20() { setTimeout(20) }
|
||||
def setTimeout30() { setTimeout(30) }
|
||||
def setTimeout40() { setTimeout(40) }
|
||||
|
||||
|
||||
def setTimeout(value) {
|
||||
// log.debug "setDefaultTime($value)"
|
||||
commands([
|
||||
zwave.configurationV1.configurationSet(parameterNumber: 0x01, size: 4, scaledConfigurationValue: value * 60),
|
||||
zwave.configurationV1.configurationGet(parameterNumber: 0x01)
|
||||
]);
|
||||
}
|
||||
|
||||
def remainingLevel(value) {
|
||||
// log.debug "remainingLevel($value)"
|
||||
def hour = value.intdiv(60);
|
||||
def min = (value % 60).toString().padLeft(2, '0');
|
||||
def text = "${hour}:${min}M"
|
||||
sendEvent(name: "remaining", value: value, displayed: false, isStateChange: true)
|
||||
sendEvent(name: "remainingText", value: text, displayed: false, isStateChange: true)
|
||||
}
|
||||
|
||||
def setRemaining() {
|
||||
def remaining = device.currentValue("remaining")
|
||||
// log.debug "setConfiguration() - remaining : $remaining"
|
||||
commands([
|
||||
zwave.configurationV1.configurationSet(parameterNumber: 0x03, size: 4, scaledConfigurationValue: remaining * 60),
|
||||
zwave.configurationV1.configurationGet(parameterNumber: 0x03)
|
||||
]);
|
||||
}
|
||||
|
||||
private command(physicalgraph.zwave.Command cmd) {
|
||||
if (state.sec != 0 && !(cmd instanceof physicalgraph.zwave.commands.batteryv1.BatteryGet)) {
|
||||
log.debug "cmd = " + cmd + ", encapsulation"
|
||||
zwave.securityV1.securityMessageEncapsulation().encapsulate(cmd).format()
|
||||
} else {
|
||||
log.debug "cmd = " + cmd + ", plain"
|
||||
cmd.format()
|
||||
}
|
||||
}
|
||||
|
||||
private commands(commands, delay=200) {
|
||||
delayBetween(commands.collect{ command(it) }, delay)
|
||||
}
|
||||
|
||||
def refresh() {
|
||||
// log.debug 'cmd - refresh()'
|
||||
commands([
|
||||
zwave.batteryV1.batteryGet(),
|
||||
zwave.switchBinaryV1.switchBinaryGet(),
|
||||
zwave.configurationV1.configurationGet(parameterNumber: 0x01),
|
||||
zwave.configurationV1.configurationGet(parameterNumber: 0x03)
|
||||
], 400)
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* Initial State Event Streamer
|
||||
*
|
||||
* Copyright 2015 David Sulpy
|
||||
* Copyright 2016 David Sulpy
|
||||
*
|
||||
* 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:
|
||||
@@ -77,6 +77,62 @@ mappings {
|
||||
}
|
||||
}
|
||||
|
||||
def getAccessKey() {
|
||||
log.trace "get access key"
|
||||
if (atomicState.accessKey == null) {
|
||||
httpError(404, "Access Key Not Found")
|
||||
} else {
|
||||
[
|
||||
accessKey: atomicState.accessKey
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
def getBucketKey() {
|
||||
log.trace "get bucket key"
|
||||
if (atomicState.bucketKey == null) {
|
||||
httpError(404, "Bucket key Not Found")
|
||||
} else {
|
||||
[
|
||||
bucketKey: atomicState.bucketKey,
|
||||
bucketName: atomicState.bucketName
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
def setBucketKey() {
|
||||
log.trace "set bucket key"
|
||||
def newBucketKey = request.JSON?.bucketKey
|
||||
def newBucketName = request.JSON?.bucketName
|
||||
|
||||
log.debug "bucket name: $newBucketName"
|
||||
log.debug "bucket key: $newBucketKey"
|
||||
|
||||
if (newBucketKey && (newBucketKey != atomicState.bucketKey || newBucketName != atomicState.bucketName)) {
|
||||
atomicState.bucketKey = "$newBucketKey"
|
||||
atomicState.bucketName = "$newBucketName"
|
||||
atomicState.isBucketCreated = false
|
||||
}
|
||||
|
||||
tryCreateBucket()
|
||||
}
|
||||
|
||||
def setAccessKey() {
|
||||
log.trace "set access key"
|
||||
def newAccessKey = request.JSON?.accessKey
|
||||
def newGrokerSubdomain = request.JSON?.grokerSubdomain
|
||||
|
||||
if (newGrokerSubdomain && newGrokerSubdomain != "" && newGrokerSubdomain != atomicState.grokerSubdomain) {
|
||||
atomicState.grokerSubdomain = "$newGrokerSubdomain"
|
||||
atomicState.isBucketCreated = false
|
||||
}
|
||||
|
||||
if (newAccessKey && newAccessKey != atomicState.accessKey) {
|
||||
atomicState.accessKey = "$newAccessKey"
|
||||
atomicState.isBucketCreated = false
|
||||
}
|
||||
}
|
||||
|
||||
def subscribeToEvents() {
|
||||
if (accelerometers != null) {
|
||||
subscribe(accelerometers, "acceleration", genericHandler)
|
||||
@@ -169,85 +225,27 @@ def subscribeToEvents() {
|
||||
}
|
||||
}
|
||||
|
||||
def getAccessKey() {
|
||||
log.trace "get access key"
|
||||
if (atomicState.accessKey == null) {
|
||||
httpError(404, "Access Key Not Found")
|
||||
} else {
|
||||
[
|
||||
accessKey: atomicState.accessKey
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
def getBucketKey() {
|
||||
log.trace "get bucket key"
|
||||
if (atomicState.bucketKey == null) {
|
||||
httpError(404, "Bucket key Not Found")
|
||||
} else {
|
||||
[
|
||||
bucketKey: atomicState.bucketKey,
|
||||
bucketName: atomicState.bucketName
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
def setBucketKey() {
|
||||
log.trace "set bucket key"
|
||||
def newBucketKey = request.JSON?.bucketKey
|
||||
def newBucketName = request.JSON?.bucketName
|
||||
|
||||
log.debug "bucket name: $newBucketName"
|
||||
log.debug "bucket key: $newBucketKey"
|
||||
|
||||
if (newBucketKey && (newBucketKey != atomicState.bucketKey || newBucketName != atomicState.bucketName)) {
|
||||
atomicState.bucketKey = "$newBucketKey"
|
||||
atomicState.bucketName = "$newBucketName"
|
||||
atomicState.isBucketCreated = false
|
||||
}
|
||||
|
||||
tryCreateBucket()
|
||||
}
|
||||
|
||||
def setAccessKey() {
|
||||
log.trace "set access key"
|
||||
def newAccessKey = request.JSON?.accessKey
|
||||
def newGrokerSubdomain = request.JSON?.grokerSubdomain
|
||||
|
||||
if (newGrokerSubdomain && newGrokerSubdomain != "" && newGrokerSubdomain != atomicState.grokerSubdomain) {
|
||||
atomicState.grokerSubdomain = "$newGrokerSubdomain"
|
||||
atomicState.isBucketCreated = false
|
||||
}
|
||||
|
||||
if (newAccessKey && newAccessKey != atomicState.accessKey) {
|
||||
atomicState.accessKey = "$newAccessKey"
|
||||
atomicState.isBucketCreated = false
|
||||
}
|
||||
}
|
||||
|
||||
def installed() {
|
||||
atomicState.version = "1.0.18"
|
||||
atomicState.version = "1.1.0"
|
||||
|
||||
atomicState.isBucketCreated = false
|
||||
atomicState.grokerSubdomain = "groker"
|
||||
|
||||
subscribeToEvents()
|
||||
|
||||
atomicState.isBucketCreated = false
|
||||
atomicState.grokerSubdomain = "groker"
|
||||
atomicState.eventBuffer = []
|
||||
|
||||
runEvery15Minutes(flushBuffer)
|
||||
|
||||
log.debug "installed (version $atomicState.version)"
|
||||
}
|
||||
|
||||
def updated() {
|
||||
atomicState.version = "1.0.18"
|
||||
atomicState.version = "1.1.0"
|
||||
unsubscribe()
|
||||
|
||||
if (atomicState.bucketKey != null && atomicState.accessKey != null) {
|
||||
atomicState.isBucketCreated = false
|
||||
}
|
||||
if (atomicState.eventBuffer == null) {
|
||||
atomicState.eventBuffer = []
|
||||
}
|
||||
if (atomicState.grokerSubdomain == null || atomicState.grokerSubdomain == "") {
|
||||
atomicState.grokerSubdomain = "groker"
|
||||
}
|
||||
@@ -327,37 +325,17 @@ def genericHandler(evt) {
|
||||
eventHandler(key, value)
|
||||
}
|
||||
|
||||
// This is a handler function for flushing the event buffer
|
||||
// after a specified amount of time to reduce the load on ST servers
|
||||
def flushBuffer() {
|
||||
def eventBuffer = atomicState.eventBuffer
|
||||
log.trace "About to flush the buffer on schedule"
|
||||
if (eventBuffer != null && eventBuffer.size() > 0) {
|
||||
atomicState.eventBuffer = []
|
||||
tryShipEvents(eventBuffer)
|
||||
}
|
||||
}
|
||||
|
||||
def eventHandler(name, value) {
|
||||
def epoch = now() / 1000
|
||||
def eventBuffer = atomicState.eventBuffer ?: []
|
||||
eventBuffer << [key: "$name", value: "$value", epoch: "$epoch"]
|
||||
|
||||
if (eventBuffer.size() >= 10) {
|
||||
// Clear eventBuffer right away since we've already pulled it off of atomicState to reduce the risk of missing
|
||||
// events. This assumes the grokerSubdomain, accessKey, and bucketKey are set correctly to avoid the eventBuffer
|
||||
// from growing unbounded.
|
||||
atomicState.eventBuffer = []
|
||||
tryShipEvents(eventBuffer)
|
||||
} else {
|
||||
// Make sure we persist the updated eventBuffer with the new event added back to atomicState
|
||||
atomicState.eventBuffer = eventBuffer
|
||||
}
|
||||
log.debug "Event added to buffer: " + eventBuffer
|
||||
def event = new JsonSlurper().parseText("{\"key\": \"$name\", \"value\": \"$value\", \"epoch\": \"$epoch\"}")
|
||||
|
||||
tryShipEvents(event)
|
||||
|
||||
log.debug "Shipped Event: " + event
|
||||
}
|
||||
|
||||
// a helper function for shipping the atomicState.eventBuffer to Initial State
|
||||
def tryShipEvents(eventBuffer) {
|
||||
def tryShipEvents(event) {
|
||||
|
||||
def grokerSubdomain = atomicState.grokerSubdomain
|
||||
// can't ship events if there is no grokerSubdomain
|
||||
@@ -380,7 +358,7 @@ def tryShipEvents(eventBuffer) {
|
||||
"X-IS-AccessKey": "${accessKey}",
|
||||
"Accept-Version": "0.0.2"
|
||||
],
|
||||
body: eventBuffer
|
||||
body: event
|
||||
]
|
||||
|
||||
try {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,101 +0,0 @@
|
||||
/**
|
||||
* Sleep Wizard
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
definition(
|
||||
name: "Smart Sleep",
|
||||
namespace: "smartsolutionsv2/sleeping",
|
||||
author: "SmartThings",
|
||||
description: "Create rules to control devices based on a sleep sensor, including in bed, out of bed, sleeping, and not sleeping.",
|
||||
category: "My Apps",
|
||||
iconUrl: "https://s3.amazonaws.com/smartapp-icons/ModeMagic/Cat-ModeMagic.png",
|
||||
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/ModeMagic/Cat-ModeMagic@2x.png",
|
||||
iconX3Url: "https://s3.amazonaws.com/smartapp-icons/ModeMagic/Cat-ModeMagic@3x.png",
|
||||
singleInstance: true
|
||||
)
|
||||
|
||||
preferences {
|
||||
page(name: "main", title: getLabel("str_Title"), install: true, uninstall: true) {
|
||||
section {
|
||||
app(name: "smartsleepautomation", appName: "Smart Sleep Automation", namespace: "smartsolutionsv2/sleeping", title: getLabel("str_SectionTitle"), multiple: true, uninstall: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def installed() {
|
||||
log.debug "Installed with settings: ${settings}"
|
||||
initialize()
|
||||
}
|
||||
|
||||
def updated() {
|
||||
log.debug "Updated with settings: ${settings}"
|
||||
initialize()
|
||||
}
|
||||
|
||||
def initialize() {
|
||||
def numChildApps = getChildApps().size();
|
||||
def text = "${numChildApps} rules configured";
|
||||
sendEvent(
|
||||
linkText: numChildApps.toString(),
|
||||
descriptionText: next,
|
||||
eventType: "SOLUTION_SUMMARY",
|
||||
name: "summary",
|
||||
value: numChildApps,
|
||||
data: [["icon": "indicator-dot-gray", "iconColor": "#878787", "value":text]],
|
||||
displayed: false
|
||||
);
|
||||
}
|
||||
|
||||
cards {
|
||||
card("Action History") {
|
||||
tiles {
|
||||
eventTile { }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def getLabel(value)
|
||||
{
|
||||
def str_Title=[
|
||||
"kr": "룰",
|
||||
"us": "Rules"
|
||||
]
|
||||
|
||||
def str_SectionTitle=[
|
||||
"kr": "새로운 자동화 룰 만들기",
|
||||
"us": "New Smart Sleep Automation"
|
||||
]
|
||||
|
||||
if(clientLocale?.language == "ko")
|
||||
{
|
||||
switch(value)
|
||||
{
|
||||
case "str_Title":
|
||||
return str_Title["kr"]
|
||||
case "str_SectionTitle":
|
||||
return str_SectionTitle["kr"]
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch(value)
|
||||
{
|
||||
case "str_Title":
|
||||
return str_Title["us"]
|
||||
case "str_SectionTitle":
|
||||
return str_SectionTitle["us"]
|
||||
}
|
||||
}
|
||||
return "Unknown"
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright 2015 SmartThings
|
||||
* 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:
|
||||
@@ -38,37 +38,75 @@ preferences {
|
||||
page(name: "schedulingPage")
|
||||
page(name: "completionPage")
|
||||
page(name: "numbersPage")
|
||||
page(name: "controllerExplanationPage")
|
||||
}
|
||||
|
||||
def rootPage() {
|
||||
dynamicPage(name: "rootPage", title: "", install: true, uninstall: true) {
|
||||
|
||||
section {
|
||||
section("What to dim") {
|
||||
input(name: "dimmers", type: "capability.switchLevel", title: "Dimmers", description: null, multiple: true, required: true, submitOnChange: true)
|
||||
if (dimmers) {
|
||||
href(name: "toNumbersPage", page: "numbersPage", title: "Duration & Direction", description: numbersPageHrefDescription(), state: "complete")
|
||||
}
|
||||
}
|
||||
|
||||
if (dimmers) {
|
||||
|
||||
section {
|
||||
href(name: "toNumbersPage", page: "numbersPage", title: "Duration & Direction", description: numbersPageHrefDescription(), state: "complete")
|
||||
section("Gentle Wake Up Has A Controller") {
|
||||
href(title: "Learn how to control Gentle Wake Up", page: "controllerExplanationPage", description: null)
|
||||
}
|
||||
|
||||
section {
|
||||
href(name: "toSchedulingPage", page: "schedulingPage", title: "Rules For Automatically Dimming Your Lights", description: schedulingHrefDescription(), state: schedulingHrefDescription() ? "complete" : "")
|
||||
}
|
||||
|
||||
section {
|
||||
href(name: "toCompletionPage", title: "Completion Actions (Optional)", page: "completionPage", state: completionHrefDescription() ? "complete" : "", description: completionHrefDescription())
|
||||
section("Rules For Dimming") {
|
||||
href(name: "toSchedulingPage", page: "schedulingPage", title: "Automation", description: schedulingHrefDescription() ?: "Set rules for when to start", state: schedulingHrefDescription() ? "complete" : "")
|
||||
input(name: "manualOverride", type: "enum", options: ["cancel": "Cancel dimming", "jumpTo": "Jump to the end"], title: "When one of the dimmers is manually turned off…", description: "dimming will continue", required: false, multiple: false)
|
||||
href(name: "toCompletionPage", title: "Completion Actions", page: "completionPage", state: completionHrefDescription() ? "complete" : "", description: completionHrefDescription() ?: "Set rules for what to do when dimming completes")
|
||||
}
|
||||
|
||||
section {
|
||||
// TODO: fancy label
|
||||
label(title: "Label this SmartApp", required: false, defaultValue: "")
|
||||
label(title: "Label This SmartApp", required: false, defaultValue: "", description: "Highly recommended", submitOnChange: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def controllerExplanationPage() {
|
||||
dynamicPage(name: "controllerExplanationPage", title: "How To Control Gentle Wake Up") {
|
||||
|
||||
section("With other SmartApps", hideable: true, hidden: false) {
|
||||
paragraph "When this SmartApp is installed, it will create a controller device which you can use in other SmartApps for even more customizable automation!"
|
||||
paragraph "The controller acts like a switch so any SmartApp that can control a switch can control Gentle Wake Up, too!"
|
||||
paragraph "Routines and 'Smart Lighting' are great ways to automate Gentle Wake Up."
|
||||
}
|
||||
|
||||
section("More about the controller", hideable: true, hidden: true) {
|
||||
paragraph "You can find the controller with your other 'Things'. It will look like this."
|
||||
image "http://f.cl.ly/items/2O0v0h41301U14042z3i/GentleWakeUpController-tile-stopped.png"
|
||||
paragraph "You can start and stop Gentle Wake up by tapping the control on the right."
|
||||
image "http://f.cl.ly/items/3W323J3M1b3K0k0V3X3a/GentleWakeUpController-tile-running.png"
|
||||
paragraph "If you look at the device details screen, you will find even more information about Gentle Wake Up and more fine grain controls."
|
||||
image "http://f.cl.ly/items/291s3z2I2Q0r2q0x171H/GentleWakeUpController-richTile-stopped.png"
|
||||
paragraph "The slider allows you to jump to any point in the dimming process. Think of it as a percentage. If Gentle Wake Up is set to dim down as you fall asleep, but your book is just too good to put down; simply drag the slider to the left and Gentle Wake Up will give you more time to finish your chapter and drift off to sleep."
|
||||
image "http://f.cl.ly/items/0F0N2G0S3v1q0L0R3J3Y/GentleWakeUpController-richTile-running.png"
|
||||
paragraph "In the lower left, you will see the amount of time remaining in the dimming cycle. It does not count down evenly. Instead, it will update whenever the slider is updated; typically every 6-18 seconds depending on the duration of your dimming cycle."
|
||||
paragraph "Of course, you may also tap the middle to start or stop the dimming cycle at any time."
|
||||
}
|
||||
|
||||
section("Starting and stopping the SmartApp itself", hideable: true, hidden: true) {
|
||||
paragraph "Tap the 'play' button on the SmartApp to start or stop dimming."
|
||||
image "http://f.cl.ly/items/0R2u1Z2H30393z2I2V3S/GentleWakeUp-appTouch2.png"
|
||||
}
|
||||
|
||||
section("Turning off devices while dimming", hideable: true, hidden: true) {
|
||||
paragraph "It's best to use other Devices and SmartApps for triggering the Controller device. However, that isn't always an option."
|
||||
paragraph "If you turn off a switch that is being dimmed, it will either continue to dim, stop dimming, or jump to the end of the dimming cycle depending on your settings."
|
||||
paragraph "Unfortunately, some switches take a little time to turn off and may not finish turning off before Gentle Wake Up sets its dim level again. You may need to try a few times to get it to stop."
|
||||
paragraph "That's why it's best to use devices that aren't currently dimming. Remember that you can use other SmartApps to toggle the controller. :)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def numbersPage() {
|
||||
dynamicPage(name:"numbersPage", title:"") {
|
||||
|
||||
@@ -128,24 +166,33 @@ def endLevelLabel() {
|
||||
return "${endLevel}%"
|
||||
}
|
||||
|
||||
def weekdays() {
|
||||
["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"]
|
||||
}
|
||||
|
||||
def weekends() {
|
||||
["Saturday", "Sunday"]
|
||||
}
|
||||
|
||||
def schedulingPage() {
|
||||
dynamicPage(name: "schedulingPage", title: "Rules For Automatically Dimming Your Lights") {
|
||||
|
||||
section {
|
||||
input(name: "days", type: "enum", title: "Allow Automatic Dimming On These Days", description: "Every day", required: false, multiple: true, options: ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"])
|
||||
section("Use Other SmartApps!") {
|
||||
href(title: "Learn how to control Gentle Wake Up", page: "controllerExplanationPage", description: null)
|
||||
}
|
||||
|
||||
section {
|
||||
input(name: "modeStart", title: "Start when entering this mode", type: "mode", required: false, mutliple: false, submitOnChange: true)
|
||||
section("Allow Automatic Dimming") {
|
||||
input(name: "days", type: "enum", title: "On These Days", description: "Every day", required: false, multiple: true, options: weekdays() + weekends())
|
||||
}
|
||||
|
||||
section("Start Dimming...") {
|
||||
input(name: "startTime", type: "time", title: "At This Time", description: null, required: false)
|
||||
input(name: "modeStart", title: "When Entering This Mode", type: "mode", required: false, mutliple: false, submitOnChange: true, description: null)
|
||||
if (modeStart) {
|
||||
input(name: "modeStop", title: "Stop when leaving '${modeStart}' mode", type: "bool", required: false)
|
||||
}
|
||||
}
|
||||
|
||||
section {
|
||||
input(name: "startTime", type: "time", title: "Start Dimming At This Time", description: null, required: false)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -194,11 +241,16 @@ def updated() {
|
||||
log.debug "Updating 'Gentle Wake Up' with settings: ${settings}"
|
||||
unschedule()
|
||||
|
||||
def controller = getController()
|
||||
if (controller) {
|
||||
controller.label = app.label
|
||||
}
|
||||
|
||||
initialize()
|
||||
}
|
||||
|
||||
private initialize() {
|
||||
stop()
|
||||
stop("settingsChange")
|
||||
|
||||
if (startTime) {
|
||||
log.debug "scheduling dimming routine to run at $startTime"
|
||||
@@ -209,15 +261,27 @@ private initialize() {
|
||||
subscribe(app, appHandler)
|
||||
|
||||
subscribe(location, locationHandler)
|
||||
|
||||
if (manualOverride) {
|
||||
subscribe(dimmers, "switch.off", stopDimmersHandler)
|
||||
}
|
||||
|
||||
if (!getAllChildDevices()) {
|
||||
// create controller device and set name to the label used here
|
||||
def dni = "${new Date().getTime()}"
|
||||
log.debug "app.label: ${app.label}"
|
||||
addChildDevice("smartthings", "Gentle Wake Up Controller", dni, null, ["label": app.label])
|
||||
state.controllerDni = dni
|
||||
}
|
||||
}
|
||||
|
||||
def appHandler(evt) {
|
||||
log.debug "appHandler evt: ${evt.value}"
|
||||
if (evt.value == "touch") {
|
||||
if (atomicState.running) {
|
||||
stop()
|
||||
stop("appTouch")
|
||||
} else {
|
||||
start()
|
||||
start("appTouch")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -233,26 +297,47 @@ def locationHandler(evt) {
|
||||
def modeStopIsTrue = (modeStop && modeStop != "false")
|
||||
|
||||
if (isSpecifiedMode && canStartAutomatically()) {
|
||||
start()
|
||||
start("modeChange")
|
||||
} else if (!isSpecifiedMode && modeStopIsTrue) {
|
||||
stop()
|
||||
stop("modeChange")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
def stopDimmersHandler(evt) {
|
||||
log.trace "stopDimmersHandler evt: ${evt.value}"
|
||||
def percentComplete = completionPercentage()
|
||||
// Often times, the first thing we do is turn lights on or off so make sure we don't stop as soon as we start
|
||||
if (percentComplete > 2 && percentComplete < 98) {
|
||||
if (manualOverride == "cancel") {
|
||||
log.debug "STOPPING in stopDimmersHandler"
|
||||
stop("manualOverride")
|
||||
} else if (manualOverride == "jumpTo") {
|
||||
def end = dynamicEndLevel()
|
||||
log.debug "Jumping to 99% complete in stopDimmersHandler"
|
||||
jumpTo(99)
|
||||
}
|
||||
|
||||
} else {
|
||||
log.debug "not stopping in stopDimmersHandler"
|
||||
}
|
||||
}
|
||||
|
||||
// ========================================================
|
||||
// Scheduling
|
||||
// ========================================================
|
||||
|
||||
def scheduledStart() {
|
||||
if (canStartAutomatically()) {
|
||||
start()
|
||||
start("schedule")
|
||||
}
|
||||
}
|
||||
|
||||
def start() {
|
||||
public def start(source) {
|
||||
log.trace "START"
|
||||
|
||||
sendStartEvent(source)
|
||||
|
||||
setLevelsInState()
|
||||
|
||||
atomicState.running = true
|
||||
@@ -263,9 +348,11 @@ def start() {
|
||||
increment()
|
||||
}
|
||||
|
||||
def stop() {
|
||||
public def stop(source) {
|
||||
log.trace "STOP"
|
||||
|
||||
sendStopEvent(source)
|
||||
|
||||
atomicState.running = false
|
||||
atomicState.start = 0
|
||||
|
||||
@@ -282,6 +369,110 @@ private healthCheck() {
|
||||
increment()
|
||||
}
|
||||
|
||||
// ========================================================
|
||||
// Controller
|
||||
// ========================================================
|
||||
|
||||
def sendStartEvent(source) {
|
||||
log.trace "sendStartEvent(${source})"
|
||||
def eventData = [
|
||||
name: "sessionStatus",
|
||||
value: "running",
|
||||
descriptionText: "${app.label} has started dimming",
|
||||
displayed: true,
|
||||
linkText: app.label,
|
||||
isStateChange: true
|
||||
]
|
||||
if (source == "modeChange") {
|
||||
eventData.descriptionText += " because of a mode change"
|
||||
} else if (source == "schedule") {
|
||||
eventData.descriptionText += " as scheduled"
|
||||
} else if (source == "appTouch") {
|
||||
eventData.descriptionText += " because you pressed play on the app"
|
||||
} else if (source == "controller") {
|
||||
eventData.descriptionText += " because you pressed play on the controller"
|
||||
}
|
||||
|
||||
sendControllerEvent(eventData)
|
||||
}
|
||||
|
||||
def sendStopEvent(source) {
|
||||
log.trace "sendStopEvent(${source})"
|
||||
def eventData = [
|
||||
name: "sessionStatus",
|
||||
value: "stopped",
|
||||
descriptionText: "${app.label} has stopped dimming",
|
||||
displayed: true,
|
||||
linkText: app.label,
|
||||
isStateChange: true
|
||||
]
|
||||
if (source == "modeChange") {
|
||||
eventData.descriptionText += " because of a mode change"
|
||||
eventData.value += "cancelled"
|
||||
} else if (source == "schedule") {
|
||||
eventData.descriptionText = "${app.label} has finished dimming"
|
||||
} else if (source == "appTouch") {
|
||||
eventData.descriptionText += " because you pressed play on the app"
|
||||
eventData.value += "cancelled"
|
||||
} else if (source == "controller") {
|
||||
eventData.descriptionText += " because you pressed stop on the controller"
|
||||
eventData.value += "cancelled"
|
||||
} else if (source == "settingsChange") {
|
||||
eventData.descriptionText += " because the settings have changed"
|
||||
eventData.value += "cancelled"
|
||||
} else if (source == "manualOverride") {
|
||||
eventData.descriptionText += " because the dimmer was manually turned off"
|
||||
eventData.value += "cancelled"
|
||||
}
|
||||
|
||||
sendControllerEvent(eventData)
|
||||
sendTimeRemainingEvent(0)
|
||||
}
|
||||
|
||||
def sendTimeRemainingEvent(percentComplete) {
|
||||
log.trace "sendTimeRemainingEvent(${percentComplete})"
|
||||
|
||||
def percentCompleteEventData = [
|
||||
name: "percentComplete",
|
||||
value: percentComplete as int,
|
||||
displayed: true,
|
||||
isStateChange: true
|
||||
]
|
||||
sendControllerEvent(percentCompleteEventData)
|
||||
|
||||
def duration = sanitizeInt(duration, 30)
|
||||
def timeRemaining = duration - (duration * (percentComplete / 100))
|
||||
def timeRemainingEventData = [
|
||||
name: "timeRemaining",
|
||||
value: displayableTime(timeRemaining),
|
||||
displayed: true,
|
||||
isStateChange: true
|
||||
]
|
||||
sendControllerEvent(timeRemainingEventData)
|
||||
}
|
||||
|
||||
def sendControllerEvent(eventData) {
|
||||
def controller = getController()
|
||||
if (controller) {
|
||||
controller.controllerEvent(eventData)
|
||||
}
|
||||
}
|
||||
|
||||
def getController() {
|
||||
def dni = state.controllerDni
|
||||
if (!dni) {
|
||||
log.warn "no controller dni"
|
||||
return null
|
||||
}
|
||||
def controller = getChildDevice(dni)
|
||||
if (!controller) {
|
||||
log.warn "no controller"
|
||||
return null
|
||||
}
|
||||
log.debug "controller: ${controller}"
|
||||
return controller
|
||||
}
|
||||
|
||||
// ========================================================
|
||||
// Setting levels
|
||||
// ========================================================
|
||||
@@ -349,6 +540,8 @@ def updateDimmers(percentComplete) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
sendTimeRemainingEvent(percentComplete)
|
||||
}
|
||||
|
||||
int dynamicLevel(dimmer, percentComplete) {
|
||||
@@ -377,7 +570,7 @@ private completion() {
|
||||
return
|
||||
}
|
||||
|
||||
stop()
|
||||
stop("schedule")
|
||||
|
||||
handleCompletionSwitches()
|
||||
|
||||
@@ -385,6 +578,7 @@ private completion() {
|
||||
|
||||
handleCompletionModesAndPhrases()
|
||||
|
||||
sendTimeRemainingEvent(100)
|
||||
}
|
||||
|
||||
private handleCompletionSwitches() {
|
||||
@@ -493,22 +687,65 @@ def completionPercentage() {
|
||||
return
|
||||
}
|
||||
|
||||
int now = new Date().getTime()
|
||||
int diff = now - atomicState.start
|
||||
int totalRunTime = totalRunTimeMillis()
|
||||
int percentOfRunTime = (diff / totalRunTime) * 100
|
||||
log.debug "percentOfRunTime: ${percentOfRunTime}"
|
||||
def now = new Date().getTime()
|
||||
def timeElapsed = now - atomicState.start
|
||||
def totalRunTime = totalRunTimeMillis()
|
||||
def percentComplete = timeElapsed / totalRunTime * 100
|
||||
log.debug "percentComplete: ${percentComplete}"
|
||||
|
||||
percentOfRunTime
|
||||
return percentComplete
|
||||
}
|
||||
|
||||
int totalRunTimeMillis() {
|
||||
int minutes = sanitizeInt(duration, 30)
|
||||
convertToMillis(minutes)
|
||||
}
|
||||
|
||||
int convertToMillis(minutes) {
|
||||
def seconds = minutes * 60
|
||||
def millis = seconds * 1000
|
||||
return millis as int
|
||||
return millis
|
||||
}
|
||||
|
||||
def timeRemaining(percentComplete) {
|
||||
def normalizedPercentComplete = percentComplete / 100
|
||||
def duration = sanitizeInt(duration, 30)
|
||||
def timeElapsed = duration * normalizedPercentComplete
|
||||
def timeRemaining = duration - timeElapsed
|
||||
return timeRemaining
|
||||
}
|
||||
|
||||
int millisToEnd(percentComplete) {
|
||||
convertToMillis(timeRemaining(percentComplete))
|
||||
}
|
||||
|
||||
String displayableTime(timeRemaining) {
|
||||
def timeString = "${timeRemaining}"
|
||||
def parts = timeString.split(/\./)
|
||||
if (!parts.size()) {
|
||||
return "0:00"
|
||||
}
|
||||
def minutes = parts[0]
|
||||
if (parts.size() == 1) {
|
||||
return "${minutes}:00"
|
||||
}
|
||||
def fraction = "0.${parts[1]}" as double
|
||||
def seconds = "${60 * fraction as int}".padRight(2, "0")
|
||||
return "${minutes}:${seconds}"
|
||||
}
|
||||
|
||||
def jumpTo(percentComplete) {
|
||||
def millisToEnd = millisToEnd(percentComplete)
|
||||
def endTime = new Date().getTime() + millisToEnd
|
||||
def duration = sanitizeInt(duration, 30)
|
||||
def durationMillis = convertToMillis(duration)
|
||||
def shiftedStart = endTime - durationMillis
|
||||
atomicState.start = shiftedStart
|
||||
updateDimmers(percentComplete)
|
||||
sendTimeRemainingEvent(percentComplete)
|
||||
}
|
||||
|
||||
|
||||
int dynamicEndLevel() {
|
||||
if (usesOldSettings()) {
|
||||
if (direction && direction == "Down") {
|
||||
@@ -673,7 +910,13 @@ def schedulingHrefDescription() {
|
||||
|
||||
def descriptionParts = []
|
||||
if (days) {
|
||||
descriptionParts << "On ${fancyString(days)},"
|
||||
if (days == weekdays()) {
|
||||
descriptionParts << "On weekdays,"
|
||||
} else if (days == weekends()) {
|
||||
descriptionParts << "On weekends,"
|
||||
} else {
|
||||
descriptionParts << "On ${fancyString(days)},"
|
||||
}
|
||||
}
|
||||
|
||||
descriptionParts << "${fancyDeviceString(dimmers)} will start dimming"
|
||||
@@ -759,15 +1002,15 @@ def completionHrefDescription() {
|
||||
|
||||
def numbersPageHrefDescription() {
|
||||
def title = "All dimmers will dim for ${duration ?: '30'} minutes from ${startLevelLabel()} to ${endLevelLabel()}"
|
||||
if (colorize) {
|
||||
def colorDimmers = dimmersWithSetColorCommand()
|
||||
if (colorDimmers == dimmers) {
|
||||
title += " and will gradually change color."
|
||||
} else {
|
||||
title += ".\n${fancyDeviceString(colorDimmers)} will gradually change color."
|
||||
}
|
||||
}
|
||||
return title
|
||||
if (colorize) {
|
||||
def colorDimmers = dimmersWithSetColorCommand()
|
||||
if (colorDimmers == dimmers) {
|
||||
title += " and will gradually change color."
|
||||
} else {
|
||||
title += ".\n${fancyDeviceString(colorDimmers)} will gradually change color."
|
||||
}
|
||||
}
|
||||
return title
|
||||
}
|
||||
|
||||
def hueSatToHex(h, s) {
|
||||
|
||||
Reference in New Issue
Block a user