Compare commits

...

28 Commits

Author SHA1 Message Date
Ben Boggess
fe2b36cd30 Revert "Convert closure to method" 2016-02-10 10:46:48 -06:00
Ben Boggess
55c17383e1 Merge pull request #490 from boggebe/boggess/convert_hexToSignedInt_to_method
Convert closure to method
2016-02-10 06:53:31 -06:00
boggebe
bd9a1d1dc5 Closure was causing sandbox issues locally 2016-02-08 16:03:01 -06:00
Vinay Rao
a6bec43f2a Merge pull request #474 from tpmanley/bugfix/multi_cmd_failure
multi: Fix occasional failures in sending ZigBee commands during configure
2016-02-02 23:04:58 -04:00
Tom Manley
5ec82217ac multi: Fix occasional failures in sending ZigBee commands during configure
The missing delay after some of the commands was sometimes causing the
ZigBee command to fail.

Resolves:
    https://smartthings.atlassian.net/browse/DVCSMP-1225
2016-01-29 14:41:12 -06:00
Kristofer Schaller
3b89368d45 Merge pull request #467 from davidsulpy/master
New Initial State Event Streamer: removed buffering and sched-tasks
2016-01-25 13:08:51 -08:00
David Sulpy
12f6039de5 New Initial State Event Streamer: removed buffering and scheduled tasks for flushing buffer 2016-01-22 19:23:37 -06:00
Vinay Rao
65c9da32e7 Merge pull request #464 from workingmonk/smartpower_v1
[DVCSMP-1430] updating fingerprint for the problem device
2016-01-21 23:29:43 -08:00
Vinay Rao
7147770e2d updating fingerprint for the problem device 2016-01-21 18:20:03 -08:00
Jason Botello
5eb33eca19 Merge pull request #387 from SmartThingsCommunity/MSA-757-1
MSA-757: Timevalve Smart
2016-01-20 16:25:05 -08:00
Jason Botello
ec321ce85d Merge pull request #425 from hhhss91/MSA-786-1
MSA-786: Encored Technologies : Smart Energy Service
2016-01-20 16:24:46 -08:00
Jason Botello
7669bec0bc Update timevalve-smart.groovy
Commenting out fingerprint temporarily to avoid potential conflicts with other devices as this devices is specifically for a Korean deployment in AP01 - see DVCSMP-1425
2016-01-20 16:17:32 -08:00
Steve Vlaminck
a20a58bd48 Merge pull request #458 from vlaminck/gentle-wake-up-controller
Added Gentle Wake Up controller, updated SmartApp
2016-01-20 14:03:12 -06:00
vlaminck
efabd07dea Added Gentle Wake Up controller, updated SmartApp 2016-01-20 09:05:47 -06:00
Tyler Lange
1e55f62048 Merge pull request #454 from workingmonk/deprecate_osram_flex
deprecating this DTH as the fingerprints have been moved to ZigBee RGBW
2016-01-19 16:06:54 -08:00
Vinay Rao
12b18eae08 deprecating this DTH as the fingerprints have been moved to ZigBee RGBW Bulb 2016-01-19 16:02:35 -08:00
Tyler Lange
aad82e10ef Merge pull request #451 from workingmonk/zigbee_color_bulb
Adding ZigBee RGBW DTH - generic ZigBee DTH for OSRAM Flex Strip, OSRAM A19 RGBW, OSRAM BR30 RGBW, OSRAM RT5/6
2016-01-19 15:55:07 -08:00
Vinay Rao
b11b362c60 Adding ZigBee RGBW DTH 2016-01-19 15:40:43 -08:00
Tom Manley
e1853b8e50 Merge pull request #443 from tpmanley/feature/sensor_updates
Feature/sensor updates
2016-01-19 13:43:40 -06:00
Tom Manley
968834e33e Use table for battery voltage to percent remaining calculation
The new table based approach yields a more accurate battery percentage
remaining than the old linear calculation.

Resolves:
    https://smartthings.atlassian.net/browse/SMJN-39
