mirror of
https://github.com/mtan93/SmartThingsPublic.git
synced 2026-03-09 13:21:53 +00:00
Compare commits
72 Commits
PROD_2016.
...
MSA-999-5
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5a29c8efd1 | ||
|
|
e5841fb3cb | ||
|
|
805b870447 | ||
|
|
10245315ee | ||
|
|
0b239d4686 | ||
|
|
9374290d64 | ||
|
|
c45129170a | ||
|
|
53406ada8e | ||
|
|
ffd0dd1545 | ||
|
|
5c1236a21a | ||
|
|
0911651f71 | ||
|
|
9cc92b1987 | ||
|
|
1e27dc1824 | ||
|
|
4bf3679942 | ||
|
|
c714720578 | ||
|
|
281fc939ac | ||
|
|
03c2dec425 | ||
|
|
633bef2ac5 | ||
|
|
38d0ca6170 | ||
|
|
836dd608c6 | ||
|
|
43e4db28eb | ||
|
|
df421a51ac | ||
|
|
3affdd21fc | ||
|
|
a8357e7644 | ||
|
|
42865abc55 | ||
|
|
dc1c78391e | ||
|
|
e279172383 | ||
|
|
0deb26810d | ||
|
|
d4fb75cc47 | ||
|
|
2276748a91 | ||
|
|
8d423e7c4b | ||
|
|
0dfbddee38 | ||
|
|
40f88fa436 | ||
|
|
c3c8bafef4 | ||
|
|
1b9f758bd6 | ||
|
|
ef1b04c08a | ||
|
|
9cece36d69 | ||
|
|
be220e02b2 | ||
|
|
131cc7b016 | ||
|
|
dd1e76e95a | ||
|
|
8aff9e78f6 | ||
|
|
6fbef3b297 | ||
|
|
0b5779528c | ||
|
|
63d25528ae | ||
|
|
d8fe639a51 | ||
|
|
92fea16beb | ||
|
|
25ae1306c4 | ||
|
|
321389aee3 | ||
|
|
106f09445b | ||
|
|
6f173981e4 | ||
|
|
a94d8f2378 | ||
|
|
2126d85f6e | ||
|
|
9218b40e25 | ||
|
|
7c326ec7c3 | ||
|
|
74598ec943 | ||
|
|
96a82797de | ||
|
|
c8e4eef66b | ||
|
|
5effd158fc | ||
|
|
3a4b4d6345 | ||
|
|
27808c3996 | ||
|
|
bd0d636b8c | ||
|
|
fbb248dc31 | ||
|
|
a53e506538 | ||
|
|
f593f08c5e | ||
|
|
2d7300d9e5 | ||
|
|
f31c3bf5ee | ||
|
|
0799722bdb | ||
|
|
7fdb99524e | ||
|
|
04941dfa21 | ||
|
|
e61be4ff9c | ||
|
|
6123fbeea5 | ||
|
|
376779598a |
@@ -52,6 +52,9 @@ hipchatShareFile {
|
||||
hipchatSendNotification {
|
||||
String branch = project.hasProperty('branch') ? project.property('branch') : 'unknown'
|
||||
message = "Began executable deploy of SmartThingsPublic(${branch})."
|
||||
if (branch == 'master') {
|
||||
message += ' (dev shards)'
|
||||
}
|
||||
color = branch == 'master' ? 'yellow' : 'red'
|
||||
notify = true
|
||||
}
|
||||
|
||||
@@ -11,6 +11,17 @@
|
||||
* for the specific language governing permissions and limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
/*
|
||||
* Purpose: Arrival Sensor HA DTH File
|
||||
*
|
||||
* Filename: Arrival-Sensor-HA.src/Arrival-Sensor-HA.groovy
|
||||
*
|
||||
* Change History:
|
||||
* 1. 20160115 TW - Update/Edit to support i18n translations
|
||||
* 2. 20160121 TW - Update to V4 battery calcs, added pref's page title translations
|
||||
*/
|
||||
|
||||
metadata {
|
||||
definition (name: "Arrival Sensor HA", namespace: "smartthings", author: "SmartThings") {
|
||||
capability "Tone"
|
||||
@@ -32,7 +43,7 @@ metadata {
|
||||
])
|
||||
}
|
||||
section {
|
||||
input "checkInterval", "enum", title: "Presence timeout (minutes)",
|
||||
input "checkInterval", "enum", title: "Presence timeout (minutes)", description: "Tap to set",
|
||||
defaultValue:"2", options: ["2", "3", "5"], displayDuringSetup: false
|
||||
}
|
||||
}
|
||||
@@ -82,7 +93,6 @@ def parse(String description) {
|
||||
|
||||
private handleReportAttributeMessage(String description) {
|
||||
def descMap = zigbee.parseDescriptionAsMap(description)
|
||||
|
||||
if (descMap.clusterInt == 0x0001 && descMap.attrInt == 0x0020) {
|
||||
handleBatteryEvent(Integer.parseInt(descMap.value, 16))
|
||||
}
|
||||
@@ -94,6 +104,7 @@ private handleReportAttributeMessage(String description) {
|
||||
* @param volts Battery voltage in .1V increments
|
||||
*/
|
||||
private handleBatteryEvent(volts) {
|
||||
def descriptionText
|
||||
if (volts == 0 || volts == 255) {
|
||||
log.debug "Ignoring invalid value for voltage (${volts/10}V)"
|
||||
}
|
||||
@@ -107,15 +118,17 @@ private handleBatteryEvent(volts) {
|
||||
volts = minVolts
|
||||
else if (volts > maxVolts)
|
||||
volts = maxVolts
|
||||
def pct = batteryMap[volts]
|
||||
if (pct != null) {
|
||||
def value = batteryMap[volts]
|
||||
if (value != null) {
|
||||
def linkText = getLinkText(device)
|
||||
descriptionText = '{{ linkText }} battery was {{ value }}'
|
||||
def eventMap = [
|
||||
name: 'battery',
|
||||
value: pct,
|
||||
descriptionText: "${linkText} battery was ${pct}%"
|
||||
value: value,
|
||||
descriptionText: descriptionText,
|
||||
translatable: true
|
||||
]
|
||||
log.debug "Creating battery event for voltage=${volts/10}V: ${eventMap}"
|
||||
log.debug "Creating battery event for voltage=${volts/10}V: ${linkText} ${eventMap.name} is ${eventMap.value}%"
|
||||
sendEvent(eventMap)
|
||||
}
|
||||
}
|
||||
@@ -131,13 +144,19 @@ private handlePresenceEvent(present) {
|
||||
stopTimer()
|
||||
}
|
||||
def linkText = getLinkText(device)
|
||||
def descriptionText
|
||||
if ( present )
|
||||
descriptionText = "{{ linkText }} has arrived"
|
||||
else
|
||||
descriptionText = "{{ linkText }} has left"
|
||||
def eventMap = [
|
||||
name: "presence",
|
||||
value: present ? "present" : "not present",
|
||||
linkText: linkText,
|
||||
descriptionText: "${linkText} has ${present ? 'arrived' : 'left'}",
|
||||
descriptionText: descriptionText,
|
||||
translatable: true
|
||||
]
|
||||
log.debug "Creating presence event: ${eventMap}"
|
||||
log.debug "Creating presence event: ${device.displayName} ${eventMap.name} is ${eventMap.value}"
|
||||
sendEvent(eventMap)
|
||||
}
|
||||
|
||||
@@ -158,4 +177,4 @@ def checkPresenceCallback() {
|
||||
if (timeSinceLastCheckin >= theCheckInterval) {
|
||||
handlePresenceEvent(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
#==============================================================================
|
||||
# Copyright 2016 SmartThings
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
# use this file except in compliance with the License. You may obtain a copy
|
||||
# of the License at:
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
#==============================================================================
|
||||
# Purpose: Arrival Sensor HA i18n Translation File
|
||||
#
|
||||
# Filename: Arrival-Sensor-HA.src/i18n/messages.properties
|
||||
#
|
||||
# Change History:
|
||||
# 1. 20160115 TW Initial release with informal Korean translation.
|
||||
# 2. 20160121 TW Added def preference section titles.
|
||||
#==============================================================================
|
||||
# Korean (ko)
|
||||
# Device Preferences
|
||||
'''Give your device a name'''.ko=기기 이름 바꾸기
|
||||
'''Set Device Image'''.ko=기기 이미지 설정
|
||||
'''Presence timeout (minutes)'''.ko=시간 초과. 스마트폰 위치 정보
|
||||
'''Tap to set'''.ko=눌러서 설정
|
||||
'''Arrival Sensor'''.ko=도착알림 센서
|
||||
# Events / Notifications
|
||||
'''{{ linkText }} battery was {{ value }}'''.ko={{ linkText }}남아있는 배터리는 {{ value }}입니다.
|
||||
'''{{ linkText }} has arrived'''.ko={{ linkText }}집에 도착했습니다.
|
||||
'''{{ linkText }} has left'''.ko={{ linkText }}집을 나갔습니다.
|
||||
#==============================================================================
|
||||
@@ -20,7 +20,6 @@ metadata {
|
||||
capability "Temperature Measurement"
|
||||
capability "Motion Sensor"
|
||||
capability "Refresh"
|
||||
capability "Polling"
|
||||
}
|
||||
|
||||
tiles {
|
||||
@@ -68,6 +67,6 @@ def refresh() {
|
||||
|
||||
void poll() {
|
||||
log.debug "Executing 'poll' using parent SmartApp"
|
||||
parent.pollChild(this)
|
||||
parent.pollChild()
|
||||
|
||||
}
|
||||
|
||||
@@ -20,7 +20,6 @@ metadata {
|
||||
capability "Actuator"
|
||||
capability "Thermostat"
|
||||
capability "Temperature Measurement"
|
||||
capability "Polling"
|
||||
capability "Sensor"
|
||||
capability "Refresh"
|
||||
capability "Relative Humidity Measurement"
|
||||
@@ -134,9 +133,7 @@ def refresh() {
|
||||
|
||||
void poll() {
|
||||
log.debug "Executing 'poll' using parent SmartApp"
|
||||
|
||||
def results = parent.pollChild(this)
|
||||
generateEvent(results) //parse received message from parent
|
||||
parent.pollChild()
|
||||
}
|
||||
|
||||
def generateEvent(Map results) {
|
||||
@@ -382,12 +379,16 @@ def getDataByName(String name) {
|
||||
state[name] ?: device.getDataValue(name)
|
||||
}
|
||||
|
||||
def setThermostatMode(String value) {
|
||||
log.debug "setThermostatMode({$value})"
|
||||
def setThermostatMode(String mode) {
|
||||
log.debug "setThermostatMode($mode)"
|
||||
mode = mode.toLowerCase()
|
||||
switchToMode(mode)
|
||||
}
|
||||
|
||||
def setThermostatFanMode(String value) {
|
||||
log.debug "setThermostatFanMode({$value})"
|
||||
def setThermostatFanMode(String mode) {
|
||||
log.debug "setThermostatFanMode($mode)"
|
||||
mode = mode.toLowerCase()
|
||||
switchToFanMode(mode)
|
||||
}
|
||||
|
||||
def generateModeEvent(mode) {
|
||||
|
||||
@@ -17,16 +17,13 @@ metadata {
|
||||
|
||||
tiles(scale: 2) {
|
||||
multiAttributeTile(name:"rich-control"){
|
||||
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
|
||||
tileAttribute ("", key: "PRIMARY_CONTROL") {
|
||||
attributeState "default", label: "Hue Bridge", action: "", icon: "st.Lighting.light99-hue", backgroundColor: "#F3C200"
|
||||
}
|
||||
}
|
||||
tileAttribute ("serialNumber", key: "SECONDARY_CONTROL") {
|
||||
attributeState "default", label:'SN: ${currentValue}'
|
||||
}
|
||||
}
|
||||
standardTile("icon", "icon", width: 1, height: 1, canChangeIcon: false, inactiveLabel: true, canChangeBackground: false) {
|
||||
state "default", label: "Hue Bridge", action: "", icon: "st.Lighting.light99-hue", backgroundColor: "#FFFFFF"
|
||||
}
|
||||
}
|
||||
}
|
||||
valueTile("serialNumber", "device.serialNumber", decoration: "flat", height: 1, width: 2, inactiveLabel: false) {
|
||||
state "default", label:'SN: ${currentValue}'
|
||||
}
|
||||
@@ -34,7 +31,7 @@ metadata {
|
||||
state "default", label:'${currentValue}', height: 1, width: 2, inactiveLabel: false
|
||||
}
|
||||
|
||||
main (["icon"])
|
||||
main (["rich-control"])
|
||||
details(["rich-control", "networkAddress"])
|
||||
}
|
||||
}
|
||||
@@ -75,6 +72,7 @@ def parse(description) {
|
||||
}
|
||||
else if (contentType?.contains("xml")) {
|
||||
log.debug "HUE BRIDGE ALREADY PRESENT"
|
||||
parent.hubVerification(device.hub.id, msg.body)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
*
|
||||
* Author: SmartThings
|
||||
*/
|
||||
|
||||
// for the UI
|
||||
metadata {
|
||||
// Automatically generated. Make future change here.
|
||||
@@ -27,10 +28,10 @@ metadata {
|
||||
tiles (scale: 2){
|
||||
multiAttributeTile(name:"rich-control", type: "lighting", width: 6, height: 4, canChangeIcon: true){
|
||||
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
|
||||
attributeState "on", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff"
|
||||
attributeState "off", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||
attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff"
|
||||
attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||
attributeState "on", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#00A0DC", nextState:"turningOff"
|
||||
attributeState "off", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#C6C7CC", nextState:"turningOn"
|
||||
attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#00A0DC", nextState:"turningOff"
|
||||
attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#C6C7CC", nextState:"turningOn"
|
||||
}
|
||||
tileAttribute ("device.level", key: "SLIDER_CONTROL") {
|
||||
attributeState "level", action:"switch level.setLevel", range:"(0..100)"
|
||||
@@ -43,16 +44,10 @@ metadata {
|
||||
}
|
||||
}
|
||||
|
||||
standardTile("switch", "device.switch", width: 2, height: 2, canChangeIcon: true) {
|
||||
state "on", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff"
|
||||
state "off", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||
state "turningOn", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff"
|
||||
state "turningOff", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||
}
|
||||
|
||||
controlTile("colorTempSliderControl", "device.colorTemperature", "slider", width: 4, height: 2, inactiveLabel: false, range:"(2000..6500)") {
|
||||
state "colorTemperature", action:"color temperature.setColorTemperature"
|
||||
}
|
||||
|
||||
valueTile("colorTemp", "device.colorTemperature", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
||||
state "colorTemperature", label: '${currentValue} K'
|
||||
}
|
||||
@@ -60,29 +55,12 @@ metadata {
|
||||
standardTile("reset", "device.reset", height: 2, width: 2, inactiveLabel: false, decoration: "flat") {
|
||||
state "default", label:"Reset Color", action:"reset", icon:"st.lights.philips.hue-single"
|
||||
}
|
||||
standardTile("refresh", "device.switch", height: 2, width: 2, inactiveLabel: false, decoration: "flat") {
|
||||
|
||||
standardTile("refresh", "device.refresh", height: 2, width: 2, inactiveLabel: false, decoration: "flat") {
|
||||
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
|
||||
}
|
||||
controlTile("levelSliderControl", "device.level", "slider", height: 1, width: 2, inactiveLabel: false, range:"(0..100)") {
|
||||
state "level", action:"switch level.setLevel"
|
||||
}
|
||||
valueTile("level", "device.level", inactiveLabel: false, decoration: "flat") {
|
||||
state "level", label: 'Level ${currentValue}%'
|
||||
}
|
||||
controlTile("saturationSliderControl", "device.saturation", "slider", height: 1, width: 2, inactiveLabel: false) {
|
||||
state "saturation", action:"color control.setSaturation"
|
||||
}
|
||||
valueTile("saturation", "device.saturation", inactiveLabel: false, decoration: "flat") {
|
||||
state "saturation", label: 'Sat ${currentValue} '
|
||||
}
|
||||
controlTile("hueSliderControl", "device.hue", "slider", height: 1, width: 2, inactiveLabel: false) {
|
||||
state "hue", action:"color control.setHue"
|
||||
}
|
||||
valueTile("hue", "device.hue", inactiveLabel: false, decoration: "flat") {
|
||||
state "hue", label: 'Hue ${currentValue} '
|
||||
}
|
||||
|
||||
main(["switch"])
|
||||
main(["rich-control"])
|
||||
details(["rich-control", "colorTempSliderControl", "colorTemp", "reset", "refresh"])
|
||||
}
|
||||
}
|
||||
@@ -125,62 +103,104 @@ void nextLevel() {
|
||||
}
|
||||
|
||||
void setLevel(percent) {
|
||||
log.debug "Executing 'setLevel'"
|
||||
parent.setLevel(this, percent)
|
||||
sendEvent(name: "level", value: percent)
|
||||
log.debug "Executing 'setLevel'"
|
||||
if (verifyPercent(percent)) {
|
||||
parent.setLevel(this, percent)
|
||||
sendEvent(name: "level", value: percent, descriptionText: "Level has changed to ${percent}%")
|
||||
sendEvent(name: "switch", value: "on")
|
||||
}
|
||||
}
|
||||
|
||||
void setSaturation(percent) {
|
||||
log.debug "Executing 'setSaturation'"
|
||||
parent.setSaturation(this, percent)
|
||||
sendEvent(name: "saturation", value: percent)
|
||||
log.debug "Executing 'setSaturation'"
|
||||
if (verifyPercent(percent)) {
|
||||
parent.setSaturation(this, percent)
|
||||
sendEvent(name: "saturation", value: percent, displayed: false)
|
||||
}
|
||||
}
|
||||
|
||||
void setHue(percent) {
|
||||
log.debug "Executing 'setHue'"
|
||||
parent.setHue(this, percent)
|
||||
sendEvent(name: "hue", value: percent)
|
||||
log.debug "Executing 'setHue'"
|
||||
if (verifyPercent(percent)) {
|
||||
parent.setHue(this, percent)
|
||||
sendEvent(name: "hue", value: percent, displayed: false)
|
||||
}
|
||||
}
|
||||
|
||||
void setColor(value) {
|
||||
log.debug "setColor: ${value}, $this"
|
||||
parent.setColor(this, value)
|
||||
if (value.hue) { sendEvent(name: "hue", value: value.hue)}
|
||||
if (value.saturation) { sendEvent(name: "saturation", value: value.saturation)}
|
||||
if (value.hex) { sendEvent(name: "color", value: value.hex)}
|
||||
if (value.level) { sendEvent(name: "level", value: value.level)}
|
||||
if (value.switch) { sendEvent(name: "switch", value: value.switch)}
|
||||
log.debug "setColor: ${value}, $this"
|
||||
def events = []
|
||||
def validValues = [:]
|
||||
|
||||
if (verifyPercent(value.hue)) {
|
||||
events << createEvent(name: "hue", value: value.hue, displayed: false)
|
||||
validValues.hue = value.hue
|
||||
}
|
||||
if (verifyPercent(value.saturation)) {
|
||||
events << createEvent(name: "saturation", value: value.saturation, displayed: false)
|
||||
validValues.saturation = value.saturation
|
||||
}
|
||||
if (value.hex != null) {
|
||||
if (value.hex ==~ /^\#([A-Fa-f0-9]){6}$/) {
|
||||
events << createEvent(name: "color", value: value.hex)
|
||||
validValues.hex = value.hex
|
||||
} else {
|
||||
log.warn "$value.hex is not a valid color"
|
||||
}
|
||||
}
|
||||
if (verifyPercent(value.level)) {
|
||||
events << createEvent(name: "level", value: value.level, descriptionText: "Level has changed to ${value.level}%")
|
||||
validValues.level = value.level
|
||||
}
|
||||
if (value.switch == "off" || (value.level != null && value.level <= 0)) {
|
||||
events << createEvent(name: "switch", value: "off")
|
||||
validValues.switch = "off"
|
||||
} else {
|
||||
events << createEvent(name: "switch", value: "on")
|
||||
validValues.switch = "on"
|
||||
}
|
||||
if (!events.isEmpty()) {
|
||||
parent.setColor(this, validValues)
|
||||
}
|
||||
events.each {
|
||||
sendEvent(it)
|
||||
}
|
||||
}
|
||||
|
||||
void reset() {
|
||||
log.debug "Executing 'reset'"
|
||||
def value = [level:100, hex:"#90C638", saturation:56, hue:23]
|
||||
log.debug "Executing 'reset'"
|
||||
def value = [level:100, saturation:56, hue:23]
|
||||
setAdjustedColor(value)
|
||||
parent.poll()
|
||||
parent.poll()
|
||||
}
|
||||
|
||||
void setAdjustedColor(value) {
|
||||
if (value) {
|
||||
if (value) {
|
||||
log.trace "setAdjustedColor: ${value}"
|
||||
def adjusted = value + [:]
|
||||
adjusted.hue = adjustOutgoingHue(value.hue)
|
||||
// Needed because color picker always sends 100
|
||||
adjusted.level = null
|
||||
setColor(adjusted)
|
||||
} else {
|
||||
log.warn "Invalid color input"
|
||||
}
|
||||
}
|
||||
|
||||
void setColorTemperature(value) {
|
||||
if (value) {
|
||||
if (value) {
|
||||
log.trace "setColorTemperature: ${value}k"
|
||||
parent.setColorTemperature(this, value)
|
||||
sendEvent(name: "colorTemperature", value: value)
|
||||
}
|
||||
sendEvent(name: "switch", value: "on")
|
||||
} else {
|
||||
log.warn "Invalid color temperature"
|
||||
}
|
||||
}
|
||||
|
||||
void refresh() {
|
||||
log.debug "Executing 'refresh'"
|
||||
parent.manualRefresh()
|
||||
log.debug "Executing 'refresh'"
|
||||
parent.manualRefresh()
|
||||
}
|
||||
|
||||
def adjustOutgoingHue(percent) {
|
||||
@@ -199,3 +219,14 @@ def adjustOutgoingHue(percent) {
|
||||
log.info "percent: $percent, adjusted: $adjusted"
|
||||
adjusted
|
||||
}
|
||||
|
||||
def verifyPercent(percent) {
|
||||
if (percent == null)
|
||||
return false
|
||||
else if (percent >= 0 && percent <= 100) {
|
||||
return true
|
||||
} else {
|
||||
log.warn "$percent is not 0-100"
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -36,13 +36,6 @@ metadata {
|
||||
}
|
||||
}
|
||||
|
||||
standardTile("switch", "device.switch", width: 2, height: 2, canChangeIcon: true) {
|
||||
state "on", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff"
|
||||
state "off", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||
state "turningOn", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff"
|
||||
state "turningOff", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||
}
|
||||
|
||||
controlTile("levelSliderControl", "device.level", "slider", height: 1, width: 2, inactiveLabel: false, range:"(0..100)") {
|
||||
state "level", action:"switch level.setLevel"
|
||||
}
|
||||
@@ -51,7 +44,7 @@ metadata {
|
||||
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
|
||||
}
|
||||
|
||||
main(["switch"])
|
||||
main(["rich-control"])
|
||||
details(["rich-control", "refresh"])
|
||||
}
|
||||
}
|
||||
@@ -86,8 +79,12 @@ void off() {
|
||||
|
||||
void setLevel(percent) {
|
||||
log.debug "Executing 'setLevel'"
|
||||
parent.setLevel(this, percent)
|
||||
sendEvent(name: "level", value: percent)
|
||||
if (percent != null && percent >= 0 && percent <= 100) {
|
||||
parent.setLevel(this, percent)
|
||||
sendEvent(name: "level", value: percent)
|
||||
} else {
|
||||
log.warn "$percent is not 0-100"
|
||||
}
|
||||
}
|
||||
|
||||
void refresh() {
|
||||
|
||||
@@ -19,13 +19,14 @@
|
||||
#
|
||||
# Change History:
|
||||
# 1. 20160205 TW Initial release with informal Korean translation.
|
||||
# 2. 20160224 TW Updated with formal Korean translation.
|
||||
#==============================================================================
|
||||
# Korean (ko)
|
||||
# Device Preferences
|
||||
'''Give your device a name'''.ko=기기 이름 바꾸기
|
||||
'''Set Device Image'''.ko=디바이스 이미지 설정
|
||||
'''Set Device Image'''.ko=기기 이미지 설정
|
||||
# Events / Notifications
|
||||
'''{{ linkText }} has left'''.ko={{ linkText }}님이 나갔습니다
|
||||
'''{{ linkText }} has arrived'''.ko={{ linkText }}님이 도착했습니다
|
||||
'''{{ linkText }} has left'''.ko={{ linkText }}집을 나갔습니다.
|
||||
'''{{ linkText }} has arrived'''.ko={{ linkText }}집에 도착했습니다.
|
||||
'''present'''.ko=집안
|
||||
'''not present'''.ko=부재중
|
||||
'''not present'''.ko=외출
|
||||
|
||||
@@ -1,16 +1,28 @@
|
||||
/**
|
||||
* 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:
|
||||
* 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.
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
===============================================================================
|
||||
* Purpose: Mobile Presence DTH File
|
||||
*
|
||||
* Filename: mobile-presence.src/mobile-presence.groovy
|
||||
*
|
||||
* Change History:
|
||||
* 1. 20160205 TW - Update/Edit to support i18n translations
|
||||
===============================================================================
|
||||
*/
|
||||
|
||||
metadata {
|
||||
definition (name: "Mobile Presence", namespace: "smartthings", author: "SmartThings") {
|
||||
capability "Presence Sensor"
|
||||
@@ -41,6 +53,7 @@ def parse(String description) {
|
||||
def isStateChange = isStateChange(device, name, value)
|
||||
|
||||
def results = [
|
||||
translatable: true,
|
||||
name: name,
|
||||
value: value,
|
||||
unit: null,
|
||||
@@ -72,8 +85,8 @@ private String parseValue(String description) {
|
||||
|
||||
private parseDescriptionText(String linkText, String value, String description) {
|
||||
switch(value) {
|
||||
case "present": return "$linkText has arrived"
|
||||
case "not present": return "$linkText has left"
|
||||
case "present": return "{{ linkText }} has arrived"
|
||||
case "not present": return "{{ linkText }} has left"
|
||||
default: return value
|
||||
}
|
||||
}
|
||||
@@ -84,4 +97,4 @@ private getState(String value) {
|
||||
case "not present": return "left"
|
||||
default: return value
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
#==============================================================================
|
||||
# Copyright 2016 SmartThings
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
# use this file except in compliance with the License. You may obtain a copy
|
||||
# of the License at:
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
#==============================================================================
|
||||
# Purpose: SmartPower Outlet i18n Translation File
|
||||
#
|
||||
# Filename: SmartPower-Outlet.src/i18n/messages.properties
|
||||
#
|
||||
# Change History:
|
||||
# 1. 20160116 TW Initial release with informal Korean translation.
|
||||
#==============================================================================
|
||||
# Korean (ko)
|
||||
# Device Preferences
|
||||
'''Give your device a name'''.ko=기기 이름 바꾸기
|
||||
'''Outlet'''.ko=플러그
|
||||
# Events descriptionText
|
||||
'''{{ device.displayName }} is On'''.ko={{ device.displayName }}켜졌습니다.
|
||||
'''{{ device.displayName }} is Off'''.ko={{ device.displayName }}꺼졌습니다.
|
||||
'''{{ device.displayName }} power is {{ value }} Watts'''.ko={{ device.displayName }} 전원은 {{ value }}와트입니다
|
||||
#==============================================================================
|
||||
@@ -1,19 +1,26 @@
|
||||
/**
|
||||
* 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:
|
||||
* 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.
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
===============================================================================
|
||||
* Purpose: SmartPower Outlet DTH File
|
||||
*
|
||||
* SmartPower Outlet (CentraLite)
|
||||
* Filename: SmartPower-Outlet.src/SmartPower-Outlet.groovy
|
||||
*
|
||||
* Author: SmartThings
|
||||
* Date: 2015-08-23
|
||||
* Change History:
|
||||
* 1. 20160117 TW - Update/Edit to support i18n translations
|
||||
===============================================================================
|
||||
*/
|
||||
metadata {
|
||||
// Automatically generated. Make future change here.
|
||||
@@ -91,22 +98,22 @@ def parse(String description) {
|
||||
finalResult = getPowerDescription(zigbee.parseDescriptionAsMap(description))
|
||||
|
||||
if (finalResult) {
|
||||
log.info finalResult
|
||||
log.info "final result = $finalResult"
|
||||
if (finalResult.type == "update") {
|
||||
log.info "$device updates: ${finalResult.value}"
|
||||
}
|
||||
else if (finalResult.type == "power") {
|
||||
def powerValue = (finalResult.value as Integer)/10
|
||||
sendEvent(name: "power", value: powerValue)
|
||||
sendEvent(name: "power", value: powerValue, descriptionText: '{{ device.displayName }} power is {{ value }} Watts', translatable: true )
|
||||
/*
|
||||
Dividing by 10 as the Divisor is 10000 and unit is kW for the device. AttrId: 0302 and 0300. Simplifying to 10
|
||||
|
||||
power level is an integer. The exact power level with correct units needs to be handled in the device type
|
||||
to account for the different Divisor value (AttrId: 0302) and POWER Unit (AttrId: 0300). CLUSTER for simple metering is 0702
|
||||
*/
|
||||
}
|
||||
else {
|
||||
sendEvent(name: finalResult.type, value: finalResult.value)
|
||||
def descriptionText = finalResult.value == "on" ? '{{ device.displayName }} is On' : '{{ device.displayName }} is Off'
|
||||
sendEvent(name: finalResult.type, value: finalResult.value, descriptionText: descriptionText, translatable: true)
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
||||
@@ -1,14 +1,41 @@
|
||||
|
||||
# Generated on Wed Feb 24 14:28:26 CST 2016 by dylan
|
||||
'''Adjust temperature by this many degrees'''.ko=몇 도씩 온도를 조절하십시오
|
||||
'''Degrees'''.ko=온도
|
||||
'''Temperature Offset'''.ko=온도 직접 설정
|
||||
'''This feature allows you to correct any temperature variations by selecting an offset. Ex: If your sensor consistently reports a temp that's 5 degrees too warm, you'd enter '-5'. If 3 degrees too cold, enter '+3'.'''.ko=기준 온도를 원하는대로 몇 도 올리거나 내려서 설정할 수 있습니다.
|
||||
'''battery'''.ko=배터리
|
||||
#==============================================================================
|
||||
# Copyright 2016 SmartThings
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
# use this file except in compliance with the License. You may obtain a copy
|
||||
# of the License at:
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
#==============================================================================
|
||||
# Purpose: SmartSense Moisture Sensor i18n Translation File
|
||||
#
|
||||
# Filename: SmartSense-Moisture-Sensor.src/i18n/messages.properties
|
||||
#
|
||||
# Change History:
|
||||
# 1. 20160116 TW Initial release with formal Korean translation.
|
||||
#==============================================================================
|
||||
# Korean (ko)
|
||||
# Device Preferences
|
||||
'''dry'''.ko=건조
|
||||
'''wet'''.ko=누수
|
||||
'''{{ device.displayName }} battery has too much power: (> 3.5) volts.'''.ko={{ device.displayName }} 배터리 전력이 너무 높습니다(3.5볼트 초과).
|
||||
'''{{ device.displayName }} battery was {{ value }}'''.ko={{ device.displayName }} 배터리가 {{ value }}였습니다
|
||||
'''{{ device.displayName }} is {{ value | translate }}'''.ko={{ device.displayName }}이(가) {{ value | translate }}입니다
|
||||
'''battery'''.ko=배터리
|
||||
'''Temperature Offset'''.ko=온도 직접 설정
|
||||
'''This feature allows you to correct any temperature variations by selecting an offset. Ex: If your sensor consistently reports a temp that's 5 degrees too warm, you'd enter '-5'. If 3 degrees too cold, enter '+3'.'''.ko=기준 온도를 원하는대로 몇 도 올리거나 내려서 설정할 수 있습니다.
|
||||
'''Degrees'''.ko=온도
|
||||
'''Adjust temperature by this many degrees'''.ko=몇 도씩 온도를 조절하십시오
|
||||
'''Give your device a name'''.ko=기기 이름 바꾸기
|
||||
'''Water Leak Sensor'''.ko=누수센서
|
||||
# Events descriptionText
|
||||
'''{{ device.displayName }} is dry'''.ko={{ device.displayName }}가 건조
|
||||
'''{{ device.displayName }} is wet'''.ko={{ device.displayName }}누수
|
||||
'''{{ device.displayName }} was {{ value }}°C'''.ko={{ device.displayName }}이(가) {{ value }}°C였습니다
|
||||
'''{{ device.displayName }} was {{ value }}°F'''.ko={{ device.displayName }}이(가) {{ value }}°F였습니다
|
||||
'''{{ device.displayName }} battery has too much power: (> 3.5) volts.'''.ko={{ device.displayName }} 배터리 전력이 너무 높습니다(3.5볼트 초과).
|
||||
'''{{ device.displayName }} battery was {{ value }}%'''.ko={{ device.displayName }}남아있는 배터리는 {{ value }}%입니다.
|
||||
#==============================================================================
|
||||
|
||||
@@ -1,18 +1,29 @@
|
||||
/**
|
||||
* SmartSense Moisture Sensor
|
||||
/*
|
||||
===============================================================================
|
||||
* Copyright 2016 SmartThings
|
||||
*
|
||||
* Copyright 2014 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:
|
||||
* 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.
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
===============================================================================
|
||||
* Purpose: SmartSense Moisture Sensor DTH File
|
||||
*
|
||||
* Filename: SmartSense-Moisture-Sensor.src/SmartSense-Moisture-Sensor.groovy
|
||||
*
|
||||
* Change History:
|
||||
* 1. 20160116 TW - Update/Edit to support i18n translations
|
||||
* 2. 20160125 TW = Incorporated new battery mapping from TM
|
||||
===============================================================================
|
||||
*/
|
||||
|
||||
metadata {
|
||||
definition (name: "SmartSense Moisture Sensor",namespace: "smartthings", author: "SmartThings") {
|
||||
capability "Configuration"
|
||||
@@ -43,7 +54,7 @@ metadata {
|
||||
])
|
||||
}
|
||||
section {
|
||||
input title: "Temperature Offset", description: "This feature allows you to correct any temperature variations by selecting an offset. Ex: If your sensor consistently reports a temp that's 5 degrees too warm, you'd enter \"-5\". If 3 degrees too cold, enter \"+3\".", displayDuringSetup: false, type: "paragraph", element: "paragraph"
|
||||
input title: "Temperature Offset", description: "This feature allows you to correct any temperature variations by selecting an offset. Ex: If your sensor consistently reports a temp that's 5 degrees too warm, you'd enter '-5'. If 3 degrees too cold, enter '+3'.", displayDuringSetup: false, type: "paragraph", element: "paragraph"
|
||||
input "tempOffset", "number", title: "Degrees", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false
|
||||
}
|
||||
}
|
||||
@@ -116,16 +127,16 @@ private Map parseCatchAllMessage(String description) {
|
||||
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) {
|
||||
@@ -220,7 +231,8 @@ private Map getBatteryResult(rawValue) {
|
||||
|
||||
def result = [
|
||||
name: 'battery',
|
||||
value: '--'
|
||||
value: '--',
|
||||
translatable: true
|
||||
]
|
||||
|
||||
def volts = rawValue / 10
|
||||
@@ -228,7 +240,7 @@ private Map getBatteryResult(rawValue) {
|
||||
if (rawValue == 0 || rawValue == 255) {}
|
||||
else {
|
||||
if (volts > 3.5) {
|
||||
result.descriptionText = "${linkText} battery has too much power (${volts} volts)."
|
||||
result.descriptionText = "{{ device.displayName }} battery has too much power: (> 3.5) volts."
|
||||
}
|
||||
else {
|
||||
if (device.getDataValue("manufacturer") == "SmartThings") {
|
||||
@@ -245,7 +257,7 @@ private Map getBatteryResult(rawValue) {
|
||||
def pct = batteryMap[volts]
|
||||
if (pct != null) {
|
||||
result.value = pct
|
||||
result.descriptionText = "${linkText} battery was ${result.value}%"
|
||||
result.descriptionText = "{{ device.displayName }} battery was {{ value }}%"
|
||||
}
|
||||
}
|
||||
else {
|
||||
@@ -253,7 +265,7 @@ private Map getBatteryResult(rawValue) {
|
||||
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}%"
|
||||
result.descriptionText = "{{ device.displayName }} battery was {{ value }}%"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -263,27 +275,37 @@ private Map getBatteryResult(rawValue) {
|
||||
|
||||
private Map getTemperatureResult(value) {
|
||||
log.debug 'TEMP'
|
||||
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}"
|
||||
def descriptionText
|
||||
if ( temperatureScale == 'C' )
|
||||
descriptionText = '{{ device.displayName }} was {{ value }}°C'
|
||||
else
|
||||
descriptionText = '{{ device.displayName }} was {{ value }}°F'
|
||||
|
||||
return [
|
||||
name: 'temperature',
|
||||
value: value,
|
||||
descriptionText: descriptionText
|
||||
descriptionText: descriptionText,
|
||||
translatable: true
|
||||
]
|
||||
}
|
||||
|
||||
private Map getMoistureResult(value) {
|
||||
log.debug 'water'
|
||||
String descriptionText = "${device.displayName} is ${value}"
|
||||
log.debug "water"
|
||||
def descriptionText
|
||||
if ( value == "wet" )
|
||||
descriptionText = '{{ device.displayName }} is wet'
|
||||
else
|
||||
descriptionText = '{{ device.displayName }} is dry'
|
||||
return [
|
||||
name: 'water',
|
||||
value: value,
|
||||
descriptionText: descriptionText
|
||||
descriptionText: descriptionText,
|
||||
translatable: true
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
@@ -1,13 +1,40 @@
|
||||
|
||||
# Generated on Wed Feb 24 14:28:26 CST 2016 by dylan
|
||||
'''Adjust temperature by this many degrees'''.ko=몇 도씩 온도를 조절하십시오
|
||||
'''Degrees'''.ko=온도
|
||||
'''Temperature Offset'''.ko=온도 직접 설정
|
||||
'''This feature allows you to correct any temperature variations by selecting an offset. Ex: If your sensor consistently reports a temp that's 5 degrees too warm, you'd enter '-5'. If 3 degrees too cold, enter '+3'.'''.ko=기준 온도를 원하는대로 몇 도 올리거나 내려서 설정할 수 있습니다.
|
||||
#==============================================================================
|
||||
# Copyright 2016 SmartThings
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
# use this file except in compliance with the License. You may obtain a copy
|
||||
# of the License at:
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
#==============================================================================
|
||||
# Purpose: SmartSense Motion Sensor i18n Translation File
|
||||
#
|
||||
# Filename: SmartSense-Motion-Sensor.src/i18n/messages.properties
|
||||
#
|
||||
# Change History:
|
||||
# 1. 20160116 TW Initial release with formal Korean translation.
|
||||
# 2. 20160224 TW Updated formal Korean translations from Mike Stoller.
|
||||
#==============================================================================
|
||||
# Korean (ko)
|
||||
# Device Preferences
|
||||
'''battery'''.ko=배터리
|
||||
'''{{ device.displayName }} battery has too much power: (> 3.5) volts.'''.ko={{ device.displayName }} 배터리 전력이 너무 높습니다(3.5볼트 초과).
|
||||
'''{{ device.displayName }} battery was {{ value }}'''.ko={{ device.displayName }} 배터리가 {{ value }}였습니다
|
||||
'''{{ device.displayName }} detected motion'''.ko={{ device.displayName }}가 움직임을 감지하였습니다.
|
||||
'''{{ device.displayName }} motion has stopped'''.ko={{ device.displayName }} 동작이 중단되었습니다
|
||||
'''{{ device.displayName }} was {{ value }}°C'''.ko={{ device.displayName }}이(가) {{ value }}°C였습니다
|
||||
'''Temperature Offset'''.ko=온도 직접 설정
|
||||
'''This feature allows you to correct any temperature variations by selecting an offset. Ex: If your sensor consistently reports a temp that's 5 degrees too warm, you'd enter '-5'. If 3 degrees too cold, enter '+3'.'''.ko=기준 온도를 원하는대로 몇 도 올리거나 내려서 설정할 수 있습니다.
|
||||
'''Degrees'''.ko=온도
|
||||
'''Adjust temperature by this many degrees'''.ko=몇 도씩 온도를 조절하십시오
|
||||
'''Give your device a name'''.ko=기기 이름 바꾸기
|
||||
'''Motion Sensor'''.ko=모션 센서
|
||||
# Events descriptionText
|
||||
'''{{ device.displayName }} detected motion'''.ko={{ device.displayName }} 가 움직임을 감지하였습니다.
|
||||
'''{{ device.displayName }} motion has stopped'''.ko={{ device.displayName }}움직임이 중단되었습니다
|
||||
'''{{ device.displayName }} was {{ value }}°C'''.ko={{ device.displayName }}이(가){{ value }}°C였습니다.
|
||||
'''{{ device.displayName }} was {{ value }}°F'''.ko={{ device.displayName }}이(가) {{ value }}°F였습니다
|
||||
'''{{ device.displayName }} battery has too much power: (> 3.5) volts.'''.ko={{ device.displayName }} 배터리 전력이 너무 높습니다(3.5볼트 초과).
|
||||
'''{{ device.displayName }} battery was {{ value }}%'''.ko={{ device.displayName }}남아있는 배터리는 {{ value }}%입니다.
|
||||
#==============================================================================
|
||||
|
||||
@@ -1,17 +1,27 @@
|
||||
/**
|
||||
* SmartSense Motion/Temp Sensor
|
||||
/*
|
||||
===============================================================================
|
||||
* Copyright 2016 SmartThings
|
||||
*
|
||||
* Copyright 2014 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:
|
||||
* 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.
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
===============================================================================
|
||||
* Purpose: SmartSense Motion Sensor DTH File
|
||||
*
|
||||
* Filename: SmartSense-Motion-Sensor.src/SmartSense-Motion-Sensor.groovy
|
||||
*
|
||||
* Change History:
|
||||
* 1. 20160116 TW - Update/Edit to support i18n translations
|
||||
* 2. 20160125 TW = Incorporated new battery mapping from TM
|
||||
===============================================================================
|
||||
*/
|
||||
|
||||
metadata {
|
||||
@@ -47,7 +57,7 @@ metadata {
|
||||
])
|
||||
}
|
||||
section {
|
||||
input title: "Temperature Offset", description: "This feature allows you to correct any temperature variations by selecting an offset. Ex: If your sensor consistently reports a temp that's 5 degrees too warm, you'd enter \"-5\". If 3 degrees too cold, enter \"+3\".", displayDuringSetup: false, type: "paragraph", element: "paragraph"
|
||||
input title: "Temperature Offset", description: "This feature allows you to correct any temperature variations by selecting an offset. Ex: If your sensor consistently reports a temp that's 5 degrees too warm, you'd enter '-5'. If 3 degrees too cold, enter '+3'.", displayDuringSetup: false, type: "paragraph", element: "paragraph"
|
||||
input "tempOffset", "number", title: "Degrees", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false
|
||||
}
|
||||
}
|
||||
@@ -236,7 +246,8 @@ private Map getBatteryResult(rawValue) {
|
||||
|
||||
def result = [
|
||||
name: 'battery',
|
||||
value: '--'
|
||||
value: '--',
|
||||
translatable: true
|
||||
]
|
||||
|
||||
def volts = rawValue / 10
|
||||
@@ -244,7 +255,7 @@ private Map getBatteryResult(rawValue) {
|
||||
if (rawValue == 0 || rawValue == 255) {}
|
||||
else {
|
||||
if (volts > 3.5) {
|
||||
result.descriptionText = "${linkText} battery has too much power (${volts} volts)."
|
||||
result.descriptionText = "{{ device.displayName }} battery has too much power: (> 3.5) volts."
|
||||
}
|
||||
else {
|
||||
if (device.getDataValue("manufacturer") == "SmartThings") {
|
||||
@@ -261,7 +272,8 @@ private Map getBatteryResult(rawValue) {
|
||||
def pct = batteryMap[volts]
|
||||
if (pct != null) {
|
||||
result.value = pct
|
||||
result.descriptionText = "${linkText} battery was ${result.value}%"
|
||||
def value = pct
|
||||
result.descriptionText = "{{ device.displayName }} battery was {{ value }}%"
|
||||
}
|
||||
}
|
||||
else {
|
||||
@@ -269,7 +281,7 @@ private Map getBatteryResult(rawValue) {
|
||||
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}%"
|
||||
result.descriptionText = "{{ device.displayName }} battery was {{ value }}%"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -279,28 +291,33 @@ private Map getBatteryResult(rawValue) {
|
||||
|
||||
private Map getTemperatureResult(value) {
|
||||
log.debug 'TEMP'
|
||||
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}"
|
||||
def descriptionText
|
||||
if ( temperatureScale == 'C' )
|
||||
descriptionText = '{{ device.displayName }} was {{ value }}°C'
|
||||
else
|
||||
descriptionText = '{{ device.displayName }} was {{ value }}°F'
|
||||
|
||||
return [
|
||||
name: 'temperature',
|
||||
value: value,
|
||||
descriptionText: descriptionText
|
||||
descriptionText: descriptionText,
|
||||
translatable: true
|
||||
]
|
||||
}
|
||||
|
||||
private Map getMotionResult(value) {
|
||||
log.debug 'motion'
|
||||
String linkText = getLinkText(device)
|
||||
String descriptionText = value == 'active' ? "${linkText} detected motion" : "${linkText} motion has stopped"
|
||||
String descriptionText = value == 'active' ? "{{ device.displayName }} detected motion" : "{{ device.displayName }} motion has stopped"
|
||||
return [
|
||||
name: 'motion',
|
||||
value: value,
|
||||
descriptionText: descriptionText
|
||||
descriptionText: descriptionText,
|
||||
translatable: true
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
@@ -1,20 +1,45 @@
|
||||
|
||||
# Generated on Wed Feb 24 14:28:26 CST 2016 by dylan
|
||||
'''Adjust temperature by this many degrees'''.ko=몇 도씩 온도를 조절하십시오
|
||||
'''Degrees'''.ko=온도
|
||||
'''Do you want to use this sensor on a garage door?'''.ko=차고 문의 센서 사용 설정하기
|
||||
#==============================================================================
|
||||
# Copyright 2016 SmartThings
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
# use this file except in compliance with the License. You may obtain a copy
|
||||
# of the License at:
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
#==============================================================================
|
||||
# Purpose: SmartSense Multi Sensor i18n Translation File
|
||||
#
|
||||
# Filename: SmartSense-Multi-Sensor.src/i18n/messages.properties
|
||||
#
|
||||
# Change History:
|
||||
# 1. 20160117 TW Initial release with informal Korean translation.
|
||||
#==============================================================================
|
||||
# Korean (ko)
|
||||
# Device Preferences
|
||||
'''Yes'''.ko=예
|
||||
'''No'''.ko=아니요
|
||||
'''Tap to set'''.ko=눌러서 설정
|
||||
'''battery'''.ko=배터리
|
||||
'''Temperature Offset'''.ko=온도 직접 설정
|
||||
'''This feature allows you to correct any temperature variations by selecting an offset. Ex: If your sensor consistently reports a temp that's 5 degrees too warm, you'd enter '-5'. If 3 degrees too cold, enter '+3'.'''.ko=기준 온도를 원하는대로 몇 도 올리거나 내려서 설정할 수 있습니다.
|
||||
'''Degrees'''.ko=온도
|
||||
'''Adjust temperature by this many degrees'''.ko=몇 도씩 온도를 조절하십시오
|
||||
'''Do you want to use this sensor on a garage door?'''.ko=차고 문의 센서 사용 설정하기
|
||||
'''Tap to set'''.ko=눌러서 설정
|
||||
'''Give your device a name'''.ko=기기 이름 바꾸기
|
||||
'''Multipurpose Sensor'''.ko=멀티 센서
|
||||
# Events descriptionText
|
||||
'''{{ device.displayName }} was opened'''.ko={{ device.displayName }}열림을 감지하였습니다.
|
||||
'''{{ device.displayName }} was closed'''.ko={{ device.displayName }}닫혔습니다.
|
||||
'''{{ device.displayName }} was active'''.ko={{ device.displayName }}활성화되었습니다.
|
||||
'''{{ device.displayName }} was inactive'''.ko={{ device.displayName }}비활성화되었습니다.
|
||||
'''{{ device.displayName }} was {{ value }}°C'''.ko={{ device.displayName }}이(가){{ value }}°C였습니다.
|
||||
'''{{ device.displayName }} was {{ value }}°F'''.ko={{ device.displayName }}이(가) {{ value }}°F였습니다
|
||||
'''{{ device.displayName }} battery was {{ value }}%'''.ko={{ device.displayName }}남아있는 배터리는 {{ value }}%입니다.
|
||||
'''Updating device to garage sensor'''.ko=기기-차고 센서 업데이트 중
|
||||
'''Updating device to open/close sensor'''.ko=기기-열림/닫힘 센서 업데이트 중
|
||||
'''Yes'''.ko=예
|
||||
'''{{ device.displayName }} status was closed'''.ko={{ device.displayName }}은(는) 닫힌 상태입니다
|
||||
'''{{ device.displayName }} status was opened'''.ko={{ device.displayName }}은(는) 열린 상태입니다
|
||||
'''{{ device.displayName }} was active'''.ko={{ device.displayName }}이(가) 활성화되었습니다
|
||||
'''{{ device.displayName }} was closed'''.ko={{ device.displayName }}이(가) 닫혔습니다
|
||||
'''{{ device.displayName }} was inactive'''.ko={{ device.displayName }}이(가) 비활성화되었습니다
|
||||
'''{{ device.displayName }} was opened'''.ko={{ device.displayName }}이(가) 열렸습니다
|
||||
'''{{ device.displayName }} was {{ value }}°C'''.ko={{ device.displayName }}이(가) {{ value }}°C였습니다
|
||||
'''{{ device.displayName }} was {{ value }}°F'''.ko={{ device.displayName }}이(가) {{ value }}°F였습니다
|
||||
|
||||
@@ -1,17 +1,27 @@
|
||||
/**
|
||||
* SmartSense Multi
|
||||
/*
|
||||
===============================================================================
|
||||
* Copyright 2016 SmartThings
|
||||
*
|
||||
* 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:
|
||||
* 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.
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
===============================================================================
|
||||
* Purpose: SmartSense Multi Sensor DTH File
|
||||
*
|
||||
* Filename: SmartSense-Multi-Sensor.src/SmartSense-Multi-Sensor.groovy
|
||||
*
|
||||
* Change History:
|
||||
* 1. 20160117 TW - Update/Edit to support i18n translations
|
||||
* 2. 20160125 TW = Incorporated new battery mapping from TM
|
||||
===============================================================================
|
||||
*/
|
||||
|
||||
metadata {
|
||||
@@ -62,11 +72,11 @@ metadata {
|
||||
])
|
||||
}
|
||||
section {
|
||||
input title: "Temperature Offset", description: "This feature allows you to correct any temperature variations by selecting an offset. Ex: If your sensor consistently reports a temp that's 5 degrees too warm, you'd enter \"-5\". If 3 degrees too cold, enter \"+3\".", displayDuringSetup: false, type: "paragraph", element: "paragraph"
|
||||
input title: "Temperature Offset", description: "This feature allows you to correct any temperature variations by selecting an offset. Ex: If your sensor consistently reports a temp that's 5 degrees too warm, you'd enter '-5'. If 3 degrees too cold, enter '+3'.", displayDuringSetup: false, type: "paragraph", element: "paragraph"
|
||||
input "tempOffset", "number", title: "Degrees", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false
|
||||
}
|
||||
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("garageSensor", "enum", title: "Do you want to use this sensor on a garage door?", description: "Tap to set", options: ["Yes", "No"], defaultValue: "No", required: false, displayDuringSetup: false)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -273,19 +283,19 @@ def updated() {
|
||||
if (garageSensor == "Yes") {
|
||||
def descriptionText = "Updating device to garage sensor"
|
||||
if (device.latestValue("status") == "open") {
|
||||
sendEvent(name: 'status', value: 'garage-open', descriptionText: descriptionText)
|
||||
sendEvent(name: 'status', value: 'garage-open', descriptionText: descriptionText, translatable: true)
|
||||
}
|
||||
else if (device.latestValue("status") == "closed") {
|
||||
sendEvent(name: 'status', value: 'garage-closed', descriptionText: descriptionText)
|
||||
sendEvent(name: 'status', value: 'garage-closed', descriptionText: descriptionText, translatable: true)
|
||||
}
|
||||
}
|
||||
else {
|
||||
def descriptionText = "Updating device to open/close sensor"
|
||||
if (device.latestValue("status") == "garage-open") {
|
||||
sendEvent(name: 'status', value: 'open', descriptionText: descriptionText)
|
||||
sendEvent(name: 'status', value: 'open', descriptionText: descriptionText, translatable: true)
|
||||
}
|
||||
else if (device.latestValue("status") == "garage-closed") {
|
||||
sendEvent(name: 'status', value: 'closed', descriptionText: descriptionText)
|
||||
sendEvent(name: 'status', value: 'closed', descriptionText: descriptionText, translatable: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -301,11 +311,11 @@ def getTemperature(value) {
|
||||
|
||||
private Map getBatteryResult(rawValue) {
|
||||
log.debug "Battery rawValue = ${rawValue}"
|
||||
def linkText = getLinkText(device)
|
||||
|
||||
def result = [
|
||||
name: 'battery',
|
||||
value: '--'
|
||||
value: '--',
|
||||
translatable: true
|
||||
]
|
||||
|
||||
def volts = rawValue / 10
|
||||
@@ -313,7 +323,7 @@ private Map getBatteryResult(rawValue) {
|
||||
if (rawValue == 0 || rawValue == 255) {}
|
||||
else {
|
||||
if (volts > 3.5) {
|
||||
result.descriptionText = "${linkText} battery has too much power (${volts} volts)."
|
||||
result.descriptionText = "{{ device.displayName }} battery has too much power: (> 3.5) volts."
|
||||
}
|
||||
else {
|
||||
if (device.getDataValue("manufacturer") == "SmartThings") {
|
||||
@@ -330,7 +340,7 @@ private Map getBatteryResult(rawValue) {
|
||||
def pct = batteryMap[volts]
|
||||
if (pct != null) {
|
||||
result.value = pct
|
||||
result.descriptionText = "${linkText} battery was ${result.value}%"
|
||||
result.descriptionText = "{{ device.displayName }} battery was {{ value }}%"
|
||||
}
|
||||
}
|
||||
else {
|
||||
@@ -338,7 +348,7 @@ private Map getBatteryResult(rawValue) {
|
||||
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}%"
|
||||
result.descriptionText = "{{ device.displayName }} battery was {{ value }}%"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -348,40 +358,50 @@ private Map getBatteryResult(rawValue) {
|
||||
|
||||
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}"
|
||||
def descriptionText = temperatureScale == 'C' ? '{{ device.displayName }} was {{ value }}°C':
|
||||
'{{ device.displayName }} was {{ value }}°F'
|
||||
|
||||
return [
|
||||
name: 'temperature',
|
||||
value: value,
|
||||
descriptionText: descriptionText
|
||||
descriptionText: descriptionText,
|
||||
translatable: true
|
||||
]
|
||||
}
|
||||
|
||||
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)
|
||||
log.debug "Contact: ${device.displayName} value = ${value}"
|
||||
def descriptionText = value == 'open' ? '{{ device.displayName }} was opened' : '{{ device.displayName }} was closed'
|
||||
sendEvent(name: 'contact', value: value, descriptionText: descriptionText, displayed: false, translatable: true)
|
||||
sendEvent(name: 'status', value: value, descriptionText: descriptionText, translatable: true)
|
||||
}
|
||||
|
||||
private getAccelerationResult(numValue) {
|
||||
log.debug "Acceleration"
|
||||
def name = "acceleration"
|
||||
def value = numValue.endsWith("1") ? "active" : "inactive"
|
||||
def linkText = getLinkText(device)
|
||||
def descriptionText = "$linkText was $value"
|
||||
def value
|
||||
def descriptionText
|
||||
|
||||
if ( numValue.endsWith("1") ) {
|
||||
value = "active"
|
||||
descriptionText = '{{ device.displayName }} was active'
|
||||
} else {
|
||||
value = "inactive"
|
||||
descriptionText = '{{ device.displayName }} was inactive'
|
||||
}
|
||||
|
||||
def isStateChange = isStateChange(device, name, value)
|
||||
[
|
||||
return [
|
||||
name: name,
|
||||
value: value,
|
||||
descriptionText: descriptionText,
|
||||
isStateChange: isStateChange
|
||||
isStateChange: isStateChange,
|
||||
translatable: true
|
||||
]
|
||||
}
|
||||
|
||||
@@ -451,15 +471,15 @@ def configure() {
|
||||
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
|
||||
|
||||
"zcl mfg-code ${manufacturerCode}", "delay 200",
|
||||
"zcl global send-me-a-report 0xFC02 0x0012 0x29 1 3600 {01}", "delay 200",
|
||||
"zcl global send-me-a-report 0xFC02 0x0012 0x29 1 3600 {0100}", "delay 200",
|
||||
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
|
||||
|
||||
"zcl mfg-code ${manufacturerCode}", "delay 200",
|
||||
"zcl global send-me-a-report 0xFC02 0x0013 0x29 1 3600 {01}", "delay 200",
|
||||
"zcl global send-me-a-report 0xFC02 0x0013 0x29 1 3600 {0100}", "delay 200",
|
||||
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
|
||||
|
||||
"zcl mfg-code ${manufacturerCode}", "delay 200",
|
||||
"zcl global send-me-a-report 0xFC02 0x0014 0x29 1 3600 {01}", "delay 200",
|
||||
"zcl global send-me-a-report 0xFC02 0x0014 0x29 1 3600 {0100}", "delay 200",
|
||||
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500"
|
||||
]
|
||||
|
||||
@@ -528,10 +548,9 @@ def garageEvent(zValue) {
|
||||
garageValue = 'garage-open'
|
||||
}
|
||||
if (contactValue != null){
|
||||
def linkText = getLinkText(device)
|
||||
def descriptionText = "${linkText} was ${contactValue == 'open' ? 'opened' : 'closed'}"
|
||||
sendEvent(name: 'contact', value: contactValue, descriptionText: descriptionText, displayed:false)
|
||||
sendEvent(name: 'status', value: garageValue, descriptionText: descriptionText)
|
||||
def descriptionText = contactValue == 'open' ? '{{ device.displayName }} was opened' :'{{ device.displayName }} was closed'
|
||||
sendEvent(name: 'contact', value: contactValue, descriptionText: descriptionText, displayed:false, translatable: true)
|
||||
sendEvent(name: 'status', value: garageValue, descriptionText: descriptionText, translatable: true)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,8 @@ metadata {
|
||||
capability "Battery"
|
||||
|
||||
fingerprint profileId: "FC01", deviceId: "0139"
|
||||
|
||||
attribute "status", "string"
|
||||
}
|
||||
|
||||
simulator {
|
||||
@@ -99,7 +101,7 @@ def parse(String description) {
|
||||
|
||||
}
|
||||
|
||||
private Map parseSingleMessage(description) {
|
||||
private List parseSingleMessage(description) {
|
||||
|
||||
def name = parseName(description)
|
||||
def value = parseValue(description)
|
||||
@@ -108,8 +110,9 @@ private Map parseSingleMessage(description) {
|
||||
def handlerName = value == 'open' ? 'opened' : value
|
||||
def isStateChange = isStateChange(device, name, value)
|
||||
|
||||
def results = [
|
||||
name: name,
|
||||
def results = []
|
||||
results << createEvent(
|
||||
name: "contact",
|
||||
value: value,
|
||||
unit: null,
|
||||
linkText: linkText,
|
||||
@@ -117,8 +120,18 @@ private Map parseSingleMessage(description) {
|
||||
handlerName: handlerName,
|
||||
isStateChange: isStateChange,
|
||||
displayed: displayed(description, isStateChange)
|
||||
]
|
||||
log.debug "Parse results for $device: $results"
|
||||
)
|
||||
|
||||
results << createEvent(
|
||||
name: "status",
|
||||
value: value,
|
||||
unit: null,
|
||||
linkText: linkText,
|
||||
descriptionText: descriptionText,
|
||||
handlerName: handlerName,
|
||||
isStateChange: isStateChange,
|
||||
displayed: displayed(description, isStateChange)
|
||||
)
|
||||
|
||||
results
|
||||
}
|
||||
@@ -190,7 +203,7 @@ private List parseContactMessage(String description) {
|
||||
parts.each { part ->
|
||||
part = part.trim()
|
||||
if (part.startsWith('contactState:')) {
|
||||
results << getContactResult(part, description)
|
||||
results.addAll(getContactResult(part, description))
|
||||
}
|
||||
else if (part.startsWith('accelerationState:')) {
|
||||
results << getAccelerationResult(part, description)
|
||||
@@ -269,7 +282,7 @@ private List parseRssiLqiMessage(String description) {
|
||||
results
|
||||
}
|
||||
|
||||
private getContactResult(part, description) {
|
||||
private List getContactResult(part, description) {
|
||||
def name = "contact"
|
||||
def value = part.endsWith("1") ? "open" : "closed"
|
||||
def handlerName = value == 'open' ? 'opened' : value
|
||||
@@ -277,19 +290,33 @@ private getContactResult(part, description) {
|
||||
def descriptionText = "$linkText was $handlerName"
|
||||
def isStateChange = isStateChange(device, name, value)
|
||||
|
||||
[
|
||||
name: name,
|
||||
value: value,
|
||||
unit: null,
|
||||
linkText: linkText,
|
||||
descriptionText: descriptionText,
|
||||
handlerName: handlerName,
|
||||
isStateChange: isStateChange,
|
||||
displayed: displayed(description, isStateChange)
|
||||
]
|
||||
def results = []
|
||||
results << createEvent(
|
||||
name: "contact",
|
||||
value: value,
|
||||
unit: null,
|
||||
linkText: linkText,
|
||||
descriptionText: descriptionText,
|
||||
handlerName: handlerName,
|
||||
isStateChange: isStateChange,
|
||||
displayed:false
|
||||
)
|
||||
|
||||
results << createEvent(
|
||||
name: "status",
|
||||
value: value,
|
||||
unit: null,
|
||||
linkText: linkText,
|
||||
descriptionText: descriptionText,
|
||||
handlerName: handlerName,
|
||||
isStateChange: isStateChange,
|
||||
displayed: displayed(description, isStateChange)
|
||||
)
|
||||
|
||||
results
|
||||
}
|
||||
|
||||
private getAccelerationResult(part, description) {
|
||||
private Map getAccelerationResult(part, description) {
|
||||
def name = "acceleration"
|
||||
def value = part.endsWith("1") ? "active" : "inactive"
|
||||
def linkText = getLinkText(device)
|
||||
@@ -308,7 +335,7 @@ private getAccelerationResult(part, description) {
|
||||
]
|
||||
}
|
||||
|
||||
private getTempResult(part, description) {
|
||||
private Map getTempResult(part, description) {
|
||||
def name = "temperature"
|
||||
def temperatureScale = getTemperatureScale()
|
||||
def value = zigbee.parseSmartThingsTemperatureValue(part, "temp: ", temperatureScale)
|
||||
@@ -333,7 +360,7 @@ private getTempResult(part, description) {
|
||||
]
|
||||
}
|
||||
|
||||
private getXyzResult(results, description) {
|
||||
private Map getXyzResult(results, description) {
|
||||
def name = "threeAxis"
|
||||
def value = "${results.x},${results.y},${results.z}"
|
||||
def linkText = getLinkText(device)
|
||||
@@ -352,7 +379,7 @@ private getXyzResult(results, description) {
|
||||
]
|
||||
}
|
||||
|
||||
private getBatteryResult(part, description) {
|
||||
private Map getBatteryResult(part, description) {
|
||||
def batteryDivisor = description.split(",").find {it.split(":")[0].trim() == "batteryDivisor"} ? description.split(",").find {it.split(":")[0].trim() == "batteryDivisor"}.split(":")[1].trim() : null
|
||||
def name = "battery"
|
||||
def value = zigbee.parseSmartThingsBatteryValue(part, batteryDivisor)
|
||||
@@ -373,7 +400,7 @@ private getBatteryResult(part, description) {
|
||||
]
|
||||
}
|
||||
|
||||
private getRssiResult(part, description, lastHop=false) {
|
||||
private Map getRssiResult(part, description, lastHop=false) {
|
||||
def name = lastHop ? "lastHopRssi" : "rssi"
|
||||
def valueString = part.split(":")[1].trim()
|
||||
def value = (Integer.parseInt(valueString) - 128).toString()
|
||||
@@ -404,7 +431,7 @@ private getRssiResult(part, description, lastHop=false) {
|
||||
* Note: To make the signal strength indicator more accurate, we could combine
|
||||
* LQI with RSSI.
|
||||
*/
|
||||
private getLqiResult(part, description, lastHop=false) {
|
||||
private Map getLqiResult(part, description, lastHop=false) {
|
||||
def name = lastHop ? "lastHopLqi" : "lqi"
|
||||
def valueString = part.split(":")[1].trim()
|
||||
def percentageOf = 255
|
||||
@@ -449,6 +476,7 @@ private Boolean isOrientationMessage(String description) {
|
||||
description ==~ /x:.*y:.*z:.*rssi:.*lqi:.*/
|
||||
}
|
||||
|
||||
//Note: Not using this method anymore
|
||||
private String parseName(String description) {
|
||||
if (isSupportedDescription(description)) {
|
||||
return "contact"
|
||||
|
||||
@@ -56,21 +56,17 @@ metadata {
|
||||
def parse(String description) {
|
||||
log.debug "description is $description"
|
||||
|
||||
def resultMap = zigbee.getKnownDescription(description)
|
||||
if (resultMap) {
|
||||
log.info resultMap
|
||||
if (resultMap.type == "update") {
|
||||
log.info "$device updates: ${resultMap.value}"
|
||||
}
|
||||
else if (resultMap.type == "power") {
|
||||
def powerValue
|
||||
def event = zigbee.getEvent(description)
|
||||
if (event) {
|
||||
log.info event
|
||||
if (event.name == "power") {
|
||||
if (device.getDataValue("manufacturer") != "OSRAM") { //OSRAM devices do not reliably update power
|
||||
powerValue = (resultMap.value as Integer)/10 //TODO: The divisor value needs to be set as part of configuration
|
||||
sendEvent(name: "power", value: powerValue)
|
||||
event.value = (event.value as Integer) / 10 //TODO: The divisor value needs to be set as part of configuration
|
||||
sendEvent(event)
|
||||
}
|
||||
}
|
||||
else {
|
||||
sendEvent(name: resultMap.type, value: resultMap.value)
|
||||
sendEvent(event)
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
||||
@@ -51,15 +51,9 @@ metadata {
|
||||
def parse(String description) {
|
||||
log.debug "description is $description"
|
||||
|
||||
def resultMap = zigbee.getKnownDescription(description)
|
||||
if (resultMap) {
|
||||
log.info resultMap
|
||||
if (resultMap.type == "update") {
|
||||
log.info "$device updates: ${resultMap.value}"
|
||||
}
|
||||
else {
|
||||
sendEvent(name: resultMap.type, value: resultMap.value)
|
||||
}
|
||||
def event = zigbee.getEvent(description)
|
||||
if (event) {
|
||||
sendEvent(event)
|
||||
}
|
||||
else {
|
||||
log.warn "DID NOT PARSE MESSAGE for description : $description"
|
||||
|
||||
@@ -83,32 +83,19 @@ def uninstalled() {
|
||||
}
|
||||
|
||||
def configure() {
|
||||
/*
|
||||
def cmds =
|
||||
zigbee.configSetup("${CLUSTER_DOORLOCK}", "${DOORLOCK_ATTR_LOCKSTATE}",
|
||||
"${TYPE_ENUM8}", 0, 3600, "{01}") +
|
||||
zigbee.configSetup("${CLUSTER_POWER}", "${POWER_ATTR_BATTERY_PERCENTAGE_REMAINING}",
|
||||
"${TYPE_U8}", 600, 21600, "{01}")
|
||||
*/
|
||||
def zigbeeId = device.zigbeeId
|
||||
def cmds =
|
||||
[
|
||||
"zdo bind 0x${device.deviceNetworkId} 0x${device.endpointId} 1 ${CLUSTER_DOORLOCK} {$zigbeeId} {}", "delay 200",
|
||||
"zcl global send-me-a-report ${CLUSTER_DOORLOCK} ${DOORLOCK_ATTR_LOCKSTATE} ${TYPE_ENUM8} 0 3600 {01}", "delay 200",
|
||||
"send 0x${device.deviceNetworkId} 1 0x${device.endpointId}", "delay 200",
|
||||
|
||||
"zdo bind 0x${device.deviceNetworkId} 0x${device.endpointId} 1 ${CLUSTER_POWER} {$zigbeeId} {}", "delay 200",
|
||||
"zcl global send-me-a-report ${CLUSTER_POWER} ${POWER_ATTR_BATTERY_PERCENTAGE_REMAINING} ${TYPE_U8} 600 21600 {01}", "delay 200",
|
||||
"send 0x${device.deviceNetworkId} 1 0x${device.endpointId}", "delay 200",
|
||||
]
|
||||
zigbee.configureReporting(CLUSTER_DOORLOCK, DOORLOCK_ATTR_LOCKSTATE,
|
||||
TYPE_ENUM8, 0, 3600, null) +
|
||||
zigbee.configureReporting(CLUSTER_POWER, POWER_ATTR_BATTERY_PERCENTAGE_REMAINING,
|
||||
TYPE_U8, 600, 21600, 0x01)
|
||||
log.info "configure() --- cmds: $cmds"
|
||||
return cmds + refresh() // send refresh cmds as part of config
|
||||
}
|
||||
|
||||
def refresh() {
|
||||
def cmds =
|
||||
zigbee.refreshData("${CLUSTER_DOORLOCK}", "${DOORLOCK_ATTR_LOCKSTATE}") +
|
||||
zigbee.refreshData("${CLUSTER_POWER}", "${POWER_ATTR_BATTERY_PERCENTAGE_REMAINING}")
|
||||
zigbee.readAttribute(CLUSTER_DOORLOCK, DOORLOCK_ATTR_LOCKSTATE) +
|
||||
zigbee.readAttribute(CLUSTER_POWER, POWER_ATTR_BATTERY_PERCENTAGE_REMAINING)
|
||||
log.info "refresh() --- cmds: $cmds"
|
||||
return cmds
|
||||
}
|
||||
@@ -121,34 +108,27 @@ def parse(String description) {
|
||||
map = parseReportAttributeMessage(description)
|
||||
}
|
||||
|
||||
log.debug "parse() --- Parse returned $map"
|
||||
def result = map ? createEvent(map) : null
|
||||
log.debug "parse() --- returned: $result"
|
||||
return result
|
||||
}
|
||||
|
||||
// Lock capability commands
|
||||
def lock() {
|
||||
//def cmds = zigbee.zigbeeCommand("${CLUSTER_DOORLOCK}", "${DOORLOCK_CMD_LOCK_DOOR}", "{}")
|
||||
//log.info "lock() -- cmds: $cmds"
|
||||
//return cmds
|
||||
"st cmd 0x${device.deviceNetworkId} 0x${device.endpointId} ${CLUSTER_DOORLOCK} ${DOORLOCK_CMD_LOCK_DOOR} {}"
|
||||
def cmds = zigbee.command(CLUSTER_DOORLOCK, DOORLOCK_CMD_LOCK_DOOR)
|
||||
log.info "lock() -- cmds: $cmds"
|
||||
return cmds
|
||||
}
|
||||
|
||||
def unlock() {
|
||||
//def cmds = zigbee.zigbeeCommand("${CLUSTER_DOORLOCK}", "${DOORLOCK_CMD_UNLOCK_DOOR}", "{}")
|
||||
//log.info "unlock() -- cmds: $cmds"
|
||||
//return cmds
|
||||
"st cmd 0x${device.deviceNetworkId} 0x${device.endpointId} ${CLUSTER_DOORLOCK} ${DOORLOCK_CMD_UNLOCK_DOOR} {}"
|
||||
def cmds = zigbee.command(CLUSTER_DOORLOCK, DOORLOCK_CMD_UNLOCK_DOOR)
|
||||
log.info "unlock() -- cmds: $cmds"
|
||||
return cmds
|
||||
}
|
||||
|
||||
// Private methods
|
||||
private Map parseReportAttributeMessage(String description) {
|
||||
log.trace "parseReportAttributeMessage() --- description: $description"
|
||||
|
||||
Map descMap = zigbee.parseDescriptionAsMap(description)
|
||||
|
||||
log.debug "parseReportAttributeMessage() --- descMap: $descMap"
|
||||
|
||||
Map resultMap = [:]
|
||||
if (descMap.clusterInt == CLUSTER_POWER && descMap.attrInt == POWER_ATTR_BATTERY_PERCENTAGE_REMAINING) {
|
||||
resultMap.name = "battery"
|
||||
@@ -156,18 +136,24 @@ private Map parseReportAttributeMessage(String description) {
|
||||
if (device.getDataValue("manufacturer") == "Yale") { //Handling issue with Yale locks incorrect battery reporting
|
||||
resultMap.value = Integer.parseInt(descMap.value, 16)
|
||||
}
|
||||
log.info "parseReportAttributeMessage() --- battery: ${resultMap.value}"
|
||||
}
|
||||
else if (descMap.clusterInt == CLUSTER_DOORLOCK && descMap.attrInt == DOORLOCK_ATTR_LOCKSTATE) {
|
||||
def value = Integer.parseInt(descMap.value, 16)
|
||||
def linkText = getLinkText(device)
|
||||
resultMap.name = "lock"
|
||||
resultMap.putAll([0:["value":"unknown",
|
||||
"descriptionText":"Not fully locked"],
|
||||
1:["value":"locked"],
|
||||
2:["value":"unlocked"]].get(value,
|
||||
["value":"unknown",
|
||||
"descriptionText":"Unknown lock state"]))
|
||||
log.info "parseReportAttributeMessage() --- lock: ${resultMap.value}"
|
||||
if (value == 0) {
|
||||
resultMap.value = "unknown"
|
||||
resultMap.descriptionText = "${linkText} is not fully locked"
|
||||
} else if (value == 1) {
|
||||
resultMap.value = "locked"
|
||||
resultMap.descriptionText = "${linkText} is locked"
|
||||
} else if (value == 2) {
|
||||
resultMap.value = "unlocked"
|
||||
resultMap.descriptionText = "${linkText} is unlocked"
|
||||
} else {
|
||||
resultMap.value = "unknown"
|
||||
resultMap.descriptionText = "${linkText} is in unknown lock state"
|
||||
}
|
||||
}
|
||||
else {
|
||||
log.debug "parseReportAttributeMessage() --- ignoring attribute"
|
||||
|
||||
@@ -51,22 +51,15 @@ metadata {
|
||||
// Parse incoming device messages to generate events
|
||||
def parse(String description) {
|
||||
log.debug "description is $description"
|
||||
|
||||
def resultMap = zigbee.getKnownDescription(description)
|
||||
if (resultMap) {
|
||||
log.info resultMap
|
||||
if (resultMap.type == "update") {
|
||||
log.info "$device updates: ${resultMap.value}"
|
||||
}
|
||||
else if (resultMap.type == "power") {
|
||||
def event = zigbee.getEvent(description)
|
||||
if (event) {
|
||||
if (event.name == "power") {
|
||||
def powerValue
|
||||
if (device.getDataValue("manufacturer") != "OSRAM") { //OSRAM devices do not reliably update power
|
||||
powerValue = (resultMap.value as Integer)/10 //TODO: The divisor value needs to be set as part of configuration
|
||||
sendEvent(name: "power", value: powerValue)
|
||||
}
|
||||
powerValue = (event.value as Integer)/10 //TODO: The divisor value needs to be set as part of configuration
|
||||
sendEvent(name: "power", value: powerValue)
|
||||
}
|
||||
else {
|
||||
sendEvent(name: resultMap.type, value: resultMap.value)
|
||||
sendEvent(event)
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
||||
@@ -53,16 +53,9 @@ metadata {
|
||||
// Parse incoming device messages to generate events
|
||||
def parse(String description) {
|
||||
log.debug "description is $description"
|
||||
|
||||
def resultMap = zigbee.getKnownDescription(description)
|
||||
if (resultMap) {
|
||||
log.info resultMap
|
||||
if (resultMap.type == "update") {
|
||||
log.info "$device updates: ${resultMap.value}"
|
||||
}
|
||||
else {
|
||||
sendEvent(name: resultMap.type, value: resultMap.value)
|
||||
}
|
||||
def event = zigbee.getEvent(description)
|
||||
if (event) {
|
||||
sendEvent(event)
|
||||
}
|
||||
else {
|
||||
log.warn "DID NOT PARSE MESSAGE for description : $description"
|
||||
|
||||
@@ -73,16 +73,9 @@ metadata {
|
||||
// Parse incoming device messages to generate events
|
||||
def parse(String description) {
|
||||
log.debug "description is $description"
|
||||
|
||||
def finalResult = zigbee.getKnownDescription(description)
|
||||
if (finalResult) {
|
||||
log.info finalResult
|
||||
if (finalResult.type == "update") {
|
||||
log.info "$device updates: ${finalResult.value}"
|
||||
}
|
||||
else {
|
||||
sendEvent(name: finalResult.type, value: finalResult.value)
|
||||
}
|
||||
def event = zigbee.getEvent(description)
|
||||
if (event) {
|
||||
sendEvent(event)
|
||||
}
|
||||
else {
|
||||
log.warn "DID NOT PARSE MESSAGE for description : $description"
|
||||
|
||||
@@ -57,7 +57,7 @@ def parse(String description) {
|
||||
return result
|
||||
}
|
||||
|
||||
def sensorValueEvent(Short value) {
|
||||
def sensorValueEvent(value) {
|
||||
if (value) {
|
||||
createEvent(name: "motion", value: "active", descriptionText: "$device.displayName detected motion")
|
||||
} else {
|
||||
@@ -94,24 +94,24 @@ def zwaveEvent(physicalgraph.zwave.commands.notificationv3.NotificationReport cm
|
||||
{
|
||||
def result = []
|
||||
if (cmd.notificationType == 0x07) {
|
||||
if (cmd.event == 0x01 || cmd.event == 0x02) {
|
||||
if (cmd.v1AlarmType == 0x07) { // special case for nonstandard messages from Monoprice ensors
|
||||
result << sensorValueEvent(cmd.v1AlarmLevel)
|
||||
} else if (cmd.event == 0x01 || cmd.event == 0x02 || cmd.event == 0x07 || cmd.event == 0x08) {
|
||||
result << sensorValueEvent(1)
|
||||
} else if (cmd.event == 0x00) {
|
||||
result << sensorValueEvent(0)
|
||||
} else if (cmd.event == 0x03) {
|
||||
result << createEvent(descriptionText: "$device.displayName covering was removed", isStateChange: true)
|
||||
result << response(zwave.wakeUpV1.wakeUpIntervalSet(seconds:4*3600, nodeid:zwaveHubNodeId))
|
||||
if(!state.MSR) result << response(zwave.manufacturerSpecificV2.manufacturerSpecificGet())
|
||||
result << createEvent(name: "tamper", value: "detected", descriptionText: "$device.displayName covering was removed", isStateChange: true)
|
||||
result << response(zwave.batteryV1.batteryGet())
|
||||
} else if (cmd.event == 0x05 || cmd.event == 0x06) {
|
||||
result << createEvent(descriptionText: "$device.displayName detected glass breakage", isStateChange: true)
|
||||
} else if (cmd.event == 0x07) {
|
||||
if(!state.MSR) result << response(zwave.manufacturerSpecificV2.manufacturerSpecificGet())
|
||||
result << sensorValueEvent(1)
|
||||
}
|
||||
} else if (cmd.notificationType) {
|
||||
def text = "Notification $cmd.notificationType: event ${([cmd.event] + cmd.eventParameter).join(", ")}"
|
||||
result << createEvent(name: "notification$cmd.notificationType", value: "$cmd.event", descriptionText: text, displayed: false)
|
||||
result << createEvent(name: "notification$cmd.notificationType", value: "$cmd.event", descriptionText: text, isStateChange: true, displayed: false)
|
||||
} else {
|
||||
def value = cmd.v1AlarmLevel == 255 ? "active" : cmd.v1AlarmLevel ?: "inactive"
|
||||
result << createEvent(name: "alarm $cmd.v1AlarmType", value: value, displayed: false)
|
||||
result << createEvent(name: "alarm $cmd.v1AlarmType", value: value, isStateChange: true, displayed: false)
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
@@ -61,37 +61,44 @@ def parse(String description) {
|
||||
zwaveEvent(cmd, results)
|
||||
}
|
||||
}
|
||||
// log.debug "\"$description\" parsed to ${results.inspect()}"
|
||||
log.debug "'$description' parsed to ${results.inspect()}"
|
||||
return results
|
||||
}
|
||||
|
||||
|
||||
def createSmokeOrCOEvents(name, results) {
|
||||
def text = null
|
||||
if (name == "smoke") {
|
||||
text = "$device.displayName smoke was detected!"
|
||||
// these are displayed:false because the composite event is the one we want to see in the app
|
||||
results << createEvent(name: "smoke", value: "detected", descriptionText: text, displayed: false)
|
||||
} else if (name == "carbonMonoxide") {
|
||||
text = "$device.displayName carbon monoxide was detected!"
|
||||
results << createEvent(name: "carbonMonoxide", value: "detected", descriptionText: text, displayed: false)
|
||||
} else if (name == "tested") {
|
||||
text = "$device.displayName was tested"
|
||||
results << createEvent(name: "smoke", value: "tested", descriptionText: text, displayed: false)
|
||||
results << createEvent(name: "carbonMonoxide", value: "tested", descriptionText: text, displayed: false)
|
||||
} else if (name == "smokeClear") {
|
||||
text = "$device.displayName smoke is clear"
|
||||
results << createEvent(name: "smoke", value: "clear", descriptionText: text, displayed: false)
|
||||
name = "clear"
|
||||
} else if (name == "carbonMonoxideClear") {
|
||||
text = "$device.displayName carbon monoxide is clear"
|
||||
results << createEvent(name: "carbonMonoxide", value: "clear", descriptionText: text, displayed: false)
|
||||
name = "clear"
|
||||
} else if (name == "testClear") {
|
||||
text = "$device.displayName smoke is clear"
|
||||
results << createEvent(name: "smoke", value: "clear", descriptionText: text, displayed: false)
|
||||
results << createEvent(name: "carbonMonoxide", value: "clear", displayed: false)
|
||||
name = "clear"
|
||||
switch (name) {
|
||||
case "smoke":
|
||||
text = "$device.displayName smoke was detected!"
|
||||
// these are displayed:false because the composite event is the one we want to see in the app
|
||||
results << createEvent(name: "smoke", value: "detected", descriptionText: text, displayed: false)
|
||||
break
|
||||
case "carbonMonoxide":
|
||||
text = "$device.displayName carbon monoxide was detected!"
|
||||
results << createEvent(name: "carbonMonoxide", value: "detected", descriptionText: text, displayed: false)
|
||||
break
|
||||
case "tested":
|
||||
text = "$device.displayName was tested"
|
||||
results << createEvent(name: "smoke", value: "tested", descriptionText: text, displayed: false)
|
||||
results << createEvent(name: "carbonMonoxide", value: "tested", descriptionText: text, displayed: false)
|
||||
break
|
||||
case "smokeClear":
|
||||
text = "$device.displayName smoke is clear"
|
||||
results << createEvent(name: "smoke", value: "clear", descriptionText: text, displayed: false)
|
||||
name = "clear"
|
||||
break
|
||||
case "carbonMonoxideClear":
|
||||
text = "$device.displayName carbon monoxide is clear"
|
||||
results << createEvent(name: "carbonMonoxide", value: "clear", descriptionText: text, displayed: false)
|
||||
name = "clear"
|
||||
break
|
||||
case "testClear":
|
||||
text = "$device.displayName test cleared"
|
||||
results << createEvent(name: "smoke", value: "clear", descriptionText: text, displayed: false)
|
||||
results << createEvent(name: "carbonMonoxide", value: "clear", displayed: false)
|
||||
name = "clear"
|
||||
break
|
||||
}
|
||||
// This composite event is used for updating the tile
|
||||
results << createEvent(name: "alarmState", value: name, descriptionText: text)
|
||||
@@ -117,8 +124,10 @@ def zwaveEvent(physicalgraph.zwave.commands.alarmv2.AlarmReport cmd, results) {
|
||||
createSmokeOrCOEvents(cmd.alarmLevel ? "tested" : "testClear", results)
|
||||
break
|
||||
case 13: // sent every hour -- not sure what this means, just a wake up notification?
|
||||
if (cmd.alarmLevel != 255) {
|
||||
results << createEvent(descriptionText: "$device.displayName code 13 is $cmd.alarmLevel", displayed: true)
|
||||
if (cmd.alarmLevel == 255) {
|
||||
results << createEvent(descriptionText: "$device.displayName checked in", isStateChange: false)
|
||||
} else {
|
||||
results << createEvent(descriptionText: "$device.displayName code 13 is $cmd.alarmLevel", isStateChange:true, displayed:false)
|
||||
}
|
||||
|
||||
// Clear smoke in case they pulled batteries and we missed the clear msg
|
||||
@@ -127,9 +136,8 @@ def zwaveEvent(physicalgraph.zwave.commands.alarmv2.AlarmReport cmd, results) {
|
||||
}
|
||||
|
||||
// Check battery if we don't have a recent battery event
|
||||
def prevBattery = device.currentState("battery")
|
||||
if (!prevBattery || (new Date().time - prevBattery.date.time)/60000 >= 60 * 53) {
|
||||
results << new physicalgraph.device.HubAction(zwave.batteryV1.batteryGet().format())
|
||||
if (!state.lastbatt || (now() - state.lastbatt) >= 48*60*60*1000) {
|
||||
results << response(zwave.batteryV1.batteryGet())
|
||||
}
|
||||
break
|
||||
default:
|
||||
@@ -158,12 +166,17 @@ def zwaveEvent(physicalgraph.zwave.commands.sensoralarmv1.SensorAlarmReport cmd,
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.wakeupv1.WakeUpNotification cmd, results) {
|
||||
results << new physicalgraph.device.HubAction(zwave.wakeUpV1.wakeUpNoMoreInformation().format())
|
||||
results << createEvent(descriptionText: "$device.displayName woke up", isStateChange: false)
|
||||
if (!state.lastbatt || (now() - state.lastbatt) >= 56*60*60*1000) {
|
||||
results << response(zwave.batteryV1.batteryGet(), "delay 2000", zwave.wakeUpV1.wakeUpNoMoreInformation())
|
||||
} else {
|
||||
results << response(zwave.wakeUpV1.wakeUpNoMoreInformation())
|
||||
}
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.batteryv1.BatteryReport cmd, results) {
|
||||
def map = [ name: "battery", unit: "%" ]
|
||||
def map = [ name: "battery", unit: "%", isStateChange: true ]
|
||||
state.lastbatt = now()
|
||||
if (cmd.batteryLevel == 0xFF) {
|
||||
map.value = 1
|
||||
map.descriptionText = "$device.displayName battery is low!"
|
||||
|
||||
@@ -67,11 +67,11 @@ mappings {
|
||||
def listAllDevices() {
|
||||
def resp = []
|
||||
switches.each {
|
||||
resp << [name: it.name, label: it.label, value: it.currentValue("switch"), type: "switch", id: it.id, hub: it.hub.name]
|
||||
resp << [name: it.name, label: it.label, value: it.currentValue("switch"), type: "switch", id: it.id, hub: it.hub?.name]
|
||||
}
|
||||
|
||||
locks.each {
|
||||
resp << [name: it.name, label: it.label, value: it.currentValue("lock"), type: "lock", id: it.id, hub: it.hub.name]
|
||||
resp << [name: it.name, label: it.label, value: it.currentValue("lock"), type: "lock", id: it.id, hub: it.hub?.name]
|
||||
}
|
||||
return resp
|
||||
}
|
||||
|
||||
@@ -87,13 +87,10 @@ def authPage(){
|
||||
}
|
||||
|
||||
def installed() {
|
||||
log.debug "Installed with settings: ${settings}"
|
||||
initialize()
|
||||
}
|
||||
|
||||
def updated() {
|
||||
log.debug "Updated with settings: ${settings}"
|
||||
unsubscribe()
|
||||
initialize()
|
||||
}
|
||||
|
||||
@@ -106,72 +103,24 @@ def uninstalled() {
|
||||
}
|
||||
|
||||
def initialize() {
|
||||
atomicState.attached_sensors = [:]
|
||||
unsubscribe()
|
||||
if (plantlinksensors){
|
||||
subscribe(plantlinksensors, "moisture_status", moistureHandler)
|
||||
subscribe(plantlinksensors, "battery_status", batteryHandler)
|
||||
plantlinksensors.each{ sensor_device ->
|
||||
sensor_device.setStatusIcon("Waiting on First Measurement")
|
||||
subscribe(sensor_device, "moisture_status", moistureHandler)
|
||||
subscribe(sensor_device, "battery_status", batteryHandler)
|
||||
sensor_device.setInstallSmartApp("connectedToSmartApp")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def dock_sensor(device_serial, expected_plant_name) {
|
||||
def docking_body_json_builder = new JsonBuilder([version: '1c', smartthings_device_id: device_serial])
|
||||
def docking_params = [
|
||||
uri : appSettings.https_plantLinkServer,
|
||||
path : "/api/v1/smartthings/links",
|
||||
headers : ["Content-Type": "application/json", "Authorization": "Bearer ${atomicState.authToken}"],
|
||||
contentType: "application/json",
|
||||
body: docking_body_json_builder.toString()
|
||||
]
|
||||
def plant_post_body_map = [
|
||||
plant_type_key: 999999,
|
||||
soil_type_key : 1000004
|
||||
]
|
||||
def plant_post_params = [
|
||||
uri : appSettings.https_plantLinkServer,
|
||||
path : "/api/v1/plants",
|
||||
headers : ["Content-Type": "application/json", "Authorization": "Bearer ${atomicState.authToken}"],
|
||||
contentType: "application/json",
|
||||
]
|
||||
log.debug "Creating new plant on myplantlink.com - ${expected_plant_name}"
|
||||
httpPost(docking_params) { docking_response ->
|
||||
if (parse_api_response(docking_response, "Docking a link")) {
|
||||
if (docking_response.data.plants.size() == 0) {
|
||||
log.debug "creating plant for - ${expected_plant_name}"
|
||||
plant_post_body_map["name"] = expected_plant_name
|
||||
plant_post_body_map['links_key'] = [docking_response.data.key]
|
||||
def plant_post_body_json_builder = new JsonBuilder(plant_post_body_map)
|
||||
plant_post_params["body"] = plant_post_body_json_builder.toString()
|
||||
httpPost(plant_post_params) { plant_post_response ->
|
||||
if(parse_api_response(plant_post_response, 'creating plant')){
|
||||
def attached_map = atomicState.attached_sensors
|
||||
attached_map[device_serial] = plant_post_response.data
|
||||
atomicState.attached_sensors = attached_map
|
||||
}
|
||||
}
|
||||
} else {
|
||||
def plant = docking_response.data.plants[0]
|
||||
def attached_map = atomicState.attached_sensors
|
||||
attached_map[device_serial] = plant
|
||||
atomicState.attached_sensors = attached_map
|
||||
checkAndUpdatePlantIfNeeded(plant, expected_plant_name)
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
def checkAndUpdatePlantIfNeeded(plant, expected_plant_name){
|
||||
def updatePlantNameIfNeeded(plant, expected_plant_name){
|
||||
def plant_put_params = [
|
||||
uri : appSettings.https_plantLinkServer,
|
||||
headers : ["Content-Type": "application/json", "Authorization": "Bearer ${atomicState.authToken}"],
|
||||
contentType : "application/json"
|
||||
]
|
||||
if (plant.name != expected_plant_name) {
|
||||
log.debug "updating plant for - ${expected_plant_name}"
|
||||
log.debug "renaming plant ${plant.key} - ${expected_plant_name}"
|
||||
plant_put_params["path"] = "/api/v1/plants/${plant.key}"
|
||||
def plant_put_body_map = [
|
||||
name: expected_plant_name
|
||||
@@ -185,32 +134,39 @@ def checkAndUpdatePlantIfNeeded(plant, expected_plant_name){
|
||||
}
|
||||
|
||||
def moistureHandler(event){
|
||||
def expected_plant_name = "SmartThings - ${event.displayName}"
|
||||
def device_serial = getDeviceSerialFromEvent(event)
|
||||
log.debug "moistureHandler - ${event.value}"
|
||||
|
||||
if (!atomicState.attached_sensors.containsKey(device_serial)){
|
||||
dock_sensor(device_serial, expected_plant_name)
|
||||
def expected_plant_name = "${event.displayName} (ST)"
|
||||
def device_serial = getDeviceSerialFromEvent(event)
|
||||
def device_battery = atomicState["battery${device_serial}"]
|
||||
if ( device_battery == null){
|
||||
log.error "Missing Battery Voltage - next cycle should have it"
|
||||
}else{
|
||||
// {"type":"link","signal":"0x00","zigbeedeviceid":"0022A3000003D75A","created":1458843686,"moisture":"0x1987"}
|
||||
def appendedEventWithBatteryInfo = event.value.replace('}',",\"battery\":\"${device_battery}\"}")
|
||||
log.debug "payload - ${appendedEventWithBatteryInfo}"
|
||||
def measurement_post_params = [
|
||||
uri: appSettings.https_plantLinkServer,
|
||||
path: "/api/v1/smartthings/links/${device_serial}/measurements",
|
||||
headers: ["Content-Type": "application/json", "Authorization": "Bearer ${atomicState.authToken}"],
|
||||
contentType: "application/json",
|
||||
body: event.value
|
||||
uri: appSettings.https_plantLinkServer,
|
||||
path: "/api/v1/smartthings/links/${device_serial}/measurements",
|
||||
headers: ["Content-Type": "application/json", "Authorization": "Bearer ${atomicState.authToken}"],
|
||||
contentType: "application/json",
|
||||
body: appendedEventWithBatteryInfo
|
||||
]
|
||||
|
||||
httpPost(measurement_post_params) { measurement_post_response ->
|
||||
if (parse_api_response(measurement_post_response, 'creating moisture measurement') &&
|
||||
measurement_post_response.data.size() >0){
|
||||
if (parse_api_response(measurement_post_response, 'creating moisture measurement') && measurement_post_response.data.size() >0){
|
||||
def measurement = measurement_post_response.data[0]
|
||||
def plant = measurement.plant
|
||||
log.debug plant
|
||||
checkAndUpdatePlantIfNeeded(plant, expected_plant_name)
|
||||
|
||||
updatePlantNameIfNeeded(plant, expected_plant_name)
|
||||
|
||||
plantlinksensors.each{ sensor_device ->
|
||||
if (sensor_device.id == event.deviceId){
|
||||
sensor_device.setStatusIcon(plant.status)
|
||||
if (plant.last_measurements && plant.last_measurements[0].moisture){
|
||||
sensor_device.setPlantFuelLevel(plant.last_measurements[0].moisture * 100 as int)
|
||||
}
|
||||
|
||||
if (plant.last_measurements && plant.last_measurements[0].battery){
|
||||
sensor_device.setBatteryLevel(plant.last_measurements[0].battery * 100 as int)
|
||||
}
|
||||
@@ -224,21 +180,7 @@ def moistureHandler(event){
|
||||
def batteryHandler(event){
|
||||
def expected_plant_name = "SmartThings - ${event.displayName}"
|
||||
def device_serial = getDeviceSerialFromEvent(event)
|
||||
|
||||
if (!atomicState.attached_sensors.containsKey(device_serial)){
|
||||
dock_sensor(device_serial, expected_plant_name)
|
||||
}else{
|
||||
def measurement_post_params = [
|
||||
uri: appSettings.https_plantLinkServer,
|
||||
path: "/api/v1/smartthings/links/${device_serial}/measurements",
|
||||
headers: ["Content-Type": "application/json", "Authorization": "Bearer ${atomicState.authToken}"],
|
||||
contentType: "application/json",
|
||||
body: event.value
|
||||
]
|
||||
httpPost(measurement_post_params) { measurement_post_response ->
|
||||
parse_api_response(measurement_post_response, 'creating battery measurement')
|
||||
}
|
||||
}
|
||||
atomicState["battery${device_serial}"] = getDeviceBatteryFromEvent(event)
|
||||
}
|
||||
|
||||
def getDeviceSerialFromEvent(event){
|
||||
@@ -247,6 +189,12 @@ def getDeviceSerialFromEvent(event){
|
||||
return match_result[0][1]
|
||||
}
|
||||
|
||||
def getDeviceBatteryFromEvent(event){
|
||||
def pattern = /.*"battery"\s*:\s*"(\w+)".*/
|
||||
def match_result = (event.value =~ pattern)
|
||||
return match_result[0][1]
|
||||
}
|
||||
|
||||
def oauthInitUrl(){
|
||||
atomicState.oauthInitState = UUID.randomUUID().toString()
|
||||
def oauthParams = [
|
||||
@@ -263,12 +211,13 @@ def buildRedirectUrl(){
|
||||
}
|
||||
|
||||
def swapToken(){
|
||||
log.debug "PlantLink Connector - OAuth Token"
|
||||
def code = params.code
|
||||
def oauthState = params.state
|
||||
def stcid = appSettings.client_id
|
||||
def postParams = [
|
||||
method: 'POST',
|
||||
uri: "https://oso-tech.appspot.com",
|
||||
uri: appSettings.https_plantLinkServer,
|
||||
path: "/api/v1/oauth-token",
|
||||
query: [grant_type:'authorization_code', code:params.code, client_id:stcid,
|
||||
client_secret:appSettings.client_secret, redirect_uri: buildRedirectUrl()],
|
||||
@@ -321,10 +270,11 @@ def swapToken(){
|
||||
}
|
||||
|
||||
private refreshAuthToken() {
|
||||
log.debug "PlantLink Connector - Refresh OAuth"
|
||||
def stcid = appSettings.client_id
|
||||
def refreshParams = [
|
||||
method: 'POST',
|
||||
uri: "https://hardware-dot-oso-tech.appspot.com",
|
||||
uri: appSettings.https_plantLinkServer,
|
||||
path: "/api/v1/oauth-token",
|
||||
query: [grant_type:'refresh_token', code:"${atomicState.refreshToken}", client_id:stcid,
|
||||
client_secret:appSettings.client_secret],
|
||||
@@ -333,7 +283,6 @@ private refreshAuthToken() {
|
||||
def jsonMap
|
||||
httpPost(refreshParams) { resp ->
|
||||
if(resp.status == 200){
|
||||
log.debug "OAuth Token refreshed"
|
||||
jsonMap = resp.data
|
||||
if (resp.data) {
|
||||
atomicState.refreshToken = resp?.data?.refresh_token
|
||||
@@ -346,12 +295,12 @@ private refreshAuthToken() {
|
||||
}
|
||||
data.action = ""
|
||||
}else{
|
||||
log.debug "refresh failed ${resp.status} : ${resp.status.code}"
|
||||
log.debug "PlantLink Server - ${resp.status} : ${resp.status.code}"
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(Exception e){
|
||||
log.debug "caught exception refreshing auth token: " + e
|
||||
log.debug "PlantLink Connector - OAuth Refresh Failed: " + e
|
||||
}
|
||||
}
|
||||
|
||||
@@ -364,7 +313,7 @@ def parse_api_response(resp, message) {
|
||||
refreshAuthToken()
|
||||
return false
|
||||
} else {
|
||||
debugEvent("Authentication error, invalid authentication method, lack of credentials, etc.", true)
|
||||
debugEvent("Plantlink Error: ${resp.status} - ${resp.status.code}", true)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -463,29 +463,32 @@ def pollChildren(child = null) {
|
||||
}
|
||||
|
||||
// Poll Child is invoked from the Child Device itself as part of the Poll Capability
|
||||
def pollChild(child){
|
||||
def pollChild(){
|
||||
|
||||
if (pollChildren(child)){
|
||||
if (!child.device.deviceNetworkId.startsWith("ecobee_sensor")){
|
||||
if(atomicState.thermostats[child.device.deviceNetworkId] != null) {
|
||||
def tData = atomicState.thermostats[child.device.deviceNetworkId]
|
||||
log.info "pollChild(child)>> data for ${child.device.deviceNetworkId} : ${tData.data}"
|
||||
child.generateEvent(tData.data) //parse received message from parent
|
||||
} else if(atomicState.thermostats[child.device.deviceNetworkId] == null) {
|
||||
log.error "ERROR: Device connection removed? no data for ${child.device.deviceNetworkId}"
|
||||
return null
|
||||
def devices = getChildDevices()
|
||||
|
||||
if (pollChildren()){
|
||||
devices.each { child ->
|
||||
if (!child.device.deviceNetworkId.startsWith("ecobee_sensor")){
|
||||
if(atomicState.thermostats[child.device.deviceNetworkId] != null) {
|
||||
def tData = atomicState.thermostats[child.device.deviceNetworkId]
|
||||
log.info "pollChild(child)>> data for ${child.device.deviceNetworkId} : ${tData.data}"
|
||||
child.generateEvent(tData.data) //parse received message from parent
|
||||
} else if(atomicState.thermostats[child.device.deviceNetworkId] == null) {
|
||||
log.error "ERROR: Device connection removed? no data for ${child.device.deviceNetworkId}"
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log.info "ERROR: pollChildren(child) for ${child.device.deviceNetworkId} after polling"
|
||||
log.info "ERROR: pollChildren()"
|
||||
return null
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void poll() {
|
||||
def devices = getChildDevices()
|
||||
devices.each {pollChild(it)}
|
||||
pollChild()
|
||||
}
|
||||
|
||||
def availableModes(child) {
|
||||
|
||||
@@ -24,7 +24,7 @@ definition(
|
||||
category: "SmartThings Labs",
|
||||
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Partner/hue.png",
|
||||
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Partner/hue@2x.png",
|
||||
//singleInstance: true
|
||||
singleInstance: true
|
||||
)
|
||||
|
||||
preferences {
|
||||
@@ -68,7 +68,7 @@ def bridgeDiscovery(params=[:])
|
||||
}
|
||||
|
||||
//setup.xml request every 3 seconds except on discoveries
|
||||
if(((bridgeRefreshCount % 1) == 0) && ((bridgeRefreshCount % 5) != 0)) {
|
||||
if(((bridgeRefreshCount % 3) == 0) && ((bridgeRefreshCount % 5) != 0)) {
|
||||
verifyHueBridges()
|
||||
}
|
||||
|
||||
@@ -161,7 +161,7 @@ private sendDeveloperReq() {
|
||||
headers: [
|
||||
HOST: host
|
||||
],
|
||||
body: [devicetype: "$token-0", username: "$token-0"]], "${selectedHue}"))
|
||||
body: [devicetype: "$token-0"]], "${selectedHue}"))
|
||||
}
|
||||
|
||||
private discoverHueBulbs() {
|
||||
@@ -175,6 +175,7 @@ private discoverHueBulbs() {
|
||||
}
|
||||
|
||||
private verifyHueBridge(String deviceNetworkId, String host) {
|
||||
log.trace "Verify Hue Bridge $deviceNetworkId"
|
||||
sendHubCommand(new physicalgraph.device.HubAction([
|
||||
method: "GET",
|
||||
path: "/description.xml",
|
||||
@@ -602,6 +603,20 @@ def parse(childDevice, description) {
|
||||
}
|
||||
}
|
||||
|
||||
def hubVerification(bodytext) {
|
||||
log.trace "Bridge sent back description.xml for verification"
|
||||
def body = new XmlSlurper().parseText(bodytext)
|
||||
if (body?.device?.modelName?.text().startsWith("Philips hue bridge")) {
|
||||
def bridges = getHueBridges()
|
||||
def bridge = bridges.find {it?.key?.contains(body?.device?.UDN?.text())}
|
||||
if (bridge) {
|
||||
bridge.value << [name:body?.device?.friendlyName?.text(), serialNumber:body?.device?.serialNumber?.text(), verified: true]
|
||||
} else {
|
||||
log.error "/description.xml returned a bridge that didn't exist"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def on(childDevice) {
|
||||
log.debug "Executing 'on'"
|
||||
put("lights/${getId(childDevice)}/state", [on: true])
|
||||
@@ -642,35 +657,53 @@ def setColorTemperature(childDevice, huesettings) {
|
||||
}
|
||||
|
||||
def setColor(childDevice, huesettings) {
|
||||
log.debug "Executing 'setColor($huesettings)'"
|
||||
def hue = Math.min(Math.round(huesettings.hue * 65535 / 100), 65535)
|
||||
def sat = Math.min(Math.round(huesettings.saturation * 255 / 100), 255)
|
||||
def alert = huesettings.alert ? huesettings.alert : "none"
|
||||
def transition = huesettings.transition ? huesettings.transition : 4
|
||||
log.debug "Executing 'setColor($huesettings)'"
|
||||
|
||||
def value = [:]
|
||||
def hue = null
|
||||
def sat = null
|
||||
def xy = null
|
||||
|
||||
if (huesettings.hex != null) {
|
||||
value.xy = getHextoXY(huesettings.hex)
|
||||
} else {
|
||||
if (huesettings.hue != null)
|
||||
value.hue = Math.min(Math.round(huesettings.hue * 65535 / 100), 65535)
|
||||
if (huesettings.saturation != null)
|
||||
value.sat = Math.min(Math.round(huesettings.saturation * 255 / 100), 255)
|
||||
}
|
||||
|
||||
// Default behavior is to turn light on
|
||||
value.on = true
|
||||
|
||||
def value = [sat: sat, hue: hue, alert: alert, transitiontime: transition]
|
||||
if (huesettings.level != null) {
|
||||
if (huesettings.level == 1) value.bri = 1 else value.bri = Math.min(Math.round(huesettings.level * 255 / 100), 255)
|
||||
value.on = value.bri > 0
|
||||
}
|
||||
if (huesettings.level != null) {
|
||||
if (huesettings.level <= 0)
|
||||
value.on = false
|
||||
else if (huesettings.level == 1)
|
||||
value.bri = 1
|
||||
else
|
||||
value.bri = Math.min(Math.round(huesettings.level * 255 / 100), 255)
|
||||
}
|
||||
value.alert = huesettings.alert ? huesettings.alert : "none"
|
||||
value.transition = huesettings.transition ? huesettings.transition : 4
|
||||
|
||||
if (huesettings.switch) {
|
||||
value.on = huesettings.switch == "on"
|
||||
}
|
||||
// Make sure to turn off light if requested
|
||||
if (huesettings.switch == "off")
|
||||
value.on = false
|
||||
|
||||
log.debug "sending command $value"
|
||||
put("lights/${getId(childDevice)}/state", value)
|
||||
log.debug "sending command $value"
|
||||
put("lights/${getId(childDevice)}/state", value)
|
||||
return "Color set to $value"
|
||||
}
|
||||
|
||||
def nextLevel(childDevice) {
|
||||
def level = device.latestValue("level") as Integer ?: 0
|
||||
if (level < 100) {
|
||||
level = Math.min(25 * (Math.round(level / 25) + 1), 100) as Integer
|
||||
}
|
||||
else {
|
||||
level = 25
|
||||
}
|
||||
setLevel(childDevice,level)
|
||||
def level = device.latestValue("level") as Integer ?: 0
|
||||
if (level < 100) {
|
||||
level = Math.min(25 * (Math.round(level / 25) + 1), 100) as Integer
|
||||
} else {
|
||||
level = 25
|
||||
}
|
||||
setLevel(childDevice,level)
|
||||
}
|
||||
|
||||
private getId(childDevice) {
|
||||
@@ -743,6 +776,57 @@ private getBridgeIP() {
|
||||
return host
|
||||
}
|
||||
|
||||
private getHextoXY(String colorStr) {
|
||||
// For the hue bulb the corners of the triangle are:
|
||||
// -Red: 0.675, 0.322
|
||||
// -Green: 0.4091, 0.518
|
||||
// -Blue: 0.167, 0.04
|
||||
|
||||
def cred = Integer.valueOf( colorStr.substring( 1, 3 ), 16 )
|
||||
def cgreen = Integer.valueOf( colorStr.substring( 3, 5 ), 16 )
|
||||
def cblue = Integer.valueOf( colorStr.substring( 5, 7 ), 16 )
|
||||
|
||||
double[] normalizedToOne = new double[3];
|
||||
normalizedToOne[0] = (cred / 255);
|
||||
normalizedToOne[1] = (cgreen / 255);
|
||||
normalizedToOne[2] = (cblue / 255);
|
||||
float red, green, blue;
|
||||
|
||||
// Make red more vivid
|
||||
if (normalizedToOne[0] > 0.04045) {
|
||||
red = (float) Math.pow(
|
||||
(normalizedToOne[0] + 0.055) / (1.0 + 0.055), 2.4);
|
||||
} else {
|
||||
red = (float) (normalizedToOne[0] / 12.92);
|
||||
}
|
||||
|
||||
// Make green more vivid
|
||||
if (normalizedToOne[1] > 0.04045) {
|
||||
green = (float) Math.pow((normalizedToOne[1] + 0.055) / (1.0 + 0.055), 2.4);
|
||||
} else {
|
||||
green = (float) (normalizedToOne[1] / 12.92);
|
||||
}
|
||||
|
||||
// Make blue more vivid
|
||||
if (normalizedToOne[2] > 0.04045) {
|
||||
blue = (float) Math.pow((normalizedToOne[2] + 0.055) / (1.0 + 0.055), 2.4);
|
||||
} else {
|
||||
blue = (float) (normalizedToOne[2] / 12.92);
|
||||
}
|
||||
|
||||
float X = (float) (red * 0.649926 + green * 0.103455 + blue * 0.197109);
|
||||
float Y = (float) (red * 0.234327 + green * 0.743075 + blue * 0.022598);
|
||||
float Z = (float) (red * 0.0000000 + green * 0.053077 + blue * 1.035763);
|
||||
|
||||
float x = (X != 0 ? X / (X + Y + Z) : 0);
|
||||
float y = (Y != 0 ? Y / (X + Y + Z) : 0);
|
||||
|
||||
double[] xy = new double[2];
|
||||
xy[0] = x;
|
||||
xy[1] = y;
|
||||
return xy;
|
||||
}
|
||||
|
||||
private Integer convertHexToInt(hex) {
|
||||
Integer.parseInt(hex,16)
|
||||
}
|
||||
|
||||
@@ -1,31 +1,31 @@
|
||||
'''Acceleration Detected'''.ko=가속화 감지됨
|
||||
'''Arrival Of'''.ko=도착
|
||||
'''Both Push and SMS?'''.ko=푸시 메시지와 SMS를 모두 사용하시겠습니까?
|
||||
'''Button Pushed'''.ko=버튼이 눌렸습니다
|
||||
'''Contact Closes'''.ko=접점 닫힘
|
||||
'''Contact Opens'''.ko=접점 열림
|
||||
'''Departure Of'''.ko=출발
|
||||
'''Acceleration Detected'''.ko=가속이 감지되었을 때
|
||||
'''Arrival Of'''.ko=도착했을 때
|
||||
'''Both Push and SMS?'''.ko=푸시 알람과 SMS 모두 사용
|
||||
'''Button Pushed'''.ko=버튼이 눌렸을 때
|
||||
'''Contact Closes'''.ko=닫힘이 감지되었을 때
|
||||
'''Contact Opens'''.ko=열림이 감지되었을 때
|
||||
'''Departure Of'''.ko=출발할 때
|
||||
'''Message Text'''.ko=문자 메시지
|
||||
'''Minutes'''.ko=분
|
||||
'''Motion Here'''.ko=동작
|
||||
'''Phone Number (for SMS, optional)'''.ko=휴대전화 번호(문자 메시지 - 옵션)
|
||||
'''Minutes'''.ko=메시지 전송 간격(분)
|
||||
'''Motion Here'''.ko=움직임이 감지되었을 때
|
||||
'''Phone Number (for SMS, optional)'''.ko=전화번호 (옵션)
|
||||
'''Receive notifications when anything happens in your home.'''.ko=집 안에 무슨 일이 일어나면 알림이 전송됩니다.
|
||||
'''Smoke Detected'''.ko=연기가 감지되었습니다
|
||||
'''Switch Turned Off'''.ko=스위치 꺼짐
|
||||
'''Switch Turned On'''.ko=스위치 꺼짐
|
||||
'''Choose one or more, when...'''.ko=다음의 경우 하나 이상 선택
|
||||
'''Smoke Detected'''.ko=연기가 감지되었을 때
|
||||
'''Switch Turned Off'''.ko=스위치가 꺼졌을 때
|
||||
'''Switch Turned On'''.ko=스위치가 켜졌을 때
|
||||
'''Choose one or more, when...'''.ko=다음 상황 중 하나 이상 선택
|
||||
'''Yes'''.ko=예
|
||||
'''No'''.ko=아니요
|
||||
'''Send this message (optional, sends standard status message if not specified)'''.ko=이 메시지 전송(선택적, 지정되지 않은 경우 표준 상태 메시지를 보냅니다)
|
||||
'''Via a push notification and/or an SMS message'''.ko=푸시 알림 및/또는 문자 메시지를 통해
|
||||
'''Set for specific mode(s)'''.ko=특정 모드 설정
|
||||
'''Send this message (optional, sends standard status message if not specified)'''.ko=메시지 작성 (작성하지 않을 경우 디폴트 메시지 전송)
|
||||
'''Via a push notification and/or an SMS message'''.ko=푸시 알람 및 SMS 설정
|
||||
'''Set for specific mode(s)'''.ko=특정 상태 설정
|
||||
'''Tap to set'''.ko=눌러서 설정
|
||||
'''Minimum time between messages (optional, defaults to every message)'''.ko=메시지작 간 최소 시간(선택 사항, 모든 메시지의 기본 설정)
|
||||
'''If outside the US please make sure to enter the proper country code'''.ko=미국 이외 거주자는 적절한 국가 코드를 입력했는지 확인하십시오
|
||||
'''Water Sensor Wet'''.ko=Water Sensor에서 물이 감지되었습니다
|
||||
'''Minimum time between messages (optional, defaults to every message)'''.ko=메시지 전송 간격 설정
|
||||
'''If outside the US please make sure to enter the proper country code'''.ko=미국 이외 국가에 거주한다면 국가 코드와 함께 입력하여 주세요.
|
||||
'''Water Sensor Wet'''.ko=누수가 감지되었을 때
|
||||
'''{{ triggerEvent.linkText }} has arrived at the {{ location.name }}'''.ko={{ triggerEvent.linkText }}님이 {{ location.name }}에 도착했습니다
|
||||
'''{{ triggerEvent.linkText }} has arrived at {{ location.name }}'''.ko={{ triggerEvent.linkText }}님이 {{ location.name }}에 도착했습니다
|
||||
'''{{ triggerEvent.linkText }} has left the {{ location.name }}'''.ko={{ triggerEvent.linkText }}님이 {{ location.name }}을(를) 떠났습니다
|
||||
'''{{ triggerEvent.linkText }} has left {{ location.name }}'''.ko={{ triggerEvent.linkText }}님이 {{ location.name }}을(를) 떠났습니다
|
||||
'''Assign a name'''.ko=이름 배정
|
||||
'''Choose Modes'''.ko=모드 선택
|
||||
'''Assign a name'''.ko=이름 설정
|
||||
'''Choose Modes'''.ko=상태 선택
|
||||
|
||||
@@ -234,7 +234,7 @@ def addSwitches() {
|
||||
def d
|
||||
if (selectedSwitch) {
|
||||
d = getChildDevices()?.find {
|
||||
it.dni == selectedSwitch.value.mac || it.device.getDataValue("mac") == selectedSwitch.value.mac
|
||||
it.deviceNetworkId == selectedSwitch.value.mac || it.device.getDataValue("mac") == selectedSwitch.value.mac
|
||||
}
|
||||
}
|
||||
|
||||
@@ -265,7 +265,7 @@ def addMotions() {
|
||||
def d
|
||||
if (selectedMotion) {
|
||||
d = getChildDevices()?.find {
|
||||
it.dni == selectedMotion.value.mac || it.device.getDataValue("mac") == selectedMotion.value.mac
|
||||
it.deviceNetworkId == selectedMotion.value.mac || it.device.getDataValue("mac") == selectedMotion.value.mac
|
||||
}
|
||||
}
|
||||
|
||||
@@ -296,7 +296,7 @@ def addLightSwitches() {
|
||||
def d
|
||||
if (selectedLightSwitch) {
|
||||
d = getChildDevices()?.find {
|
||||
it.dni == selectedLightSwitch.value.mac || it.device.getDataValue("mac") == selectedLightSwitch.value.mac
|
||||
it.deviceNetworkId == selectedLightSwitch.value.mac || it.device.getDataValue("mac") == selectedLightSwitch.value.mac
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,17 @@
|
||||
/**
|
||||
* Vacation Lighting Director
|
||||
*
|
||||
* Version 2.4 - Added information paragraphs
|
||||
* Version 2.5 - Moved scheduling over to Cron and added time as a trigger.
|
||||
* Cleaned up formatting and some typos.
|
||||
* Updated license.
|
||||
* Made people option optional
|
||||
* Added sttement to unschedule on mode change if people option is not selected
|
||||
*
|
||||
* Version 2.4 - Added information paragraphs
|
||||
*
|
||||
* Source code can be found here: https://github.com/tslagle13/SmartThings/blob/master/smartapps/tslagle13/vacation-lighting-director.groovy
|
||||
*
|
||||
* Copyright 2015 Tim Slagle
|
||||
* Copyright 2016 Tim Slagle
|
||||
*
|
||||
* 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:
|
||||
@@ -51,8 +57,7 @@ def pageSetup() {
|
||||
return dynamicPage(pageProperties) {
|
||||
section(""){
|
||||
paragraph "This app can be used to make your home seem occupied anytime you are away from your home. " +
|
||||
"Please use each othe the sections below to setup the different preferences to your liking. " +
|
||||
"I recommend this app be used with at least two away modes. An example would be 'Away Day' 'and Away Night'. "
|
||||
"Please use each of the the sections below to setup the different preferences to your liking. "
|
||||
}
|
||||
section("Setup Menu") {
|
||||
href "Setup", title: "Setup", description: "", state:greyedOut()
|
||||
@@ -70,7 +75,7 @@ def Setup() {
|
||||
def newMode = [
|
||||
name: "newMode",
|
||||
type: "mode",
|
||||
title: "Which?",
|
||||
title: "Modes",
|
||||
multiple: true,
|
||||
required: true
|
||||
]
|
||||
@@ -96,14 +101,6 @@ def Setup() {
|
||||
required: true,
|
||||
]
|
||||
|
||||
def people = [
|
||||
name: "people",
|
||||
type: "capability.presenceSensor",
|
||||
title: "If these people are home do not change light status",
|
||||
required: true,
|
||||
multiple: true
|
||||
]
|
||||
|
||||
def pageName = "Setup"
|
||||
|
||||
def pageProperties = [
|
||||
@@ -116,10 +113,11 @@ def Setup() {
|
||||
|
||||
section(""){
|
||||
paragraph "In this section you need to setup the deatils of how you want your lighting to be affected while " +
|
||||
paragraph "you are away. All of these settings are required in order for the simulator to run correctly."
|
||||
"you are away. All of these settings are required in order for the simulator to run correctly."
|
||||
}
|
||||
section("Which mode change triggers the simulator? (This app will only run in selected mode(s))") {
|
||||
input newMode
|
||||
section("Simulator Triggers") {
|
||||
input newMode
|
||||
href "timeIntervalInput", title: "Times", description: timeIntervalLabel(), refreshAfterSelection:true
|
||||
}
|
||||
section("Light switches to turn on/off") {
|
||||
input switches
|
||||
@@ -130,9 +128,6 @@ def Setup() {
|
||||
section("Number of active lights at any given time") {
|
||||
input number_of_active_lights
|
||||
}
|
||||
section("People") {
|
||||
input people
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -162,18 +157,29 @@ def Settings() {
|
||||
title: "Settings",
|
||||
nextPage: "pageSetup"
|
||||
]
|
||||
|
||||
def people = [
|
||||
name: "people",
|
||||
type: "capability.presenceSensor",
|
||||
title: "If these people are home do not change light status",
|
||||
required: false,
|
||||
multiple: true
|
||||
]
|
||||
|
||||
return dynamicPage(pageProperties) {
|
||||
|
||||
section(""){
|
||||
paragraph "In this section you can restrict how your simulator runs. For instance you can restrict on which days it will run " +
|
||||
paragraph "as well as a delay for the simulator to start after it is in the correct mode. Delaying the simulator helps with false starts based on a incorrect mode change."
|
||||
"as well as a delay for the simulator to start after it is in the correct mode. Delaying the simulator helps with false starts based on a incorrect mode change."
|
||||
}
|
||||
section("Delay to start simulator") {
|
||||
input falseAlarmThreshold
|
||||
}
|
||||
section("People") {
|
||||
paragraph "Not using this setting may cause some lights to remain on when you arrive home"
|
||||
input people
|
||||
}
|
||||
section("More options") {
|
||||
href "timeIntervalInput", title: "Only during a certain time", description: getTimeLabel(starting, ending), state: greyedOutTime(starting, ending), refreshAfterSelection:true
|
||||
input days
|
||||
}
|
||||
}
|
||||
@@ -181,9 +187,24 @@ def Settings() {
|
||||
|
||||
page(name: "timeIntervalInput", title: "Only during a certain time", refreshAfterSelection:true) {
|
||||
section {
|
||||
input "starting", "time", title: "Starting", required: false
|
||||
input "ending", "time", title: "Ending", required: false
|
||||
input "startTimeType", "enum", title: "Starting at", options: [["time": "A specific time"], ["sunrise": "Sunrise"], ["sunset": "Sunset"]], defaultValue: "time", submitOnChange: true
|
||||
if (startTimeType in ["sunrise","sunset"]) {
|
||||
input "startTimeOffset", "number", title: "Offset in minutes (+/-)", range: "*..*", required: false
|
||||
}
|
||||
else {
|
||||
input "starting", "time", title: "Start time", required: false
|
||||
}
|
||||
}
|
||||
section {
|
||||
input "endTimeType", "enum", title: "Ending at", options: [["time": "A specific time"], ["sunrise": "Sunrise"], ["sunset": "Sunset"]], defaultValue: "time", submitOnChange: true
|
||||
if (endTimeType in ["sunrise","sunset"]) {
|
||||
input "endTimeOffset", "number", title: "Offset in minutes (+/-)", range: "*..*", required: false
|
||||
}
|
||||
else {
|
||||
input "ending", "time", title: "End time", required: false
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
def installed() {
|
||||
@@ -201,10 +222,13 @@ def initialize(){
|
||||
if (newMode != null) {
|
||||
subscribe(location, modeChangeHandler)
|
||||
}
|
||||
if (starting != null) {
|
||||
schedule(starting, modeChangeHandler)
|
||||
}
|
||||
log.debug "Installed with settings: ${settings}"
|
||||
}
|
||||
|
||||
def modeChangeHandler(evt) {
|
||||
log.debug "Mode change to: ${evt.value}"
|
||||
def delay = (falseAlarmThreshold != null && falseAlarmThreshold != "") ? falseAlarmThreshold * 60 : 2 * 60
|
||||
runIn(delay, scheduleCheck)
|
||||
}
|
||||
@@ -212,48 +236,54 @@ def modeChangeHandler(evt) {
|
||||
|
||||
//Main logic to pick a random set of lights from the large set of lights to turn on and then turn the rest off
|
||||
def scheduleCheck(evt) {
|
||||
if(allOk){
|
||||
log.debug("Running")
|
||||
// turn off all the switches
|
||||
switches.off()
|
||||
|
||||
// grab a random switch
|
||||
def random = new Random()
|
||||
def inactive_switches = switches
|
||||
for (int i = 0 ; i < number_of_active_lights ; i++) {
|
||||
// if there are no inactive switches to turn on then let's break
|
||||
if (inactive_switches.size() == 0){
|
||||
break
|
||||
if(allOk){
|
||||
log.debug("Running")
|
||||
// turn off all the switches
|
||||
switches.off()
|
||||
|
||||
// grab a random switch
|
||||
def random = new Random()
|
||||
def inactive_switches = switches
|
||||
for (int i = 0 ; i < number_of_active_lights ; i++) {
|
||||
// if there are no inactive switches to turn on then let's break
|
||||
if (inactive_switches.size() == 0){
|
||||
break
|
||||
}
|
||||
|
||||
// grab a random switch and turn it on
|
||||
def random_int = random.nextInt(inactive_switches.size())
|
||||
inactive_switches[random_int].on()
|
||||
|
||||
// then remove that switch from the pool off switches that can be turned on
|
||||
inactive_switches.remove(random_int)
|
||||
}
|
||||
|
||||
// re-run again when the frequency demands it
|
||||
schedule("0 0/${frequency_minutes} * 1/1 * ? *", scheduleCheck)
|
||||
}
|
||||
|
||||
// grab a random switch and turn it on
|
||||
def random_int = random.nextInt(inactive_switches.size())
|
||||
inactive_switches[random_int].on()
|
||||
|
||||
// then remove that switch from the pool off switches that can be turned on
|
||||
inactive_switches.remove(random_int)
|
||||
}
|
||||
|
||||
// re-run again when the frequency demands it
|
||||
runIn(frequency_minutes * 60, scheduleCheck)
|
||||
}
|
||||
//Check to see if mode is ok but not time/day. If mode is still ok, check again after frequency period.
|
||||
else if (modeOk) {
|
||||
log.debug("mode OK. Running again")
|
||||
runIn(frequency_minutes * 60, scheduleCheck)
|
||||
switches.off()
|
||||
}
|
||||
//if none is ok turn off frequency check and turn off lights.
|
||||
else if(people){
|
||||
//don't turn off lights if anyone is home
|
||||
if(someoneIsHome()){
|
||||
log.debug("Stopping Check for Light")
|
||||
//Check to see if mode is ok but not time/day. If mode is still ok, check again after frequency period.
|
||||
else if (modeOk) {
|
||||
log.debug("mode OK. Running again")
|
||||
switches.off()
|
||||
}
|
||||
//if none is ok turn off frequency check and turn off lights.
|
||||
else {
|
||||
if(people){
|
||||
//don't turn off lights if anyone is home
|
||||
if(someoneIsHome()){
|
||||
log.debug("Stopping Check for Light")
|
||||
unschedule()
|
||||
}
|
||||
else{
|
||||
log.debug("Stopping Check for Light and turning off all lights")
|
||||
switches.off()
|
||||
unschedule()
|
||||
}
|
||||
}
|
||||
else{
|
||||
log.debug("Stopping Check for Light and turning off all lights")
|
||||
switches.off()
|
||||
else if (!modeOk) {
|
||||
unschedule()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -286,26 +316,6 @@ private getDaysOk() {
|
||||
result
|
||||
}
|
||||
|
||||
private getTimeOk() {
|
||||
def result = true
|
||||
if (starting && ending) {
|
||||
def currTime = now()
|
||||
def start = timeToday(starting).time
|
||||
def stop = timeToday(ending).time
|
||||
result = start < stop ? currTime >= start && currTime <= stop : currTime <= stop || currTime >= start
|
||||
}
|
||||
|
||||
else if (starting){
|
||||
result = currTime >= start
|
||||
}
|
||||
else if (ending){
|
||||
result = currTime <= stop
|
||||
}
|
||||
|
||||
log.trace "timeOk = $result"
|
||||
result
|
||||
}
|
||||
|
||||
private getHomeIsEmpty() {
|
||||
def result = true
|
||||
|
||||
@@ -330,25 +340,59 @@ private getSomeoneIsHome() {
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
//gets the label for time restriction. Label phrasing changes depending on if there is both start and stop times or just one start/stop time.
|
||||
def getTimeLabel(starting, ending){
|
||||
|
||||
def timeLabel = "Tap to set"
|
||||
|
||||
if(starting && ending){
|
||||
timeLabel = "Between" + " " + hhmm(starting) + " " + "and" + " " + hhmm(ending)
|
||||
}
|
||||
else if (starting) {
|
||||
timeLabel = "Start at" + " " + hhmm(starting)
|
||||
}
|
||||
else if(ending){
|
||||
timeLabel = "End at" + hhmm(ending)
|
||||
}
|
||||
timeLabel
|
||||
private getTimeOk() {
|
||||
def result = true
|
||||
def start = timeWindowStart()
|
||||
def stop = timeWindowStop()
|
||||
if (start && stop && location.timeZone) {
|
||||
result = timeOfDayIsBetween(start, stop, new Date(), location.timeZone)
|
||||
}
|
||||
log.trace "timeOk = $result"
|
||||
result
|
||||
}
|
||||
|
||||
private timeWindowStart() {
|
||||
def result = null
|
||||
if (startTimeType == "sunrise") {
|
||||
result = location.currentState("sunriseTime")?.dateValue
|
||||
if (result && startTimeOffset) {
|
||||
result = new Date(result.time + Math.round(startTimeOffset * 60000))
|
||||
}
|
||||
}
|
||||
else if (startTimeType == "sunset") {
|
||||
result = location.currentState("sunsetTime")?.dateValue
|
||||
if (result && startTimeOffset) {
|
||||
result = new Date(result.time + Math.round(startTimeOffset * 60000))
|
||||
}
|
||||
}
|
||||
else if (starting && location.timeZone) {
|
||||
result = timeToday(starting, location.timeZone)
|
||||
}
|
||||
log.trace "timeWindowStart = ${result}"
|
||||
result
|
||||
}
|
||||
|
||||
private timeWindowStop() {
|
||||
def result = null
|
||||
if (endTimeType == "sunrise") {
|
||||
result = location.currentState("sunriseTime")?.dateValue
|
||||
if (result && endTimeOffset) {
|
||||
result = new Date(result.time + Math.round(endTimeOffset * 60000))
|
||||
}
|
||||
}
|
||||
else if (endTimeType == "sunset") {
|
||||
result = location.currentState("sunsetTime")?.dateValue
|
||||
if (result && endTimeOffset) {
|
||||
result = new Date(result.time + Math.round(endTimeOffset * 60000))
|
||||
}
|
||||
}
|
||||
else if (ending && location.timeZone) {
|
||||
result = timeToday(ending, location.timeZone)
|
||||
}
|
||||
log.trace "timeWindowStop = ${result}"
|
||||
result
|
||||
}
|
||||
|
||||
//fomrats time to readable format for time label
|
||||
private hhmm(time, fmt = "h:mm a")
|
||||
{
|
||||
def t = timeToday(time, location.timeZone)
|
||||
@@ -357,6 +401,41 @@ private hhmm(time, fmt = "h:mm a")
|
||||
f.format(t)
|
||||
}
|
||||
|
||||
private timeIntervalLabel() {
|
||||
def start = ""
|
||||
switch (startTimeType) {
|
||||
case "time":
|
||||
if (ending) {
|
||||
start += hhmm(starting)
|
||||
}
|
||||
break
|
||||
case "sunrise":
|
||||
case "sunset":
|
||||
start += startTimeType[0].toUpperCase() + startTimeType[1..-1]
|
||||
if (startTimeOffset) {
|
||||
start += startTimeOffset > 0 ? "+${startTimeOffset} min" : "${startTimeOffset} min"
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
def finish = ""
|
||||
switch (endTimeType) {
|
||||
case "time":
|
||||
if (ending) {
|
||||
finish += hhmm(ending)
|
||||
}
|
||||
break
|
||||
case "sunrise":
|
||||
case "sunset":
|
||||
finish += endTimeType[0].toUpperCase() + endTimeType[1..-1]
|
||||
if (endTimeOffset) {
|
||||
finish += endTimeOffset > 0 ? "+${endTimeOffset} min" : "${endTimeOffset} min"
|
||||
}
|
||||
break
|
||||
}
|
||||
start && finish ? "${start} to ${finish}" : ""
|
||||
}
|
||||
|
||||
//sets complete/not complete for the setup section on the main dynamic page
|
||||
def greyedOut(){
|
||||
def result = ""
|
||||
@@ -369,16 +448,7 @@ def greyedOut(){
|
||||
//sets complete/not complete for the settings section on the main dynamic page
|
||||
def greyedOutSettings(){
|
||||
def result = ""
|
||||
if (starting || ending || days || falseAlarmThreshold) {
|
||||
result = "complete"
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
//sets complete/not complete for time restriction section in settings
|
||||
def greyedOutTime(starting, ending){
|
||||
def result = ""
|
||||
if (starting || ending) {
|
||||
if (people || days || falseAlarmThreshold ) {
|
||||
result = "complete"
|
||||
}
|
||||
result
|
||||
|
||||
Reference in New Issue
Block a user