2016-01-19 13:40:02 -06:00
Tom Manley
e51a38eb28 Fix whitespace issues - no code changes
Replaced spaces with tabs for indentation and removed some unnecessary
white space.
2016-01-18 11:00:37 -06:00
Tom Manley
553b45a3f3 Merge pull request #441 from tpmanley/bugfix/arrival_readattr_events
arrival: Keep 'read attr' messages from being turned into events
2016-01-15 09:51:39 -06:00
Tom Manley
d9ab3bca00 arrival: Keep 'read attr' messages from being turned into events
Previously parse was returning null which causes the platform to create an event
using the message passed to parse. We don't want that to happen so return
an empty list instead.

Resolves:
    https://smartthings.atlassian.net/browse/SMJN-38
2016-01-14 15:34:08 -06:00
Juan Pablo Risso
56cfe9e936 Merge pull request #430 from juano2310/Wemo
PROB-537 - Fix error in line 337
2016-01-13 13:20:10 -05:00
Lee Joonmin
3a7abd6169 Modifying 'Timevalve Smart' 2016-01-12 18:01:23 -06:00
Juan Risso
0d214b742e PROB-537 - Fix error in line 337 2016-01-11 12:10:13 -05:00
hyeon seok yang
c20026b376 MSA-786: Description from the app : "With visible realtime energy usage status, have good energy habits and enrich your life"
This app is specialized to show energy data which was grabbed from Encored Technologies' device that user installed at their home.
2016-01-08 16:01:05 +09:00
Lee Joonmin
7def620f04 MSA-757: Smart Gas-Lock work with z-wave 2015-12-23 03:44:19 -06:00
14 changed files with 2859 additions and 580 deletions

View File

@@ -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>
"""
}
}
}

View File

@@ -76,6 +76,8 @@ def parse(String description) {
if (description?.startsWith('read attr -')) { if (description?.startsWith('read attr -')) {
handleReportAttributeMessage(description) handleReportAttributeMessage(description)
} }
return []
} }
private handleReportAttributeMessage(String description) { private handleReportAttributeMessage(String description) {
@@ -86,31 +88,37 @@ private handleReportAttributeMessage(String description) {
} }
} }
private handleBatteryEvent(rawValue) { /**
def linkText = getLinkText(device) * Create battery event from reported battery voltage.
*
def eventMap = [ * @param volts Battery voltage in .1V increments
name: 'battery', */
value: '--' private handleBatteryEvent(volts) {
] if (volts == 0 || volts == 255) {
log.debug "Ignoring invalid value for voltage (${volts/10}V)"
def volts = rawValue / 10 }
if (volts > 0){ else {
def minVolts = 2.0 def batteryMap = [28:100, 27:100, 26:100, 25:90, 24:90, 23:70,
def maxVolts = 2.8 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) if (volts < minVolts)
volts = minVolts volts = minVolts
else if (volts > maxVolts) else if (volts > maxVolts)
volts = maxVolts volts = maxVolts
def pct = (volts - minVolts) / (maxVolts - minVolts) def pct = batteryMap[volts]
if (pct != null) {
eventMap.value = Math.round(pct * 100) def linkText = getLinkText(device)
eventMap.descriptionText = "${linkText} battery was ${eventMap.value}%" def eventMap = [
} name: 'battery',
value: pct,
log.debug "Creating battery event: ${eventMap}" descriptionText: "${linkText} battery was ${pct}%"
]
log.debug "Creating battery event for voltage=${volts/10}V: ${eventMap}"
sendEvent(eventMap) sendEvent(eventMap)
}
}
} }
private handlePresenceEvent(present) { private handlePresenceEvent(present) {

View File

@@ -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)
}

View File

@@ -4,6 +4,7 @@
Osram bulbs have a firmware issue causing it to forget its dimming level when turned off (via commands). Handling Osram bulbs have a firmware issue causing it to forget its dimming level when turned off (via commands). Handling
that issue by using state variables that issue by using state variables
*/ */
//DEPRECATED - Using the generic DTH for this device. Users need to be moved before deleting this DTH
metadata { metadata {
definition (name: "OSRAM LIGHTIFY LED Flexible Strip RGBW", namespace: "smartthings", author: "SmartThings") { definition (name: "OSRAM LIGHTIFY LED Flexible Strip RGBW", namespace: "smartthings", author: "SmartThings") {
@@ -23,8 +24,8 @@ metadata {
command "setAdjustedColor" command "setAdjustedColor"
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY Flex RGBW" //fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY Flex RGBW"
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "Flex RGBW" //fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04,FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "Flex RGBW"
} }

View File

@@ -5,7 +5,7 @@ metadata {
capability "Switch" capability "Switch"
capability "Sensor" 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 // simulator metadata

View File

@@ -215,18 +215,39 @@ def getTemperature(value) {
} }
private Map getBatteryResult(rawValue) { private Map getBatteryResult(rawValue) {
log.debug 'Battery' log.debug "Battery rawValue = ${rawValue}"
def linkText = getLinkText(device) def linkText = getLinkText(device)
def result = [ def result = [
name: 'battery' name: 'battery',
value: '--'
] ]
def volts = rawValue / 10 def volts = rawValue / 10
def descriptionText
if (rawValue == 0 || rawValue == 255) {}
else {
if (volts > 3.5) { if (volts > 3.5) {
result.descriptionText = "${linkText} battery has too much power (${volts} volts)." result.descriptionText = "${linkText} battery has too much power (${volts} volts)."
} }
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 { else {
def minVolts = 2.1 def minVolts = 2.1
def maxVolts = 3.0 def maxVolts = 3.0
@@ -234,6 +255,8 @@ private Map getBatteryResult(rawValue) {
result.value = Math.min(100, (int) pct * 100) result.value = Math.min(100, (int) pct * 100)
result.descriptionText = "${linkText} battery was ${result.value}%" result.descriptionText = "${linkText} battery was ${result.value}%"
} }
}
}
return result return result
} }

View File

@@ -230,25 +230,40 @@ def getTemperature(value) {
} }
private Map getBatteryResult(rawValue) { private Map getBatteryResult(rawValue) {
log.debug 'Battery' log.debug "Battery rawValue = ${rawValue}"
def linkText = getLinkText(device) def linkText = getLinkText(device)
log.debug rawValue
def result = [ def result = [
name: 'battery', name: 'battery',
value: '--' value: '--'
] ]
def volts = rawValue / 10 def volts = rawValue / 10
def descriptionText
if (rawValue == 0) {} if (rawValue == 0 || rawValue == 255) {}
else { else {
if (volts > 3.5) { if (volts > 3.5) {
result.descriptionText = "${linkText} battery has too much power (${volts} volts)." result.descriptionText = "${linkText} battery has too much power (${volts} volts)."
} }
else if (volts > 0){ 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 minVolts = 2.1
def maxVolts = 3.0 def maxVolts = 3.0
def pct = (volts - minVolts) / (maxVolts - minVolts) def pct = (volts - minVolts) / (maxVolts - minVolts)
@@ -256,6 +271,7 @@ private Map getBatteryResult(rawValue) {
result.descriptionText = "${linkText} battery was ${result.value}%" result.descriptionText = "${linkText} battery was ${result.value}%"
} }
} }
}
return result return result
} }

View File

@@ -14,7 +14,7 @@
* *
*/ */
metadata { metadata {
definition (name: "SmartSense Multi Sensor", namespace: "smartthings", author: "SmartThings") { definition (name: "SmartSense Multi Sensor", namespace: "smartthings", author: "SmartThings") {
capability "Three Axis" capability "Three Axis"
@@ -141,7 +141,7 @@ def parse(String description) {
return result return result
} }
private Map parseCatchAllMessage(String description) { private Map parseCatchAllMessage(String description) {
Map resultMap = [:] Map resultMap = [:]
def cluster = zigbee.parse(description) def cluster = zigbee.parse(description)
log.debug cluster log.debug cluster
@@ -166,7 +166,7 @@ def parse(String description) {
} }
return resultMap return resultMap
} }
private boolean shouldProcessMessage(cluster) { private boolean shouldProcessMessage(cluster) {
// 0x0B is default response indicating message got through // 0x0B is default response indicating message got through
@@ -302,9 +302,8 @@ def getTemperature(value) {
} }
} }
private Map getBatteryResult(rawValue) { private Map getBatteryResult(rawValue) {
log.debug "Battery" log.debug "Battery rawValue = ${rawValue}"
log.debug rawValue
def linkText = getLinkText(device) def linkText = getLinkText(device)
def result = [ def result = [
@@ -313,26 +312,44 @@ def getTemperature(value) {
] ]
def volts = rawValue / 10 def volts = rawValue / 10
def descriptionText
if (rawValue == 255) {} if (rawValue == 0 || rawValue == 255) {}
else { else {
if (volts > 3.5) { if (volts > 3.5) {
result.descriptionText = "${linkText} battery has too much power (${volts} volts)." result.descriptionText = "${linkText} battery has too much power (${volts} volts)."
} }
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 { else {
def minVolts = 2.1 def minVolts = 2.1
def maxVolts = 3.0 def maxVolts = 3.0
def pct = (volts - minVolts) / (maxVolts - minVolts) def pct = (volts - minVolts) / (maxVolts - minVolts)
result.value = Math.min(100, (int) pct * 100) result.value = Math.min(100, (int) pct * 100)
result.descriptionText = "${linkText} battery was ${result.value}%" result.descriptionText = "${linkText} battery was ${result.value}%"
}} }
}
return result
} }
private Map getTemperatureResult(value) { return result
}
private Map getTemperatureResult(value) {
log.debug "Temperature" log.debug "Temperature"
def linkText = getLinkText(device) def linkText = getLinkText(device)
if (tempOffset) { if (tempOffset) {
@@ -346,17 +363,17 @@ def getTemperature(value) {
value: value, value: value,
descriptionText: descriptionText descriptionText: descriptionText
] ]
} }
private Map getContactResult(value) { private Map getContactResult(value) {
log.debug "Contact" log.debug "Contact"
def linkText = getLinkText(device) def linkText = getLinkText(device)
def descriptionText = "${linkText} was ${value == 'open' ? 'opened' : 'closed'}" def descriptionText = "${linkText} was ${value == 'open' ? 'opened' : 'closed'}"
sendEvent(name: 'contact', value: value, descriptionText: descriptionText, displayed:false) sendEvent(name: 'contact', value: value, descriptionText: descriptionText, displayed:false)
sendEvent(name: 'status', value: value, descriptionText: descriptionText) sendEvent(name: 'status', value: value, descriptionText: descriptionText)
} }
private getAccelerationResult(numValue) { private getAccelerationResult(numValue) {
log.debug "Acceleration" log.debug "Acceleration"
def name = "acceleration" def name = "acceleration"
def value = numValue.endsWith("1") ? "active" : "inactive" def value = numValue.endsWith("1") ? "active" : "inactive"
@@ -369,18 +386,16 @@ def getTemperature(value) {
descriptionText: descriptionText, descriptionText: descriptionText,
isStateChange: isStateChange isStateChange: isStateChange
] ]
} }
def refresh() { def refresh() {
log.debug "Refreshing Values " log.debug "Refreshing Values "
def refreshCmds = [] def refreshCmds = []
if (device.getDataValue("manufacturer") == "SmartThings") { if (device.getDataValue("manufacturer") == "SmartThings") {
log.debug "Refreshing Values for manufacturer: SmartThings " log.debug "Refreshing Values for manufacturer: SmartThings "
refreshCmds = refreshCmds + [ refreshCmds = refreshCmds + [
/* These values of Motion Threshold Multiplier(01) and Motion Threshold (7602) /* 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. 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 Separating these out in a separate if-else because I do not want to touch Centralite part
@@ -394,13 +409,9 @@ def getTemperature(value) {
"zcl mfg-code ${manufacturerCode}", "delay 200", "zcl mfg-code ${manufacturerCode}", "delay 200",
"zcl global write 0xFC02 2 0x21 {7602}", "delay 200", "zcl global write 0xFC02 2 0x21 {7602}", "delay 200",
"send 0x${device.deviceNetworkId} 1 1", "delay 400", "send 0x${device.deviceNetworkId} 1 1", "delay 400",
] ]
} else { } else {
refreshCmds = refreshCmds + [ refreshCmds = refreshCmds + [
/* sensitivity - default value (8) */ /* sensitivity - default value (8) */
"zcl mfg-code ${manufacturerCode}", "delay 200", "zcl mfg-code ${manufacturerCode}", "delay 200",
"zcl global write 0xFC02 0 0x20 {02}", "delay 200", "zcl global write 0xFC02 0 0x20 {02}", "delay 200",
@@ -419,43 +430,40 @@ def getTemperature(value) {
] ]
return refreshCmds + enrollResponse() return refreshCmds + enrollResponse()
} }
def configure() {
def configure() {
String zigbeeEui = swapEndianHex(device.hub.zigbeeEui) String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
log.debug "Configuring Reporting" log.debug "Configuring Reporting"
def configCmds = [ def configCmds = [
"zcl global write 0x500 0x10 0xf0 {${zigbeeEui}}", "delay 200", "zcl global write 0x500 0x10 0xf0 {${zigbeeEui}}", "delay 200",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500", "send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 1 {${device.zigbeeId}} {}", "delay 200", "zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 1 {${device.zigbeeId}} {}", "delay 200",
"zcl global send-me-a-report 1 0x20 0x20 30 21600 {01}", //checkin time 6 hrs "zcl global send-me-a-report 1 0x20 0x20 30 21600 {01}", "delay 200", //checkin time 6 hrs
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500", "send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 0x402 {${device.zigbeeId}} {}", "delay 200", "zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 0x402 {${device.zigbeeId}} {}", "delay 200",
"zcl global send-me-a-report 0x402 0 0x29 30 3600 {6400}", "zcl global send-me-a-report 0x402 0 0x29 30 3600 {6400}", "delay 200",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500", "send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 0xFC02 {${device.zigbeeId}} {}", "delay 200", "zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 0xFC02 {${device.zigbeeId}} {}", "delay 200",
"zcl mfg-code ${manufacturerCode}", "zcl mfg-code ${manufacturerCode}", "delay 200",
"zcl global send-me-a-report 0xFC02 0x0010 0x18 10 3600 {01}", "zcl global send-me-a-report 0xFC02 0x0010 0x18 10 3600 {01}", "delay 200",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500", "send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
"zcl mfg-code ${manufacturerCode}", "zcl mfg-code ${manufacturerCode}", "delay 200",
"zcl global send-me-a-report 0xFC02 0x0012 0x29 1 3600 {01}", "zcl global send-me-a-report 0xFC02 0x0012 0x29 1 3600 {01}", "delay 200",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500", "send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
"zcl mfg-code ${manufacturerCode}", "zcl mfg-code ${manufacturerCode}", "delay 200",
"zcl global send-me-a-report 0xFC02 0x0013 0x29 1 3600 {01}", "zcl global send-me-a-report 0xFC02 0x0013 0x29 1 3600 {01}", "delay 200",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500", "send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
"zcl mfg-code ${manufacturerCode}", "zcl mfg-code ${manufacturerCode}", "delay 200",
"zcl global send-me-a-report 0xFC02 0x0014 0x29 1 3600 {01}", "zcl global send-me-a-report 0xFC02 0x0014 0x29 1 3600 {01}", "delay 200",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500" "send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500"
] ]
return configCmds + refresh() return configCmds + refresh()
@@ -473,7 +481,7 @@ def enrollResponse() {
"zcl global write 0x500 0x10 0xf0 {${zigbeeEui}}", "delay 200", "zcl global write 0x500 0x10 0xf0 {${zigbeeEui}}", "delay 200",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500", "send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
//Enroll Response //Enroll Response
"raw 0x500 {01 23 00 00 00}", "raw 0x500 {01 23 00 00 00}", "delay 200",
"send 0x${device.deviceNetworkId} 1 1", "delay 200" "send 0x${device.deviceNetworkId} 1 1", "delay 200"
] ]
} }
@@ -582,5 +590,3 @@ private byte[] reverseArray(byte[] array) {
} }
return array return array
} }

View File

@@ -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
}

View File

@@ -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)
}

View File

@@ -1,7 +1,7 @@
/** /**
* Initial State Event Streamer * 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 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at: * in compliance with the License. You may obtain a copy of the License at:
@@ -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() { def subscribeToEvents() {
if (accelerometers != null) { if (accelerometers != null) {
subscribe(accelerometers, "acceleration", genericHandler) 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() { def installed() {
atomicState.version = "1.0.18" atomicState.version = "1.1.0"
atomicState.isBucketCreated = false
atomicState.grokerSubdomain = "groker"
subscribeToEvents() subscribeToEvents()
atomicState.isBucketCreated = false atomicState.isBucketCreated = false
atomicState.grokerSubdomain = "groker" atomicState.grokerSubdomain = "groker"
atomicState.eventBuffer = []
runEvery15Minutes(flushBuffer)
log.debug "installed (version $atomicState.version)" log.debug "installed (version $atomicState.version)"
} }
def updated() { def updated() {
atomicState.version = "1.0.18" atomicState.version = "1.1.0"
unsubscribe() unsubscribe()
if (atomicState.bucketKey != null && atomicState.accessKey != null) { if (atomicState.bucketKey != null && atomicState.accessKey != null) {
atomicState.isBucketCreated = false atomicState.isBucketCreated = false
} }
if (atomicState.eventBuffer == null) {
atomicState.eventBuffer = []
}
if (atomicState.grokerSubdomain == null || atomicState.grokerSubdomain == "") { if (atomicState.grokerSubdomain == null || atomicState.grokerSubdomain == "") {
atomicState.grokerSubdomain = "groker" atomicState.grokerSubdomain = "groker"
} }
@@ -327,37 +325,17 @@ def genericHandler(evt) {
eventHandler(key, value) 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 eventHandler(name, value) {
def epoch = now() / 1000 def epoch = now() / 1000
def eventBuffer = atomicState.eventBuffer ?: []
eventBuffer << [key: "$name", value: "$value", epoch: "$epoch"]
if (eventBuffer.size() >= 10) { def event = new JsonSlurper().parseText("{\"key\": \"$name\", \"value\": \"$value\", \"epoch\": \"$epoch\"}")
// 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 tryShipEvents(event)
// from growing unbounded.
atomicState.eventBuffer = [] log.debug "Shipped Event: " + event
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
} }
// a helper function for shipping the atomicState.eventBuffer to Initial State def tryShipEvents(event) {
def tryShipEvents(eventBuffer) {
def grokerSubdomain = atomicState.grokerSubdomain def grokerSubdomain = atomicState.grokerSubdomain
// can't ship events if there is no grokerSubdomain // can't ship events if there is no grokerSubdomain
@@ -380,7 +358,7 @@ def tryShipEvents(eventBuffer) {
"X-IS-AccessKey": "${accessKey}", "X-IS-AccessKey": "${accessKey}",
"Accept-Version": "0.0.2" "Accept-Version": "0.0.2"
], ],
body: eventBuffer body: event
] ]
try { try {

View File

@@ -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 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at: * in compliance with the License. You may obtain a copy of the License at:
@@ -38,37 +38,75 @@ preferences {
page(name: "schedulingPage") page(name: "schedulingPage")
page(name: "completionPage") page(name: "completionPage")
page(name: "numbersPage") page(name: "numbersPage")
page(name: "controllerExplanationPage")
} }
def rootPage() { def rootPage() {
dynamicPage(name: "rootPage", title: "", install: true, uninstall: true) { 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) 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) { if (dimmers) {
section { section("Gentle Wake Up Has A Controller") {
href(name: "toNumbersPage", page: "numbersPage", title: "Duration & Direction", description: numbersPageHrefDescription(), state: "complete") href(title: "Learn how to control Gentle Wake Up", page: "controllerExplanationPage", description: null)
} }
section { section("Rules For Dimming") {
href(name: "toSchedulingPage", page: "schedulingPage", title: "Rules For Automatically Dimming Your Lights", description: schedulingHrefDescription(), state: schedulingHrefDescription() ? "complete" : "") 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 {
href(name: "toCompletionPage", title: "Completion Actions (Optional)", page: "completionPage", state: completionHrefDescription() ? "complete" : "", description: completionHrefDescription())
} }
section { section {
// TODO: fancy label // 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() { def numbersPage() {
dynamicPage(name:"numbersPage", title:"") { dynamicPage(name:"numbersPage", title:"") {
@@ -128,24 +166,33 @@ def endLevelLabel() {
return "${endLevel}%" return "${endLevel}%"
} }
def weekdays() {
["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"]
}
def weekends() {
["Saturday", "Sunday"]
}
def schedulingPage() { def schedulingPage() {
dynamicPage(name: "schedulingPage", title: "Rules For Automatically Dimming Your Lights") { dynamicPage(name: "schedulingPage", title: "Rules For Automatically Dimming Your Lights") {
section { section("Use Other SmartApps!") {
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"]) href(title: "Learn how to control Gentle Wake Up", page: "controllerExplanationPage", description: null)
} }
section { section("Allow Automatic Dimming") {
input(name: "modeStart", title: "Start when entering this mode", type: "mode", required: false, mutliple: false, submitOnChange: true) 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) { if (modeStart) {
input(name: "modeStop", title: "Stop when leaving '${modeStart}' mode", type: "bool", required: false) 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}" log.debug "Updating 'Gentle Wake Up' with settings: ${settings}"
unschedule() unschedule()
def controller = getController()
if (controller) {
controller.label = app.label
}
initialize() initialize()
} }
private initialize() { private initialize() {
stop() stop("settingsChange")
if (startTime) { if (startTime) {
log.debug "scheduling dimming routine to run at $startTime" log.debug "scheduling dimming routine to run at $startTime"
@@ -209,15 +261,27 @@ private initialize() {
subscribe(app, appHandler) subscribe(app, appHandler)
subscribe(location, locationHandler) 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) { def appHandler(evt) {
log.debug "appHandler evt: ${evt.value}" log.debug "appHandler evt: ${evt.value}"
if (evt.value == "touch") { if (evt.value == "touch") {
if (atomicState.running) { if (atomicState.running) {
stop() stop("appTouch")
} else { } else {
start() start("appTouch")
} }
} }
} }
@@ -233,26 +297,47 @@ def locationHandler(evt) {
def modeStopIsTrue = (modeStop && modeStop != "false") def modeStopIsTrue = (modeStop && modeStop != "false")
if (isSpecifiedMode && canStartAutomatically()) { if (isSpecifiedMode && canStartAutomatically()) {
start() start("modeChange")
} else if (!isSpecifiedMode && modeStopIsTrue) { } 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 // Scheduling
// ======================================================== // ========================================================
def scheduledStart() { def scheduledStart() {
if (canStartAutomatically()) { if (canStartAutomatically()) {
start() start("schedule")
} }
} }
def start() { public def start(source) {
log.trace "START" log.trace "START"
sendStartEvent(source)
setLevelsInState() setLevelsInState()
atomicState.running = true atomicState.running = true
@@ -263,9 +348,11 @@ def start() {
increment() increment()
} }
def stop() { public def stop(source) {
log.trace "STOP" log.trace "STOP"
sendStopEvent(source)
atomicState.running = false atomicState.running = false
atomicState.start = 0 atomicState.start = 0
@@ -282,6 +369,110 @@ private healthCheck() {
increment() 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 // Setting levels
// ======================================================== // ========================================================
@@ -349,6 +540,8 @@ def updateDimmers(percentComplete) {
} }
} }
sendTimeRemainingEvent(percentComplete)
} }
int dynamicLevel(dimmer, percentComplete) { int dynamicLevel(dimmer, percentComplete) {
@@ -377,7 +570,7 @@ private completion() {
return return
} }
stop() stop("schedule")
handleCompletionSwitches() handleCompletionSwitches()
@@ -385,6 +578,7 @@ private completion() {
handleCompletionModesAndPhrases() handleCompletionModesAndPhrases()
sendTimeRemainingEvent(100)
} }
private handleCompletionSwitches() { private handleCompletionSwitches() {
@@ -493,22 +687,65 @@ def completionPercentage() {
return return
} }
int now = new Date().getTime() def now = new Date().getTime()
int diff = now - atomicState.start def timeElapsed = now - atomicState.start
int totalRunTime = totalRunTimeMillis() def totalRunTime = totalRunTimeMillis()
int percentOfRunTime = (diff / totalRunTime) * 100 def percentComplete = timeElapsed / totalRunTime * 100
log.debug "percentOfRunTime: ${percentOfRunTime}" log.debug "percentComplete: ${percentComplete}"
percentOfRunTime return percentComplete
} }
int totalRunTimeMillis() { int totalRunTimeMillis() {
int minutes = sanitizeInt(duration, 30) int minutes = sanitizeInt(duration, 30)
convertToMillis(minutes)
}
int convertToMillis(minutes) {
def seconds = minutes * 60 def seconds = minutes * 60
def millis = seconds * 1000 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() { int dynamicEndLevel() {
if (usesOldSettings()) { if (usesOldSettings()) {
if (direction && direction == "Down") { if (direction && direction == "Down") {
@@ -673,8 +910,14 @@ def schedulingHrefDescription() {
def descriptionParts = [] def descriptionParts = []
if (days) { if (days) {
if (days == weekdays()) {
descriptionParts << "On weekdays,"
} else if (days == weekends()) {
descriptionParts << "On weekends,"
} else {
descriptionParts << "On ${fancyString(days)}," descriptionParts << "On ${fancyString(days)},"
} }
}
descriptionParts << "${fancyDeviceString(dimmers)} will start dimming" descriptionParts << "${fancyDeviceString(dimmers)} will start dimming"

View File

@@ -258,8 +258,8 @@ def installed() {
def updated() { def updated() {
log.trace "Updated with settings: ${settings}" log.trace "Updated with settings: ${settings}"
unschedule()
unsubscribe() unsubscribe()
unschedule()
initialize() initialize()
} }
@@ -325,6 +325,7 @@ def addBulbs() {
} else { } else {
d = addChildDevice("smartthings", "Hue Bulb", dni, newHueBulb?.value.hub, ["label":newHueBulb?.value.name]) d = addChildDevice("smartthings", "Hue Bulb", dni, newHueBulb?.value.hub, ["label":newHueBulb?.value.name])
} }
log.debug "created ${d.displayName} with id $dni"
} else { } else {
log.debug "$dni in not longer paired to the Hue Bridge or ID changed" log.debug "$dni in not longer paired to the Hue Bridge or ID changed"
} }
@@ -333,8 +334,6 @@ def addBulbs() {
newHueBulb = bulbs.find { (app.id + "/" + it.id) == dni } newHueBulb = bulbs.find { (app.id + "/" + it.id) == dni }
d = addChildDevice("smartthings", "Hue Bulb", dni, newHueBulb?.hub, ["label":newHueBulb?.name]) d = addChildDevice("smartthings", "Hue Bulb", dni, newHueBulb?.hub, ["label":newHueBulb?.name])
} }
log.debug "created ${d.displayName} with id $dni"
d.refresh() d.refresh()
} else { } else {
log.debug "found ${d.displayName} with id $dni already exists, type: '$d.typeName'" log.debug "found ${d.displayName} with id $dni already exists, type: '$d.typeName'"