Compare commits

..

5 Commits

Author SHA1 Message Date
Vinay Rao
024a6cb698 Merge pull request #632 from SmartThingsCommunity/staging
Rolling up changes to production from staging 2016-03-17 Release
2016-03-18 10:43:22 -07:00
Vinay Rao
62a965d90b Merge pull request #599 from SmartThingsCommunity/staging
Rolling up changes to production from staging 2016-03-10 Release
2016-03-10 16:26:26 -08:00
Vinay Rao
515b268374 Merge pull request #549 from SmartThingsCommunity/staging
Rolling up changes to production from staging 2016-02-25 Release
2016-02-26 10:51:42 -08:00
Vinay Rao
a103d437c2 Merge pull request #523 from SmartThingsCommunity/staging
Deploy to production 2/18
2016-02-18 09:28:02 -08:00
Vinay Rao
bdd88deb99 Merge pull request #504 from SmartThingsCommunity/staging
Merging changes from staging to production
2016-02-11 22:40:38 -08:00
34 changed files with 583 additions and 1009 deletions

View File

@@ -52,9 +52,6 @@ hipchatShareFile {
hipchatSendNotification { hipchatSendNotification {
String branch = project.hasProperty('branch') ? project.property('branch') : 'unknown' String branch = project.hasProperty('branch') ? project.property('branch') : 'unknown'
message = "Began executable deploy of SmartThingsPublic(${branch})." message = "Began executable deploy of SmartThingsPublic(${branch})."
if (branch == 'master') {
message += ' (dev shards)'
}
color = branch == 'master' ? 'yellow' : 'red' color = branch == 'master' ? 'yellow' : 'red'
notify = true notify = true
} }

View File

@@ -11,17 +11,6 @@
* for the specific language governing permissions and limitations under the License. * for the specific language governing permissions and limitations under the License.
* *
*/ */
/*
* Purpose: Arrival Sensor HA DTH File
*
* Filename: Arrival-Sensor-HA.src/Arrival-Sensor-HA.groovy
*
* Change History:
* 1. 20160115 TW - Update/Edit to support i18n translations
* 2. 20160121 TW - Update to V4 battery calcs, added pref's page title translations
*/
metadata { metadata {
definition (name: "Arrival Sensor HA", namespace: "smartthings", author: "SmartThings") { definition (name: "Arrival Sensor HA", namespace: "smartthings", author: "SmartThings") {
capability "Tone" capability "Tone"
@@ -43,7 +32,7 @@ metadata {
]) ])
} }
section { section {
input "checkInterval", "enum", title: "Presence timeout (minutes)", description: "Tap to set", input "checkInterval", "enum", title: "Presence timeout (minutes)",
defaultValue:"2", options: ["2", "3", "5"], displayDuringSetup: false defaultValue:"2", options: ["2", "3", "5"], displayDuringSetup: false
} }
} }
@@ -93,6 +82,7 @@ def parse(String description) {
private handleReportAttributeMessage(String description) { private handleReportAttributeMessage(String description) {
def descMap = zigbee.parseDescriptionAsMap(description) def descMap = zigbee.parseDescriptionAsMap(description)
if (descMap.clusterInt == 0x0001 && descMap.attrInt == 0x0020) { if (descMap.clusterInt == 0x0001 && descMap.attrInt == 0x0020) {
handleBatteryEvent(Integer.parseInt(descMap.value, 16)) handleBatteryEvent(Integer.parseInt(descMap.value, 16))
} }
@@ -104,7 +94,6 @@ private handleReportAttributeMessage(String description) {
* @param volts Battery voltage in .1V increments * @param volts Battery voltage in .1V increments
*/ */
private handleBatteryEvent(volts) { private handleBatteryEvent(volts) {
def descriptionText
if (volts == 0 || volts == 255) { if (volts == 0 || volts == 255) {
log.debug "Ignoring invalid value for voltage (${volts/10}V)" log.debug "Ignoring invalid value for voltage (${volts/10}V)"
} }
@@ -118,17 +107,15 @@ private handleBatteryEvent(volts) {
volts = minVolts volts = minVolts
else if (volts > maxVolts) else if (volts > maxVolts)
volts = maxVolts volts = maxVolts
def value = batteryMap[volts] def pct = batteryMap[volts]
if (value != null) { if (pct != null) {
def linkText = getLinkText(device) def linkText = getLinkText(device)
descriptionText = '{{ linkText }} battery was {{ value }}'
def eventMap = [ def eventMap = [
name: 'battery', name: 'battery',
value: value, value: pct,
descriptionText: descriptionText, descriptionText: "${linkText} battery was ${pct}%"
translatable: true
] ]
log.debug "Creating battery event for voltage=${volts/10}V: ${linkText} ${eventMap.name} is ${eventMap.value}%" log.debug "Creating battery event for voltage=${volts/10}V: ${eventMap}"
sendEvent(eventMap) sendEvent(eventMap)
} }
} }
@@ -144,19 +131,13 @@ private handlePresenceEvent(present) {
stopTimer() stopTimer()
} }
def linkText = getLinkText(device) def linkText = getLinkText(device)
def descriptionText
if ( present )
descriptionText = "{{ linkText }} has arrived"
else
descriptionText = "{{ linkText }} has left"
def eventMap = [ def eventMap = [
name: "presence", name: "presence",
value: present ? "present" : "not present", value: present ? "present" : "not present",
linkText: linkText, linkText: linkText,
descriptionText: descriptionText, descriptionText: "${linkText} has ${present ? 'arrived' : 'left'}",
translatable: true
] ]
log.debug "Creating presence event: ${device.displayName} ${eventMap.name} is ${eventMap.value}" log.debug "Creating presence event: ${eventMap}"
sendEvent(eventMap) sendEvent(eventMap)
} }
@@ -177,4 +158,4 @@ def checkPresenceCallback() {
if (timeSinceLastCheckin >= theCheckInterval) { if (timeSinceLastCheckin >= theCheckInterval) {
handlePresenceEvent(false) handlePresenceEvent(false)
} }
} }

View File

@@ -1,35 +0,0 @@
#==============================================================================
# Copyright 2016 SmartThings
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
# use this file except in compliance with the License. You may obtain a copy
# of the License at:
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#==============================================================================
# Purpose: Arrival Sensor HA i18n Translation File
#
# Filename: Arrival-Sensor-HA.src/i18n/messages.properties
#
# Change History:
# 1. 20160115 TW Initial release with informal Korean translation.
# 2. 20160121 TW Added def preference section titles.
#==============================================================================
# Korean (ko)
# Device Preferences
'''Give your device a name'''.ko=기기 이름 바꾸기
'''Set Device Image'''.ko=기기 이미지 설정
'''Presence timeout (minutes)'''.ko=시간 초과. 스마트폰 위치 정보
'''Tap to set'''.ko=눌러서 설정
'''Arrival Sensor'''.ko=도착알림 센서
# Events / Notifications
'''{{ linkText }} battery was {{ value }}'''.ko={{ linkText }}남아있는 배터리는 {{ value }}입니다.
'''{{ linkText }} has arrived'''.ko={{ linkText }}집에 도착했습니다.
'''{{ linkText }} has left'''.ko={{ linkText }}집을 나갔습니다.
#==============================================================================

View File

@@ -20,6 +20,7 @@ metadata {
capability "Temperature Measurement" capability "Temperature Measurement"
capability "Motion Sensor" capability "Motion Sensor"
capability "Refresh" capability "Refresh"
capability "Polling"
} }
tiles { tiles {
@@ -67,6 +68,6 @@ def refresh() {
void poll() { void poll() {
log.debug "Executing 'poll' using parent SmartApp" log.debug "Executing 'poll' using parent SmartApp"
parent.pollChild() parent.pollChild(this)
} }

View File

@@ -20,6 +20,7 @@ metadata {
capability "Actuator" capability "Actuator"
capability "Thermostat" capability "Thermostat"
capability "Temperature Measurement" capability "Temperature Measurement"
capability "Polling"
capability "Sensor" capability "Sensor"
capability "Refresh" capability "Refresh"
capability "Relative Humidity Measurement" capability "Relative Humidity Measurement"
@@ -133,7 +134,9 @@ def refresh() {
void poll() { void poll() {
log.debug "Executing 'poll' using parent SmartApp" log.debug "Executing 'poll' using parent SmartApp"
parent.pollChild()
def results = parent.pollChild(this)
generateEvent(results) //parse received message from parent
} }
def generateEvent(Map results) { def generateEvent(Map results) {
@@ -379,16 +382,12 @@ def getDataByName(String name) {
state[name] ?: device.getDataValue(name) state[name] ?: device.getDataValue(name)
} }
def setThermostatMode(String mode) { def setThermostatMode(String value) {
log.debug "setThermostatMode($mode)" log.debug "setThermostatMode({$value})"
mode = mode.toLowerCase()
switchToMode(mode)
} }
def setThermostatFanMode(String mode) { def setThermostatFanMode(String value) {
log.debug "setThermostatFanMode($mode)" log.debug "setThermostatFanMode({$value})"
mode = mode.toLowerCase()
switchToFanMode(mode)
} }
def generateModeEvent(mode) { def generateModeEvent(mode) {

View File

@@ -17,13 +17,16 @@ metadata {
tiles(scale: 2) { tiles(scale: 2) {
multiAttributeTile(name:"rich-control"){ multiAttributeTile(name:"rich-control"){
tileAttribute ("", key: "PRIMARY_CONTROL") { tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
attributeState "default", label: "Hue Bridge", action: "", icon: "st.Lighting.light99-hue", backgroundColor: "#F3C200" attributeState "default", label: "Hue Bridge", action: "", icon: "st.Lighting.light99-hue", backgroundColor: "#F3C200"
} }
tileAttribute ("serialNumber", key: "SECONDARY_CONTROL") { tileAttribute ("serialNumber", key: "SECONDARY_CONTROL") {
attributeState "default", label:'SN: ${currentValue}' attributeState "default", label:'SN: ${currentValue}'
} }
} }
standardTile("icon", "icon", width: 1, height: 1, canChangeIcon: false, inactiveLabel: true, canChangeBackground: false) {
state "default", label: "Hue Bridge", action: "", icon: "st.Lighting.light99-hue", backgroundColor: "#FFFFFF"
}
valueTile("serialNumber", "device.serialNumber", decoration: "flat", height: 1, width: 2, inactiveLabel: false) { valueTile("serialNumber", "device.serialNumber", decoration: "flat", height: 1, width: 2, inactiveLabel: false) {
state "default", label:'SN: ${currentValue}' state "default", label:'SN: ${currentValue}'
} }
@@ -31,7 +34,7 @@ metadata {
state "default", label:'${currentValue}', height: 1, width: 2, inactiveLabel: false state "default", label:'${currentValue}', height: 1, width: 2, inactiveLabel: false
} }
main (["rich-control"]) main (["icon"])
details(["rich-control", "networkAddress"]) details(["rich-control", "networkAddress"])
} }
} }
@@ -72,7 +75,6 @@ def parse(description) {
} }
else if (contentType?.contains("xml")) { else if (contentType?.contains("xml")) {
log.debug "HUE BRIDGE ALREADY PRESENT" log.debug "HUE BRIDGE ALREADY PRESENT"
parent.hubVerification(device.hub.id, msg.body)
} }
} }
} }

View File

@@ -3,7 +3,6 @@
* *
* Author: SmartThings * Author: SmartThings
*/ */
// for the UI // for the UI
metadata { metadata {
// Automatically generated. Make future change here. // Automatically generated. Make future change here.
@@ -28,10 +27,10 @@ metadata {
tiles (scale: 2){ tiles (scale: 2){
multiAttributeTile(name:"rich-control", type: "lighting", width: 6, height: 4, canChangeIcon: true){ multiAttributeTile(name:"rich-control", type: "lighting", width: 6, height: 4, canChangeIcon: true){
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") { tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
attributeState "on", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#00A0DC", nextState:"turningOff" attributeState "on", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff"
attributeState "off", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#C6C7CC", nextState:"turningOn" attributeState "off", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#00A0DC", nextState:"turningOff" attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff"
attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#C6C7CC", nextState:"turningOn" attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
} }
tileAttribute ("device.level", key: "SLIDER_CONTROL") { tileAttribute ("device.level", key: "SLIDER_CONTROL") {
attributeState "level", action:"switch level.setLevel", range:"(0..100)" attributeState "level", action:"switch level.setLevel", range:"(0..100)"
@@ -44,10 +43,16 @@ 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)") { controlTile("colorTempSliderControl", "device.colorTemperature", "slider", width: 4, height: 2, inactiveLabel: false, range:"(2000..6500)") {
state "colorTemperature", action:"color temperature.setColorTemperature" state "colorTemperature", action:"color temperature.setColorTemperature"
} }
valueTile("colorTemp", "device.colorTemperature", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { valueTile("colorTemp", "device.colorTemperature", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
state "colorTemperature", label: '${currentValue} K' state "colorTemperature", label: '${currentValue} K'
} }
@@ -55,12 +60,29 @@ metadata {
standardTile("reset", "device.reset", height: 2, width: 2, inactiveLabel: false, decoration: "flat") { standardTile("reset", "device.reset", height: 2, width: 2, inactiveLabel: false, decoration: "flat") {
state "default", label:"Reset Color", action:"reset", icon:"st.lights.philips.hue-single" state "default", label:"Reset Color", action:"reset", icon:"st.lights.philips.hue-single"
} }
standardTile("refresh", "device.switch", 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" 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(["rich-control"]) main(["switch"])
details(["rich-control", "colorTempSliderControl", "colorTemp", "reset", "refresh"]) details(["rich-control", "colorTempSliderControl", "colorTemp", "reset", "refresh"])
} }
} }
@@ -103,104 +125,62 @@ void nextLevel() {
} }
void setLevel(percent) { void setLevel(percent) {
log.debug "Executing 'setLevel'" log.debug "Executing 'setLevel'"
if (verifyPercent(percent)) { parent.setLevel(this, percent)
parent.setLevel(this, percent) sendEvent(name: "level", value: percent)
sendEvent(name: "level", value: percent, descriptionText: "Level has changed to ${percent}%")
sendEvent(name: "switch", value: "on")
}
} }
void setSaturation(percent) { void setSaturation(percent) {
log.debug "Executing 'setSaturation'" log.debug "Executing 'setSaturation'"
if (verifyPercent(percent)) { parent.setSaturation(this, percent)
parent.setSaturation(this, percent) sendEvent(name: "saturation", value: percent)
sendEvent(name: "saturation", value: percent, displayed: false)
}
} }
void setHue(percent) { void setHue(percent) {
log.debug "Executing 'setHue'" log.debug "Executing 'setHue'"
if (verifyPercent(percent)) { parent.setHue(this, percent)
parent.setHue(this, percent) sendEvent(name: "hue", value: percent)
sendEvent(name: "hue", value: percent, displayed: false)
}
} }
void setColor(value) { void setColor(value) {
log.debug "setColor: ${value}, $this" log.debug "setColor: ${value}, $this"
def events = [] parent.setColor(this, value)
def validValues = [:] if (value.hue) { sendEvent(name: "hue", value: value.hue)}
if (value.saturation) { sendEvent(name: "saturation", value: value.saturation)}
if (verifyPercent(value.hue)) { if (value.hex) { sendEvent(name: "color", value: value.hex)}
events << createEvent(name: "hue", value: value.hue, displayed: false) if (value.level) { sendEvent(name: "level", value: value.level)}
validValues.hue = value.hue if (value.switch) { sendEvent(name: "switch", value: value.switch)}
}
if (verifyPercent(value.saturation)) {
events << createEvent(name: "saturation", value: value.saturation, displayed: false)
validValues.saturation = value.saturation
}
if (value.hex != null) {
if (value.hex ==~ /^\#([A-Fa-f0-9]){6}$/) {
events << createEvent(name: "color", value: value.hex)
validValues.hex = value.hex
} else {
log.warn "$value.hex is not a valid color"
}
}
if (verifyPercent(value.level)) {
events << createEvent(name: "level", value: value.level, descriptionText: "Level has changed to ${value.level}%")
validValues.level = value.level
}
if (value.switch == "off" || (value.level != null && value.level <= 0)) {
events << createEvent(name: "switch", value: "off")
validValues.switch = "off"
} else {
events << createEvent(name: "switch", value: "on")
validValues.switch = "on"
}
if (!events.isEmpty()) {
parent.setColor(this, validValues)
}
events.each {
sendEvent(it)
}
} }
void reset() { void reset() {
log.debug "Executing 'reset'" log.debug "Executing 'reset'"
def value = [level:100, saturation:56, hue:23] def value = [level:100, hex:"#90C638", saturation:56, hue:23]
setAdjustedColor(value) setAdjustedColor(value)
parent.poll() parent.poll()
} }
void setAdjustedColor(value) { void setAdjustedColor(value) {
if (value) { if (value) {
log.trace "setAdjustedColor: ${value}" log.trace "setAdjustedColor: ${value}"
def adjusted = value + [:] def adjusted = value + [:]
adjusted.hue = adjustOutgoingHue(value.hue) adjusted.hue = adjustOutgoingHue(value.hue)
// Needed because color picker always sends 100 // Needed because color picker always sends 100
adjusted.level = null adjusted.level = null
setColor(adjusted) setColor(adjusted)
} else {
log.warn "Invalid color input"
} }
} }
void setColorTemperature(value) { void setColorTemperature(value) {
if (value) { if (value) {
log.trace "setColorTemperature: ${value}k" log.trace "setColorTemperature: ${value}k"
parent.setColorTemperature(this, value) parent.setColorTemperature(this, value)
sendEvent(name: "colorTemperature", value: value) sendEvent(name: "colorTemperature", value: value)
sendEvent(name: "switch", value: "on") }
} else {
log.warn "Invalid color temperature"
}
} }
void refresh() { void refresh() {
log.debug "Executing 'refresh'" log.debug "Executing 'refresh'"
parent.manualRefresh() parent.manualRefresh()
} }
def adjustOutgoingHue(percent) { def adjustOutgoingHue(percent) {
@@ -219,14 +199,3 @@ def adjustOutgoingHue(percent) {
log.info "percent: $percent, adjusted: $adjusted" log.info "percent: $percent, adjusted: $adjusted"
adjusted adjusted
} }
def verifyPercent(percent) {
if (percent == null)
return false
else if (percent >= 0 && percent <= 100) {
return true
} else {
log.warn "$percent is not 0-100"
return false
}
}

View File

@@ -36,6 +36,13 @@ 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)") { controlTile("levelSliderControl", "device.level", "slider", height: 1, width: 2, inactiveLabel: false, range:"(0..100)") {
state "level", action:"switch level.setLevel" state "level", action:"switch level.setLevel"
} }
@@ -44,7 +51,7 @@ metadata {
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh" state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
} }
main(["rich-control"]) main(["switch"])
details(["rich-control", "refresh"]) details(["rich-control", "refresh"])
} }
} }
@@ -79,12 +86,8 @@ void off() {
void setLevel(percent) { void setLevel(percent) {
log.debug "Executing 'setLevel'" log.debug "Executing 'setLevel'"
if (percent != null && percent >= 0 && percent <= 100) { parent.setLevel(this, percent)
parent.setLevel(this, percent) sendEvent(name: "level", value: percent)
sendEvent(name: "level", value: percent)
} else {
log.warn "$percent is not 0-100"
}
} }
void refresh() { void refresh() {

View File

@@ -19,14 +19,13 @@
# #
# Change History: # Change History:
# 1. 20160205 TW Initial release with informal Korean translation. # 1. 20160205 TW Initial release with informal Korean translation.
# 2. 20160224 TW Updated with formal Korean translation.
#============================================================================== #==============================================================================
# Korean (ko) # Korean (ko)
# Device Preferences # Device Preferences
'''Give your device a name'''.ko=기기 이름 바꾸기 '''Give your device a name'''.ko=기기 이름 바꾸기
'''Set Device Image'''.ko=기기 이미지 설정 '''Set Device Image'''.ko=디바이스 이미지 설정
# Events / Notifications # Events / Notifications
'''{{ linkText }} has left'''.ko={{ linkText }}집을 나갔습니다. '''{{ linkText }} has left'''.ko={{ linkText }}님이 나갔습니다
'''{{ linkText }} has arrived'''.ko={{ linkText }}집에 도착했습니다. '''{{ linkText }} has arrived'''.ko={{ linkText }}님이 도착했습니다
'''present'''.ko=집안 '''present'''.ko=집안
'''not present'''.ko=외출 '''not present'''.ko=부재중

View File

@@ -1,28 +1,16 @@
/* /**
=============================================================================== * Copyright 2015 SmartThings
* Copyright 2016 SmartThings
* *
* Licensed under the Apache License, Version 2.0 (the "License"); you may not * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* use this file except in compliance with the License. You may obtain a copy * in compliance with the License. You may obtain a copy of the License at:
* of the License at:
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * for the specific language governing permissions and limitations under the License.
* License for the specific language governing permissions and limitations
* under the License.
===============================================================================
* Purpose: Mobile Presence DTH File
* *
* Filename: mobile-presence.src/mobile-presence.groovy
*
* Change History:
* 1. 20160205 TW - Update/Edit to support i18n translations
===============================================================================
*/ */
metadata { metadata {
definition (name: "Mobile Presence", namespace: "smartthings", author: "SmartThings") { definition (name: "Mobile Presence", namespace: "smartthings", author: "SmartThings") {
capability "Presence Sensor" capability "Presence Sensor"
@@ -53,7 +41,6 @@ def parse(String description) {
def isStateChange = isStateChange(device, name, value) def isStateChange = isStateChange(device, name, value)
def results = [ def results = [
translatable: true,
name: name, name: name,
value: value, value: value,
unit: null, unit: null,
@@ -85,8 +72,8 @@ private String parseValue(String description) {
private parseDescriptionText(String linkText, String value, String description) { private parseDescriptionText(String linkText, String value, String description) {
switch(value) { switch(value) {
case "present": return "{{ linkText }} has arrived" case "present": return "$linkText has arrived"
case "not present": return "{{ linkText }} has left" case "not present": return "$linkText has left"
default: return value default: return value
} }
} }
@@ -97,4 +84,4 @@ private getState(String value) {
case "not present": return "left" case "not present": return "left"
default: return value default: return value
} }
} }

View File

@@ -1,31 +0,0 @@
#==============================================================================
# Copyright 2016 SmartThings
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
# use this file except in compliance with the License. You may obtain a copy
# of the License at:
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#==============================================================================
# Purpose: SmartPower Outlet i18n Translation File
#
# Filename: SmartPower-Outlet.src/i18n/messages.properties
#
# Change History:
# 1. 20160116 TW Initial release with informal Korean translation.
#==============================================================================
# Korean (ko)
# Device Preferences
'''Give your device a name'''.ko=기기 이름 바꾸기
'''Outlet'''.ko=플러그
# Events descriptionText
'''{{ device.displayName }} is On'''.ko={{ device.displayName }}켜졌습니다.
'''{{ device.displayName }} is Off'''.ko={{ device.displayName }}꺼졌습니다.
'''{{ device.displayName }} power is {{ value }} Watts'''.ko={{ device.displayName }} 전원은 {{ value }}와트입니다
#==============================================================================

View File

@@ -1,26 +1,19 @@
/* /**
=============================================================================== * Copyright 2015 SmartThings
* Copyright 2016 SmartThings
* *
* Licensed under the Apache License, Version 2.0 (the "License"); you may not * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* use this file except in compliance with the License. You may obtain a copy * in compliance with the License. You may obtain a copy of the License at:
* of the License at:
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * for the specific language governing permissions and limitations under the License.
* License for the specific language governing permissions and limitations
* under the License.
===============================================================================
* Purpose: SmartPower Outlet DTH File
* *
* Filename: SmartPower-Outlet.src/SmartPower-Outlet.groovy * SmartPower Outlet (CentraLite)
* *
* Change History: * Author: SmartThings
* 1. 20160117 TW - Update/Edit to support i18n translations * Date: 2015-08-23
===============================================================================
*/ */
metadata { metadata {
// Automatically generated. Make future change here. // Automatically generated. Make future change here.
@@ -98,22 +91,22 @@ def parse(String description) {
finalResult = getPowerDescription(zigbee.parseDescriptionAsMap(description)) finalResult = getPowerDescription(zigbee.parseDescriptionAsMap(description))
if (finalResult) { if (finalResult) {
log.info "final result = $finalResult" log.info finalResult
if (finalResult.type == "update") { if (finalResult.type == "update") {
log.info "$device updates: ${finalResult.value}" log.info "$device updates: ${finalResult.value}"
} }
else if (finalResult.type == "power") { else if (finalResult.type == "power") {
def powerValue = (finalResult.value as Integer)/10 def powerValue = (finalResult.value as Integer)/10
sendEvent(name: "power", value: powerValue, descriptionText: '{{ device.displayName }} power is {{ value }} Watts', translatable: true ) sendEvent(name: "power", value: powerValue)
/* /*
Dividing by 10 as the Divisor is 10000 and unit is kW for the device. AttrId: 0302 and 0300. Simplifying to 10 Dividing by 10 as the Divisor is 10000 and unit is kW for the device. AttrId: 0302 and 0300. Simplifying to 10
power level is an integer. The exact power level with correct units needs to be handled in the device type power level is an integer. The exact power level with correct units needs to be handled in the device type
to account for the different Divisor value (AttrId: 0302) and POWER Unit (AttrId: 0300). CLUSTER for simple metering is 0702 to account for the different Divisor value (AttrId: 0302) and POWER Unit (AttrId: 0300). CLUSTER for simple metering is 0702
*/ */
} }
else { else {
def descriptionText = finalResult.value == "on" ? '{{ device.displayName }} is On' : '{{ device.displayName }} is Off' sendEvent(name: finalResult.type, value: finalResult.value)
sendEvent(name: finalResult.type, value: finalResult.value, descriptionText: descriptionText, translatable: true)
} }
} }
else { else {

View File

@@ -1,41 +1,14 @@
#==============================================================================
# Copyright 2016 SmartThings # Generated on Wed Feb 24 14:28:26 CST 2016 by dylan
# '''Adjust temperature by this many degrees'''.ko=몇 도씩 온도를 조절하십시오
# Licensed under the Apache License, Version 2.0 (the "License"); you may not '''Degrees'''.ko=온도
# use this file except in compliance with the License. You may obtain a copy '''Temperature Offset'''.ko=온도 직접 설정
# of the License at: '''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=배터리
# 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=건조 '''dry'''.ko=건조
'''wet'''.ko=누수 '''wet'''.ko=누수
'''battery'''.ko=배터리 '''{{ device.displayName }} battery has too much power: (> 3.5) volts.'''.ko={{ device.displayName }} 배터리 전력이 너무 높습니다(3.5볼트 초과).
'''Temperature Offset'''.ko=온도 직접 설정 '''{{ device.displayName }} battery was {{ value }}'''.ko={{ device.displayName }} 배터리가 {{ value }}였습니다
'''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=기준 온도를 원하는대로 몇 도 올리거나 내려서 설정할 수 있습니다. '''{{ device.displayName }} is {{ value | translate }}'''.ko={{ device.displayName }}이(가) {{ value | translate }}입니다
'''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 }}°C'''.ko={{ device.displayName }}이(가) {{ value }}°C였습니다
'''{{ device.displayName }} was {{ value }}°F'''.ko={{ device.displayName }}이(가) {{ value }}°F였습니다 '''{{ 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 }}%입니다.
#==============================================================================

View File

@@ -1,29 +1,18 @@
/* /**
=============================================================================== * SmartSense Moisture Sensor
* Copyright 2016 SmartThings
* *
* Licensed under the Apache License, Version 2.0 (the "License"); you may not * Copyright 2014 SmartThings
* use this file except in compliance with the License. You may obtain a copy *
* of the License at: * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at:
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * for the specific language governing permissions and limitations under the License.
* License for the specific language governing permissions and limitations
* under the License.
===============================================================================
* Purpose: SmartSense Moisture Sensor DTH File
* *
* Filename: SmartSense-Moisture-Sensor.src/SmartSense-Moisture-Sensor.groovy
*
* Change History:
* 1. 20160116 TW - Update/Edit to support i18n translations
* 2. 20160125 TW = Incorporated new battery mapping from TM
===============================================================================
*/ */
metadata { metadata {
definition (name: "SmartSense Moisture Sensor",namespace: "smartthings", author: "SmartThings") { definition (name: "SmartSense Moisture Sensor",namespace: "smartthings", author: "SmartThings") {
capability "Configuration" capability "Configuration"
@@ -54,7 +43,7 @@ metadata {
]) ])
} }
section { section {
input title: "Temperature Offset", description: "This feature allows you to correct any temperature variations by selecting an offset. Ex: If your sensor consistently reports a temp that's 5 degrees too warm, you'd enter '-5'. If 3 degrees too cold, enter '+3'.", displayDuringSetup: false, type: "paragraph", element: "paragraph" input title: "Temperature Offset", description: "This feature allows you to correct any temperature variations by selecting an offset. Ex: If your sensor consistently reports a temp that's 5 degrees too warm, you'd enter \"-5\". If 3 degrees too cold, enter \"+3\".", displayDuringSetup: false, type: "paragraph", element: "paragraph"
input "tempOffset", "number", title: "Degrees", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false input "tempOffset", "number", title: "Degrees", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false
} }
} }
@@ -127,16 +116,16 @@ private Map parseCatchAllMessage(String description) {
resultMap = getBatteryResult(cluster.data.last()) resultMap = getBatteryResult(cluster.data.last())
break break
case 0x0402: case 0x0402:
// temp is last 2 data values. reverse to swap endian // temp is last 2 data values. reverse to swap endian
String temp = cluster.data[-2..-1].reverse().collect { cluster.hex1(it) }.join() String temp = cluster.data[-2..-1].reverse().collect { cluster.hex1(it) }.join()
def value = getTemperature(temp) def value = getTemperature(temp)
resultMap = getTemperatureResult(value) resultMap = getTemperatureResult(value)
break break
} }
} }
return resultMap return resultMap
} }
private boolean shouldProcessMessage(cluster) { private boolean shouldProcessMessage(cluster) {
@@ -231,8 +220,7 @@ private Map getBatteryResult(rawValue) {
def result = [ def result = [
name: 'battery', name: 'battery',
value: '--', value: '--'
translatable: true
] ]
def volts = rawValue / 10 def volts = rawValue / 10
@@ -240,7 +228,7 @@ private Map getBatteryResult(rawValue) {
if (rawValue == 0 || rawValue == 255) {} if (rawValue == 0 || rawValue == 255) {}
else { else {
if (volts > 3.5) { if (volts > 3.5) {
result.descriptionText = "{{ device.displayName }} battery has too much power: (> 3.5) volts." result.descriptionText = "${linkText} battery has too much power (${volts} volts)."
} }
else { else {
if (device.getDataValue("manufacturer") == "SmartThings") { if (device.getDataValue("manufacturer") == "SmartThings") {
@@ -257,7 +245,7 @@ private Map getBatteryResult(rawValue) {
def pct = batteryMap[volts] def pct = batteryMap[volts]
if (pct != null) { if (pct != null) {
result.value = pct result.value = pct
result.descriptionText = "{{ device.displayName }} battery was {{ value }}%" result.descriptionText = "${linkText} battery was ${result.value}%"
} }
} }
else { else {
@@ -265,7 +253,7 @@ private Map getBatteryResult(rawValue) {
def maxVolts = 3.0 def maxVolts = 3.0
def pct = (volts - minVolts) / (maxVolts - minVolts) def pct = (volts - minVolts) / (maxVolts - minVolts)
result.value = Math.min(100, (int) pct * 100) result.value = Math.min(100, (int) pct * 100)
result.descriptionText = "{{ device.displayName }} battery was {{ value }}%" result.descriptionText = "${linkText} battery was ${result.value}%"
} }
} }
} }
@@ -275,37 +263,27 @@ private Map getBatteryResult(rawValue) {
private Map getTemperatureResult(value) { private Map getTemperatureResult(value) {
log.debug 'TEMP' log.debug 'TEMP'
def linkText = getLinkText(device)
if (tempOffset) { if (tempOffset) {
def offset = tempOffset as int def offset = tempOffset as int
def v = value as int def v = value as int
value = v + offset value = v + offset
} }
def descriptionText def descriptionText = "${linkText} was ${value}°${temperatureScale}"
if ( temperatureScale == 'C' )
descriptionText = '{{ device.displayName }} was {{ value }}°C'
else
descriptionText = '{{ device.displayName }} was {{ value }}°F'
return [ return [
name: 'temperature', name: 'temperature',
value: value, value: value,
descriptionText: descriptionText, descriptionText: descriptionText
translatable: true
] ]
} }
private Map getMoistureResult(value) { private Map getMoistureResult(value) {
log.debug "water" log.debug 'water'
def descriptionText String descriptionText = "${device.displayName} is ${value}"
if ( value == "wet" )
descriptionText = '{{ device.displayName }} is wet'
else
descriptionText = '{{ device.displayName }} is dry'
return [ return [
name: 'water', name: 'water',
value: value, value: value,
descriptionText: descriptionText, descriptionText: descriptionText
translatable: true
] ]
} }

View File

@@ -1,40 +1,13 @@
#==============================================================================
# Copyright 2016 SmartThings # Generated on Wed Feb 24 14:28:26 CST 2016 by dylan
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
# use this file except in compliance with the License. You may obtain a copy
# of the License at:
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#==============================================================================
# Purpose: SmartSense Motion Sensor i18n Translation File
#
# Filename: SmartSense-Motion-Sensor.src/i18n/messages.properties
#
# Change History:
# 1. 20160116 TW Initial release with formal Korean translation.
# 2. 20160224 TW Updated formal Korean translations from Mike Stoller.
#==============================================================================
# Korean (ko)
# Device Preferences
'''battery'''.ko=배터리
'''Temperature Offset'''.ko=온도 직접 설정
'''This feature allows you to correct any temperature variations by selecting an offset. Ex: If your sensor consistently reports a temp that's 5 degrees too warm, you'd enter '-5'. If 3 degrees too cold, enter '+3'.'''.ko=기준 온도를 원하는대로 몇 도 올리거나 내려서 설정할 수 있습니다.
'''Degrees'''.ko=온도
'''Adjust temperature by this many degrees'''.ko=몇 도씩 온도를 조절하십시오 '''Adjust temperature by this many degrees'''.ko=몇 도씩 온도를 조절하십시오
'''Give your device a name'''.ko=기기 이름 바꾸기 '''Degrees'''.ko=온도
'''Motion Sensor'''.ko=모션 센서 '''Temperature Offset'''.ko=온도 직접 설정
# Events descriptionText '''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=기준 온도를 원하는대로 몇 도 올리거나 내려서 설정할 수 있습니다.
'''{{ device.displayName }} detected motion'''.ko={{ device.displayName }} 가 움직임을 감지하였습니다. '''battery'''.ko=배터리
'''{{ 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 has too much power: (> 3.5) volts.'''.ko={{ device.displayName }} 배터리 전력이 너무 높습니다(3.5볼트 초과).
'''{{ device.displayName }} battery was {{ value }}%'''.ko={{ device.displayName }}남아있는 배터리 {{ value }}%입니다. '''{{ 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였습니다
'''{{ device.displayName }} was {{ value }}°F'''.ko={{ device.displayName }}이(가) {{ value }}°F였습니다

View File

@@ -1,27 +1,17 @@
/* /**
=============================================================================== * SmartSense Motion/Temp Sensor
* Copyright 2016 SmartThings
* *
* Licensed under the Apache License, Version 2.0 (the "License"); you may not * Copyright 2014 SmartThings
* use this file except in compliance with the License. You may obtain a copy *
* of the License at: * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at:
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * for the specific language governing permissions and limitations under the License.
* License for the specific language governing permissions and limitations
* under the License.
===============================================================================
* Purpose: SmartSense Motion Sensor DTH File
* *
* Filename: SmartSense-Motion-Sensor.src/SmartSense-Motion-Sensor.groovy
*
* Change History:
* 1. 20160116 TW - Update/Edit to support i18n translations
* 2. 20160125 TW = Incorporated new battery mapping from TM
===============================================================================
*/ */
metadata { metadata {
@@ -57,7 +47,7 @@ metadata {
]) ])
} }
section { section {
input title: "Temperature Offset", description: "This feature allows you to correct any temperature variations by selecting an offset. Ex: If your sensor consistently reports a temp that's 5 degrees too warm, you'd enter '-5'. If 3 degrees too cold, enter '+3'.", displayDuringSetup: false, type: "paragraph", element: "paragraph" input title: "Temperature Offset", description: "This feature allows you to correct any temperature variations by selecting an offset. Ex: If your sensor consistently reports a temp that's 5 degrees too warm, you'd enter \"-5\". If 3 degrees too cold, enter \"+3\".", displayDuringSetup: false, type: "paragraph", element: "paragraph"
input "tempOffset", "number", title: "Degrees", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false input "tempOffset", "number", title: "Degrees", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false
} }
} }
@@ -246,8 +236,7 @@ private Map getBatteryResult(rawValue) {
def result = [ def result = [
name: 'battery', name: 'battery',
value: '--', value: '--'
translatable: true
] ]
def volts = rawValue / 10 def volts = rawValue / 10
@@ -255,7 +244,7 @@ private Map getBatteryResult(rawValue) {
if (rawValue == 0 || rawValue == 255) {} if (rawValue == 0 || rawValue == 255) {}
else { else {
if (volts > 3.5) { if (volts > 3.5) {
result.descriptionText = "{{ device.displayName }} battery has too much power: (> 3.5) volts." result.descriptionText = "${linkText} battery has too much power (${volts} volts)."
} }
else { else {
if (device.getDataValue("manufacturer") == "SmartThings") { if (device.getDataValue("manufacturer") == "SmartThings") {
@@ -272,8 +261,7 @@ private Map getBatteryResult(rawValue) {
def pct = batteryMap[volts] def pct = batteryMap[volts]
if (pct != null) { if (pct != null) {
result.value = pct result.value = pct
def value = pct result.descriptionText = "${linkText} battery was ${result.value}%"
result.descriptionText = "{{ device.displayName }} battery was {{ value }}%"
} }
} }
else { else {
@@ -281,7 +269,7 @@ private Map getBatteryResult(rawValue) {
def maxVolts = 3.0 def maxVolts = 3.0
def pct = (volts - minVolts) / (maxVolts - minVolts) def pct = (volts - minVolts) / (maxVolts - minVolts)
result.value = Math.min(100, (int) pct * 100) result.value = Math.min(100, (int) pct * 100)
result.descriptionText = "{{ device.displayName }} battery was {{ value }}%" result.descriptionText = "${linkText} battery was ${result.value}%"
} }
} }
} }
@@ -291,33 +279,28 @@ private Map getBatteryResult(rawValue) {
private Map getTemperatureResult(value) { private Map getTemperatureResult(value) {
log.debug 'TEMP' log.debug 'TEMP'
def linkText = getLinkText(device)
if (tempOffset) { if (tempOffset) {
def offset = tempOffset as int def offset = tempOffset as int
def v = value as int def v = value as int
value = v + offset value = v + offset
} }
def descriptionText def descriptionText = "${linkText} was ${value}°${temperatureScale}"
if ( temperatureScale == 'C' )
descriptionText = '{{ device.displayName }} was {{ value }}°C'
else
descriptionText = '{{ device.displayName }} was {{ value }}°F'
return [ return [
name: 'temperature', name: 'temperature',
value: value, value: value,
descriptionText: descriptionText, descriptionText: descriptionText
translatable: true
] ]
} }
private Map getMotionResult(value) { private Map getMotionResult(value) {
log.debug 'motion' log.debug 'motion'
String descriptionText = value == 'active' ? "{{ device.displayName }} detected motion" : "{{ device.displayName }} motion has stopped" String linkText = getLinkText(device)
String descriptionText = value == 'active' ? "${linkText} detected motion" : "${linkText} motion has stopped"
return [ return [
name: 'motion', name: 'motion',
value: value, value: value,
descriptionText: descriptionText, descriptionText: descriptionText
translatable: true
] ]
} }

View File

@@ -1,45 +1,20 @@
#==============================================================================
# Copyright 2016 SmartThings # Generated on Wed Feb 24 14:28:26 CST 2016 by dylan
# '''Adjust temperature by this many degrees'''.ko=몇 도씩 온도를 조절하십시오
# Licensed under the Apache License, Version 2.0 (the "License"); you may not '''Degrees'''.ko=온도
# use this file except in compliance with the License. You may obtain a copy '''Do you want to use this sensor on a garage door?'''.ko=차고 문의 센서 사용 설정하기
# 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=아니요 '''No'''.ko=아니요
'''battery'''.ko=배터리 '''Tap to set'''.ko=눌러서 설정
'''Temperature Offset'''.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=기준 온도를 원하는대로 몇 도 올리거나 내려서 설정할 수 있습니다. '''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 garage sensor'''.ko=기기-차고 센서 업데이트 중
'''Updating device to open/close 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였습니다

View File

@@ -1,27 +1,17 @@
/* /**
=============================================================================== * SmartSense Multi
* Copyright 2016 SmartThings
* *
* Licensed under the Apache License, Version 2.0 (the "License"); you may not * Copyright 2015 SmartThings
* use this file except in compliance with the License. You may obtain a copy *
* of the License at: * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at:
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * for the specific language governing permissions and limitations under the License.
* License for the specific language governing permissions and limitations
* under the License.
===============================================================================
* Purpose: SmartSense Multi Sensor DTH File
* *
* Filename: SmartSense-Multi-Sensor.src/SmartSense-Multi-Sensor.groovy
*
* Change History:
* 1. 20160117 TW - Update/Edit to support i18n translations
* 2. 20160125 TW = Incorporated new battery mapping from TM
===============================================================================
*/ */
metadata { metadata {
@@ -72,11 +62,11 @@ metadata {
]) ])
} }
section { section {
input title: "Temperature Offset", description: "This feature allows you to correct any temperature variations by selecting an offset. Ex: If your sensor consistently reports a temp that's 5 degrees too warm, you'd enter '-5'. If 3 degrees too cold, enter '+3'.", displayDuringSetup: false, type: "paragraph", element: "paragraph" input title: "Temperature Offset", description: "This feature allows you to correct any temperature variations by selecting an offset. Ex: If your sensor consistently reports a temp that's 5 degrees too warm, you'd enter \"-5\". If 3 degrees too cold, enter \"+3\".", displayDuringSetup: false, type: "paragraph", element: "paragraph"
input "tempOffset", "number", title: "Degrees", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false input "tempOffset", "number", title: "Degrees", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false
} }
section { section {
input("garageSensor", "enum", title: "Do you want to use this sensor on a garage door?", description: "Tap to set", options: ["Yes", "No"], defaultValue: "No", required: false, displayDuringSetup: false) input("garageSensor", "enum", title: "Do you want to use this sensor on a garage door?", options: ["Yes", "No"], defaultValue: "No", required: false, displayDuringSetup: false)
} }
} }
@@ -283,19 +273,19 @@ def updated() {
if (garageSensor == "Yes") { if (garageSensor == "Yes") {
def descriptionText = "Updating device to garage sensor" def descriptionText = "Updating device to garage sensor"
if (device.latestValue("status") == "open") { if (device.latestValue("status") == "open") {
sendEvent(name: 'status', value: 'garage-open', descriptionText: descriptionText, translatable: true) sendEvent(name: 'status', value: 'garage-open', descriptionText: descriptionText)
} }
else if (device.latestValue("status") == "closed") { else if (device.latestValue("status") == "closed") {
sendEvent(name: 'status', value: 'garage-closed', descriptionText: descriptionText, translatable: true) sendEvent(name: 'status', value: 'garage-closed', descriptionText: descriptionText)
} }
} }
else { else {
def descriptionText = "Updating device to open/close sensor" def descriptionText = "Updating device to open/close sensor"
if (device.latestValue("status") == "garage-open") { if (device.latestValue("status") == "garage-open") {
sendEvent(name: 'status', value: 'open', descriptionText: descriptionText, translatable: true) sendEvent(name: 'status', value: 'open', descriptionText: descriptionText)
} }
else if (device.latestValue("status") == "garage-closed") { else if (device.latestValue("status") == "garage-closed") {
sendEvent(name: 'status', value: 'closed', descriptionText: descriptionText, translatable: true) sendEvent(name: 'status', value: 'closed', descriptionText: descriptionText)
} }
} }
} }
@@ -311,11 +301,11 @@ def getTemperature(value) {
private Map getBatteryResult(rawValue) { private Map getBatteryResult(rawValue) {
log.debug "Battery rawValue = ${rawValue}" log.debug "Battery rawValue = ${rawValue}"
def linkText = getLinkText(device)
def result = [ def result = [
name: 'battery', name: 'battery',
value: '--', value: '--'
translatable: true
] ]
def volts = rawValue / 10 def volts = rawValue / 10
@@ -323,7 +313,7 @@ private Map getBatteryResult(rawValue) {
if (rawValue == 0 || rawValue == 255) {} if (rawValue == 0 || rawValue == 255) {}
else { else {
if (volts > 3.5) { if (volts > 3.5) {
result.descriptionText = "{{ device.displayName }} battery has too much power: (> 3.5) volts." result.descriptionText = "${linkText} battery has too much power (${volts} volts)."
} }
else { else {
if (device.getDataValue("manufacturer") == "SmartThings") { if (device.getDataValue("manufacturer") == "SmartThings") {
@@ -340,7 +330,7 @@ private Map getBatteryResult(rawValue) {
def pct = batteryMap[volts] def pct = batteryMap[volts]
if (pct != null) { if (pct != null) {
result.value = pct result.value = pct
result.descriptionText = "{{ device.displayName }} battery was {{ value }}%" result.descriptionText = "${linkText} battery was ${result.value}%"
} }
} }
else { else {
@@ -348,7 +338,7 @@ private Map getBatteryResult(rawValue) {
def maxVolts = 3.0 def maxVolts = 3.0
def pct = (volts - minVolts) / (maxVolts - minVolts) def pct = (volts - minVolts) / (maxVolts - minVolts)
result.value = Math.min(100, (int) pct * 100) result.value = Math.min(100, (int) pct * 100)
result.descriptionText = "{{ device.displayName }} battery was {{ value }}%" result.descriptionText = "${linkText} battery was ${result.value}%"
} }
} }
} }
@@ -358,50 +348,40 @@ private Map getBatteryResult(rawValue) {
private Map getTemperatureResult(value) { private Map getTemperatureResult(value) {
log.debug "Temperature" log.debug "Temperature"
def linkText = getLinkText(device)
if (tempOffset) { if (tempOffset) {
def offset = tempOffset as int def offset = tempOffset as int
def v = value as int def v = value as int
value = v + offset value = v + offset
} }
def descriptionText = temperatureScale == 'C' ? '{{ device.displayName }} was {{ value }}°C': def descriptionText = "${linkText} was ${value}°${temperatureScale}"
'{{ device.displayName }} was {{ value }}°F'
return [ return [
name: 'temperature', name: 'temperature',
value: value, value: value,
descriptionText: descriptionText, descriptionText: descriptionText
translatable: true
] ]
} }
private Map getContactResult(value) { private Map getContactResult(value) {
log.debug "Contact: ${device.displayName} value = ${value}" log.debug "Contact"
def descriptionText = value == 'open' ? '{{ device.displayName }} was opened' : '{{ device.displayName }} was closed' def linkText = getLinkText(device)
sendEvent(name: 'contact', value: value, descriptionText: descriptionText, displayed: false, translatable: true) def descriptionText = "${linkText} was ${value == 'open' ? 'opened' : 'closed'}"
sendEvent(name: 'status', value: value, descriptionText: descriptionText, translatable: true) sendEvent(name: 'contact', value: value, descriptionText: descriptionText, displayed:false)
sendEvent(name: 'status', value: value, descriptionText: descriptionText)
} }
private getAccelerationResult(numValue) { private getAccelerationResult(numValue) {
log.debug "Acceleration" log.debug "Acceleration"
def name = "acceleration" def name = "acceleration"
def value def value = numValue.endsWith("1") ? "active" : "inactive"
def descriptionText def linkText = getLinkText(device)
def descriptionText = "$linkText was $value"
if ( numValue.endsWith("1") ) {
value = "active"
descriptionText = '{{ device.displayName }} was active'
} else {
value = "inactive"
descriptionText = '{{ device.displayName }} was inactive'
}
def isStateChange = isStateChange(device, name, value) def isStateChange = isStateChange(device, name, value)
return [ [
name: name, name: name,
value: value, value: value,
descriptionText: descriptionText, descriptionText: descriptionText,
isStateChange: isStateChange, isStateChange: isStateChange
translatable: true
] ]
} }
@@ -471,15 +451,15 @@ def configure() {
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500", "send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
"zcl mfg-code ${manufacturerCode}", "delay 200", "zcl mfg-code ${manufacturerCode}", "delay 200",
"zcl global send-me-a-report 0xFC02 0x0012 0x29 1 3600 {0100}", "delay 200", "zcl global send-me-a-report 0xFC02 0x0012 0x29 1 3600 {01}", "delay 200",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500", "send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
"zcl mfg-code ${manufacturerCode}", "delay 200", "zcl mfg-code ${manufacturerCode}", "delay 200",
"zcl global send-me-a-report 0xFC02 0x0013 0x29 1 3600 {0100}", "delay 200", "zcl global send-me-a-report 0xFC02 0x0013 0x29 1 3600 {01}", "delay 200",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500", "send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
"zcl mfg-code ${manufacturerCode}", "delay 200", "zcl mfg-code ${manufacturerCode}", "delay 200",
"zcl global send-me-a-report 0xFC02 0x0014 0x29 1 3600 {0100}", "delay 200", "zcl global send-me-a-report 0xFC02 0x0014 0x29 1 3600 {01}", "delay 200",
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500" "send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500"
] ]
@@ -548,9 +528,10 @@ def garageEvent(zValue) {
garageValue = 'garage-open' garageValue = 'garage-open'
} }
if (contactValue != null){ if (contactValue != null){
def descriptionText = contactValue == 'open' ? '{{ device.displayName }} was opened' :'{{ device.displayName }} was closed' def linkText = getLinkText(device)
sendEvent(name: 'contact', value: contactValue, descriptionText: descriptionText, displayed:false, translatable: true) def descriptionText = "${linkText} was ${contactValue == 'open' ? 'opened' : 'closed'}"
sendEvent(name: 'status', value: garageValue, descriptionText: descriptionText, translatable: true) sendEvent(name: 'contact', value: contactValue, descriptionText: descriptionText, displayed:false)
sendEvent(name: 'status', value: garageValue, descriptionText: descriptionText)
} }
} }

View File

@@ -22,8 +22,6 @@ metadata {
capability "Battery" capability "Battery"
fingerprint profileId: "FC01", deviceId: "0139" fingerprint profileId: "FC01", deviceId: "0139"
attribute "status", "string"
} }
simulator { simulator {
@@ -101,7 +99,7 @@ def parse(String description) {
} }
private List parseSingleMessage(description) { private Map parseSingleMessage(description) {
def name = parseName(description) def name = parseName(description)
def value = parseValue(description) def value = parseValue(description)
@@ -110,9 +108,8 @@ private List parseSingleMessage(description) {
def handlerName = value == 'open' ? 'opened' : value def handlerName = value == 'open' ? 'opened' : value
def isStateChange = isStateChange(device, name, value) def isStateChange = isStateChange(device, name, value)
def results = [] def results = [
results << createEvent( name: name,
name: "contact",
value: value, value: value,
unit: null, unit: null,
linkText: linkText, linkText: linkText,
@@ -120,18 +117,8 @@ private List parseSingleMessage(description) {
handlerName: handlerName, handlerName: handlerName,
isStateChange: isStateChange, isStateChange: isStateChange,
displayed: displayed(description, isStateChange) displayed: displayed(description, isStateChange)
) ]
log.debug "Parse results for $device: $results"
results << createEvent(
name: "status",
value: value,
unit: null,
linkText: linkText,
descriptionText: descriptionText,
handlerName: handlerName,
isStateChange: isStateChange,
displayed: displayed(description, isStateChange)
)
results results
} }
@@ -203,7 +190,7 @@ private List parseContactMessage(String description) {
parts.each { part -> parts.each { part ->
part = part.trim() part = part.trim()
if (part.startsWith('contactState:')) { if (part.startsWith('contactState:')) {
results.addAll(getContactResult(part, description)) results << getContactResult(part, description)
} }
else if (part.startsWith('accelerationState:')) { else if (part.startsWith('accelerationState:')) {
results << getAccelerationResult(part, description) results << getAccelerationResult(part, description)
@@ -282,7 +269,7 @@ private List parseRssiLqiMessage(String description) {
results results
} }
private List getContactResult(part, description) { private getContactResult(part, description) {
def name = "contact" def name = "contact"
def value = part.endsWith("1") ? "open" : "closed" def value = part.endsWith("1") ? "open" : "closed"
def handlerName = value == 'open' ? 'opened' : value def handlerName = value == 'open' ? 'opened' : value
@@ -290,33 +277,19 @@ private List getContactResult(part, description) {
def descriptionText = "$linkText was $handlerName" def descriptionText = "$linkText was $handlerName"
def isStateChange = isStateChange(device, name, value) def isStateChange = isStateChange(device, name, value)
def results = [] [
results << createEvent( name: name,
name: "contact", value: value,
value: value, unit: null,
unit: null, linkText: linkText,
linkText: linkText, descriptionText: descriptionText,
descriptionText: descriptionText, handlerName: handlerName,
handlerName: handlerName, isStateChange: isStateChange,
isStateChange: isStateChange, displayed: displayed(description, isStateChange)
displayed:false ]
)
results << createEvent(
name: "status",
value: value,
unit: null,
linkText: linkText,
descriptionText: descriptionText,
handlerName: handlerName,
isStateChange: isStateChange,
displayed: displayed(description, isStateChange)
)
results
} }
private Map getAccelerationResult(part, description) { private getAccelerationResult(part, description) {
def name = "acceleration" def name = "acceleration"
def value = part.endsWith("1") ? "active" : "inactive" def value = part.endsWith("1") ? "active" : "inactive"
def linkText = getLinkText(device) def linkText = getLinkText(device)
@@ -335,7 +308,7 @@ private Map getAccelerationResult(part, description) {
] ]
} }
private Map getTempResult(part, description) { private getTempResult(part, description) {
def name = "temperature" def name = "temperature"
def temperatureScale = getTemperatureScale() def temperatureScale = getTemperatureScale()
def value = zigbee.parseSmartThingsTemperatureValue(part, "temp: ", temperatureScale) def value = zigbee.parseSmartThingsTemperatureValue(part, "temp: ", temperatureScale)
@@ -360,7 +333,7 @@ private Map getTempResult(part, description) {
] ]
} }
private Map getXyzResult(results, description) { private getXyzResult(results, description) {
def name = "threeAxis" def name = "threeAxis"
def value = "${results.x},${results.y},${results.z}" def value = "${results.x},${results.y},${results.z}"
def linkText = getLinkText(device) def linkText = getLinkText(device)
@@ -379,7 +352,7 @@ private Map getXyzResult(results, description) {
] ]
} }
private Map getBatteryResult(part, description) { private getBatteryResult(part, description) {
def batteryDivisor = description.split(",").find {it.split(":")[0].trim() == "batteryDivisor"} ? description.split(",").find {it.split(":")[0].trim() == "batteryDivisor"}.split(":")[1].trim() : null def batteryDivisor = description.split(",").find {it.split(":")[0].trim() == "batteryDivisor"} ? description.split(",").find {it.split(":")[0].trim() == "batteryDivisor"}.split(":")[1].trim() : null
def name = "battery" def name = "battery"
def value = zigbee.parseSmartThingsBatteryValue(part, batteryDivisor) def value = zigbee.parseSmartThingsBatteryValue(part, batteryDivisor)
@@ -400,7 +373,7 @@ private Map getBatteryResult(part, description) {
] ]
} }
private Map getRssiResult(part, description, lastHop=false) { private getRssiResult(part, description, lastHop=false) {
def name = lastHop ? "lastHopRssi" : "rssi" def name = lastHop ? "lastHopRssi" : "rssi"
def valueString = part.split(":")[1].trim() def valueString = part.split(":")[1].trim()
def value = (Integer.parseInt(valueString) - 128).toString() def value = (Integer.parseInt(valueString) - 128).toString()
@@ -431,7 +404,7 @@ private Map getRssiResult(part, description, lastHop=false) {
* Note: To make the signal strength indicator more accurate, we could combine * Note: To make the signal strength indicator more accurate, we could combine
* LQI with RSSI. * LQI with RSSI.
*/ */
private Map getLqiResult(part, description, lastHop=false) { private getLqiResult(part, description, lastHop=false) {
def name = lastHop ? "lastHopLqi" : "lqi" def name = lastHop ? "lastHopLqi" : "lqi"
def valueString = part.split(":")[1].trim() def valueString = part.split(":")[1].trim()
def percentageOf = 255 def percentageOf = 255
@@ -476,7 +449,6 @@ private Boolean isOrientationMessage(String description) {
description ==~ /x:.*y:.*z:.*rssi:.*lqi:.*/ description ==~ /x:.*y:.*z:.*rssi:.*lqi:.*/
} }
//Note: Not using this method anymore
private String parseName(String description) { private String parseName(String description) {
if (isSupportedDescription(description)) { if (isSupportedDescription(description)) {
return "contact" return "contact"

View File

@@ -56,17 +56,21 @@ metadata {
def parse(String description) { def parse(String description) {
log.debug "description is $description" log.debug "description is $description"
def event = zigbee.getEvent(description) def resultMap = zigbee.getKnownDescription(description)
if (event) { if (resultMap) {
log.info event log.info resultMap
if (event.name == "power") { if (resultMap.type == "update") {
log.info "$device updates: ${resultMap.value}"
}
else if (resultMap.type == "power") {
def powerValue
if (device.getDataValue("manufacturer") != "OSRAM") { //OSRAM devices do not reliably update power if (device.getDataValue("manufacturer") != "OSRAM") { //OSRAM devices do not reliably update power
event.value = (event.value as Integer) / 10 //TODO: The divisor value needs to be set as part of configuration powerValue = (resultMap.value as Integer)/10 //TODO: The divisor value needs to be set as part of configuration
sendEvent(event) sendEvent(name: "power", value: powerValue)
} }
} }
else { else {
sendEvent(event) sendEvent(name: resultMap.type, value: resultMap.value)
} }
} }
else { else {

View File

@@ -51,9 +51,15 @@ metadata {
def parse(String description) { def parse(String description) {
log.debug "description is $description" log.debug "description is $description"
def event = zigbee.getEvent(description) def resultMap = zigbee.getKnownDescription(description)
if (event) { if (resultMap) {
sendEvent(event) log.info resultMap
if (resultMap.type == "update") {
log.info "$device updates: ${resultMap.value}"
}
else {
sendEvent(name: resultMap.type, value: resultMap.value)
}
} }
else { else {
log.warn "DID NOT PARSE MESSAGE for description : $description" log.warn "DID NOT PARSE MESSAGE for description : $description"

View File

@@ -83,19 +83,32 @@ def uninstalled() {
} }
def configure() { def configure() {
/*
def cmds = def cmds =
zigbee.configureReporting(CLUSTER_DOORLOCK, DOORLOCK_ATTR_LOCKSTATE, zigbee.configSetup("${CLUSTER_DOORLOCK}", "${DOORLOCK_ATTR_LOCKSTATE}",
TYPE_ENUM8, 0, 3600, null) + "${TYPE_ENUM8}", 0, 3600, "{01}") +
zigbee.configureReporting(CLUSTER_POWER, POWER_ATTR_BATTERY_PERCENTAGE_REMAINING, zigbee.configSetup("${CLUSTER_POWER}", "${POWER_ATTR_BATTERY_PERCENTAGE_REMAINING}",
TYPE_U8, 600, 21600, 0x01) "${TYPE_U8}", 600, 21600, "{01}")
*/
def zigbeeId = device.zigbeeId
def cmds =
[
"zdo bind 0x${device.deviceNetworkId} 0x${device.endpointId} 1 ${CLUSTER_DOORLOCK} {$zigbeeId} {}", "delay 200",
"zcl global send-me-a-report ${CLUSTER_DOORLOCK} ${DOORLOCK_ATTR_LOCKSTATE} ${TYPE_ENUM8} 0 3600 {01}", "delay 200",
"send 0x${device.deviceNetworkId} 1 0x${device.endpointId}", "delay 200",
"zdo bind 0x${device.deviceNetworkId} 0x${device.endpointId} 1 ${CLUSTER_POWER} {$zigbeeId} {}", "delay 200",
"zcl global send-me-a-report ${CLUSTER_POWER} ${POWER_ATTR_BATTERY_PERCENTAGE_REMAINING} ${TYPE_U8} 600 21600 {01}", "delay 200",
"send 0x${device.deviceNetworkId} 1 0x${device.endpointId}", "delay 200",
]
log.info "configure() --- cmds: $cmds" log.info "configure() --- cmds: $cmds"
return cmds + refresh() // send refresh cmds as part of config return cmds + refresh() // send refresh cmds as part of config
} }
def refresh() { def refresh() {
def cmds = def cmds =
zigbee.readAttribute(CLUSTER_DOORLOCK, DOORLOCK_ATTR_LOCKSTATE) + zigbee.refreshData("${CLUSTER_DOORLOCK}", "${DOORLOCK_ATTR_LOCKSTATE}") +
zigbee.readAttribute(CLUSTER_POWER, POWER_ATTR_BATTERY_PERCENTAGE_REMAINING) zigbee.refreshData("${CLUSTER_POWER}", "${POWER_ATTR_BATTERY_PERCENTAGE_REMAINING}")
log.info "refresh() --- cmds: $cmds" log.info "refresh() --- cmds: $cmds"
return cmds return cmds
} }
@@ -108,27 +121,34 @@ def parse(String description) {
map = parseReportAttributeMessage(description) map = parseReportAttributeMessage(description)
} }
log.debug "parse() --- Parse returned $map"
def result = map ? createEvent(map) : null def result = map ? createEvent(map) : null
log.debug "parse() --- returned: $result"
return result return result
} }
// Lock capability commands // Lock capability commands
def lock() { def lock() {
def cmds = zigbee.command(CLUSTER_DOORLOCK, DOORLOCK_CMD_LOCK_DOOR) //def cmds = zigbee.zigbeeCommand("${CLUSTER_DOORLOCK}", "${DOORLOCK_CMD_LOCK_DOOR}", "{}")
log.info "lock() -- cmds: $cmds" //log.info "lock() -- cmds: $cmds"
return cmds //return cmds
"st cmd 0x${device.deviceNetworkId} 0x${device.endpointId} ${CLUSTER_DOORLOCK} ${DOORLOCK_CMD_LOCK_DOOR} {}"
} }
def unlock() { def unlock() {
def cmds = zigbee.command(CLUSTER_DOORLOCK, DOORLOCK_CMD_UNLOCK_DOOR) //def cmds = zigbee.zigbeeCommand("${CLUSTER_DOORLOCK}", "${DOORLOCK_CMD_UNLOCK_DOOR}", "{}")
log.info "unlock() -- cmds: $cmds" //log.info "unlock() -- cmds: $cmds"
return cmds //return cmds
"st cmd 0x${device.deviceNetworkId} 0x${device.endpointId} ${CLUSTER_DOORLOCK} ${DOORLOCK_CMD_UNLOCK_DOOR} {}"
} }
// Private methods // Private methods
private Map parseReportAttributeMessage(String description) { private Map parseReportAttributeMessage(String description) {
log.trace "parseReportAttributeMessage() --- description: $description"
Map descMap = zigbee.parseDescriptionAsMap(description) Map descMap = zigbee.parseDescriptionAsMap(description)
log.debug "parseReportAttributeMessage() --- descMap: $descMap"
Map resultMap = [:] Map resultMap = [:]
if (descMap.clusterInt == CLUSTER_POWER && descMap.attrInt == POWER_ATTR_BATTERY_PERCENTAGE_REMAINING) { if (descMap.clusterInt == CLUSTER_POWER && descMap.attrInt == POWER_ATTR_BATTERY_PERCENTAGE_REMAINING) {
resultMap.name = "battery" resultMap.name = "battery"
@@ -136,24 +156,18 @@ private Map parseReportAttributeMessage(String description) {
if (device.getDataValue("manufacturer") == "Yale") { //Handling issue with Yale locks incorrect battery reporting if (device.getDataValue("manufacturer") == "Yale") { //Handling issue with Yale locks incorrect battery reporting
resultMap.value = Integer.parseInt(descMap.value, 16) resultMap.value = Integer.parseInt(descMap.value, 16)
} }
log.info "parseReportAttributeMessage() --- battery: ${resultMap.value}"
} }
else if (descMap.clusterInt == CLUSTER_DOORLOCK && descMap.attrInt == DOORLOCK_ATTR_LOCKSTATE) { else if (descMap.clusterInt == CLUSTER_DOORLOCK && descMap.attrInt == DOORLOCK_ATTR_LOCKSTATE) {
def value = Integer.parseInt(descMap.value, 16) def value = Integer.parseInt(descMap.value, 16)
def linkText = getLinkText(device)
resultMap.name = "lock" resultMap.name = "lock"
if (value == 0) { resultMap.putAll([0:["value":"unknown",
resultMap.value = "unknown" "descriptionText":"Not fully locked"],
resultMap.descriptionText = "${linkText} is not fully locked" 1:["value":"locked"],
} else if (value == 1) { 2:["value":"unlocked"]].get(value,
resultMap.value = "locked" ["value":"unknown",
resultMap.descriptionText = "${linkText} is locked" "descriptionText":"Unknown lock state"]))
} else if (value == 2) { log.info "parseReportAttributeMessage() --- lock: ${resultMap.value}"
resultMap.value = "unlocked"
resultMap.descriptionText = "${linkText} is unlocked"
} else {
resultMap.value = "unknown"
resultMap.descriptionText = "${linkText} is in unknown lock state"
}
} }
else { else {
log.debug "parseReportAttributeMessage() --- ignoring attribute" log.debug "parseReportAttributeMessage() --- ignoring attribute"

View File

@@ -51,15 +51,22 @@ metadata {
// Parse incoming device messages to generate events // Parse incoming device messages to generate events
def parse(String description) { def parse(String description) {
log.debug "description is $description" log.debug "description is $description"
def event = zigbee.getEvent(description)
if (event) { def resultMap = zigbee.getKnownDescription(description)
if (event.name == "power") { if (resultMap) {
log.info resultMap
if (resultMap.type == "update") {
log.info "$device updates: ${resultMap.value}"
}
else if (resultMap.type == "power") {
def powerValue def powerValue
powerValue = (event.value as Integer)/10 //TODO: The divisor value needs to be set as part of configuration if (device.getDataValue("manufacturer") != "OSRAM") { //OSRAM devices do not reliably update power
sendEvent(name: "power", value: powerValue) powerValue = (resultMap.value as Integer)/10 //TODO: The divisor value needs to be set as part of configuration
sendEvent(name: "power", value: powerValue)
}
} }
else { else {
sendEvent(event) sendEvent(name: resultMap.type, value: resultMap.value)
} }
} }
else { else {

View File

@@ -53,9 +53,16 @@ metadata {
// Parse incoming device messages to generate events // Parse incoming device messages to generate events
def parse(String description) { def parse(String description) {
log.debug "description is $description" log.debug "description is $description"
def event = zigbee.getEvent(description)
if (event) { def resultMap = zigbee.getKnownDescription(description)
sendEvent(event) if (resultMap) {
log.info resultMap
if (resultMap.type == "update") {
log.info "$device updates: ${resultMap.value}"
}
else {
sendEvent(name: resultMap.type, value: resultMap.value)
}
} }
else { else {
log.warn "DID NOT PARSE MESSAGE for description : $description" log.warn "DID NOT PARSE MESSAGE for description : $description"

View File

@@ -73,9 +73,16 @@ metadata {
// Parse incoming device messages to generate events // Parse incoming device messages to generate events
def parse(String description) { def parse(String description) {
log.debug "description is $description" log.debug "description is $description"
def event = zigbee.getEvent(description)
if (event) { def finalResult = zigbee.getKnownDescription(description)
sendEvent(event) if (finalResult) {
log.info finalResult
if (finalResult.type == "update") {
log.info "$device updates: ${finalResult.value}"
}
else {
sendEvent(name: finalResult.type, value: finalResult.value)
}
} }
else { else {
log.warn "DID NOT PARSE MESSAGE for description : $description" log.warn "DID NOT PARSE MESSAGE for description : $description"

View File

@@ -41,7 +41,6 @@ metadata {
attributeState "locked", label:'locked', action:"lock.unlock", icon:"st.locks.lock.locked", backgroundColor:"#79b821", nextState:"unlocking" attributeState "locked", label:'locked', action:"lock.unlock", icon:"st.locks.lock.locked", backgroundColor:"#79b821", nextState:"unlocking"
attributeState "unlocked", label:'unlocked', action:"lock.lock", icon:"st.locks.lock.unlocked", backgroundColor:"#ffffff", nextState:"locking" attributeState "unlocked", label:'unlocked', action:"lock.lock", icon:"st.locks.lock.unlocked", backgroundColor:"#ffffff", nextState:"locking"
attributeState "unknown", label:"unknown", action:"lock.lock", icon:"st.locks.lock.unknown", backgroundColor:"#ffffff", nextState:"locking" attributeState "unknown", label:"unknown", action:"lock.lock", icon:"st.locks.lock.unknown", backgroundColor:"#ffffff", nextState:"locking"
attributeState "jammed", label:"jammed", action:"lock.lock", icon:"st.locks.lock.unknown", backgroundColor:"#ffffff", nextState:"locking"
attributeState "locking", label:'locking', icon:"st.locks.lock.locked", backgroundColor:"#79b821" attributeState "locking", label:'locking', icon:"st.locks.lock.locked", backgroundColor:"#79b821"
attributeState "unlocking", label:'unlocking', icon:"st.locks.lock.unlocked", backgroundColor:"#ffffff" attributeState "unlocking", label:'unlocking', icon:"st.locks.lock.unlocked", backgroundColor:"#ffffff"
} }
@@ -131,7 +130,7 @@ def zwaveEvent(DoorLockOperationReport cmd) {
if (cmd.doorLockMode == 0xFF) { if (cmd.doorLockMode == 0xFF) {
map.value = "locked" map.value = "locked"
} else if (cmd.doorLockMode >= 0x40) { } else if (cmd.doorLockMode >= 0x40) {
map.value = "unknown" // XXX: Jammed? map.value = "unknown"
} else if (cmd.doorLockMode & 1) { } else if (cmd.doorLockMode & 1) {
map.value = "unlocked with timeout" map.value = "unlocked with timeout"
} else { } else {
@@ -181,7 +180,7 @@ def zwaveEvent(physicalgraph.zwave.commands.alarmv2.AlarmReport cmd) {
map = [ name: "lock", value: "unknown", descriptionText: "$device.displayName was not locked fully" ] map = [ name: "lock", value: "unknown", descriptionText: "$device.displayName was not locked fully" ]
break break
case 0xB: case 0xB:
map = [ name: "lock", value: "jammed", descriptionText: "$device.displayName is jammed", displayed: true, eventType: "ALERT" ] map = [ name: "lock", value: "unknown", descriptionText: "$device.displayName is jammed" ]
break break
case 0xC: case 0xC:
map = [ name: "codeChanged", value: "all", descriptionText: "$device.displayName: all user codes deleted", isStateChange: true ] map = [ name: "codeChanged", value: "all", descriptionText: "$device.displayName: all user codes deleted", isStateChange: true ]
@@ -267,7 +266,7 @@ def zwaveEvent(physicalgraph.zwave.commands.alarmv2.AlarmReport cmd) {
case 17: case 17:
case 23: case 23:
case 26: case 26:
map = [ name: "lock", value: "jammed", descriptionText: "$device.displayName bolt is jammed", displayed: true, eventType: "ALERT" ] map = [ name: "lock", value: "unknown", descriptionText: "$device.displayName bolt is jammed" ]
break break
case 13: case 13:
map = [ name: "codeChanged", value: cmd.alarmLevel, descriptionText: "$device.displayName code $cmd.alarmLevel was added", isStateChange: true ] map = [ name: "codeChanged", value: cmd.alarmLevel, descriptionText: "$device.displayName code $cmd.alarmLevel was added", isStateChange: true ]

View File

@@ -57,7 +57,7 @@ def parse(String description) {
return result return result
} }
def sensorValueEvent(value) { def sensorValueEvent(Short value) {
if (value) { if (value) {
createEvent(name: "motion", value: "active", descriptionText: "$device.displayName detected motion") createEvent(name: "motion", value: "active", descriptionText: "$device.displayName detected motion")
} else { } else {
@@ -94,24 +94,24 @@ def zwaveEvent(physicalgraph.zwave.commands.notificationv3.NotificationReport cm
{ {
def result = [] def result = []
if (cmd.notificationType == 0x07) { if (cmd.notificationType == 0x07) {
if (cmd.v1AlarmType == 0x07) { // special case for nonstandard messages from Monoprice ensors if (cmd.event == 0x01 || cmd.event == 0x02) {
result << sensorValueEvent(cmd.v1AlarmLevel)
} else if (cmd.event == 0x01 || cmd.event == 0x02 || cmd.event == 0x07 || cmd.event == 0x08) {
result << sensorValueEvent(1) result << sensorValueEvent(1)
} else if (cmd.event == 0x00) {
result << sensorValueEvent(0)
} else if (cmd.event == 0x03) { } else if (cmd.event == 0x03) {
result << createEvent(name: "tamper", value: "detected", descriptionText: "$device.displayName covering was removed", isStateChange: true) result << createEvent(descriptionText: "$device.displayName covering was removed", isStateChange: true)
result << response(zwave.batteryV1.batteryGet()) result << response(zwave.wakeUpV1.wakeUpIntervalSet(seconds:4*3600, nodeid:zwaveHubNodeId))
if(!state.MSR) result << response(zwave.manufacturerSpecificV2.manufacturerSpecificGet())
} else if (cmd.event == 0x05 || cmd.event == 0x06) { } else if (cmd.event == 0x05 || cmd.event == 0x06) {
result << createEvent(descriptionText: "$device.displayName detected glass breakage", isStateChange: true) result << createEvent(descriptionText: "$device.displayName detected glass breakage", isStateChange: true)
} else if (cmd.event == 0x07) {
if(!state.MSR) result << response(zwave.manufacturerSpecificV2.manufacturerSpecificGet())
result << sensorValueEvent(1)
} }
} else if (cmd.notificationType) { } else if (cmd.notificationType) {
def text = "Notification $cmd.notificationType: event ${([cmd.event] + cmd.eventParameter).join(", ")}" def text = "Notification $cmd.notificationType: event ${([cmd.event] + cmd.eventParameter).join(", ")}"
result << createEvent(name: "notification$cmd.notificationType", value: "$cmd.event", descriptionText: text, isStateChange: true, displayed: false) result << createEvent(name: "notification$cmd.notificationType", value: "$cmd.event", descriptionText: text, displayed: false)
} else { } else {
def value = cmd.v1AlarmLevel == 255 ? "active" : cmd.v1AlarmLevel ?: "inactive" def value = cmd.v1AlarmLevel == 255 ? "active" : cmd.v1AlarmLevel ?: "inactive"
result << createEvent(name: "alarm $cmd.v1AlarmType", value: value, isStateChange: true, displayed: false) result << createEvent(name: "alarm $cmd.v1AlarmType", value: value, displayed: false)
} }
result result
} }

View File

@@ -61,44 +61,37 @@ def parse(String description) {
zwaveEvent(cmd, results) zwaveEvent(cmd, results)
} }
} }
log.debug "'$description' parsed to ${results.inspect()}" // log.debug "\"$description\" parsed to ${results.inspect()}"
return results return results
} }
def createSmokeOrCOEvents(name, results) { def createSmokeOrCOEvents(name, results) {
def text = null def text = null
switch (name) { if (name == "smoke") {
case "smoke": text = "$device.displayName smoke was detected!"
text = "$device.displayName smoke was detected!" // these are displayed:false because the composite event is the one we want to see in the app
// these are displayed:false because the composite event is the one we want to see in the app results << createEvent(name: "smoke", value: "detected", descriptionText: text, displayed: false)
results << createEvent(name: "smoke", value: "detected", descriptionText: text, displayed: false) } else if (name == "carbonMonoxide") {
break text = "$device.displayName carbon monoxide was detected!"
case "carbonMonoxide": results << createEvent(name: "carbonMonoxide", value: "detected", descriptionText: text, displayed: false)
text = "$device.displayName carbon monoxide was detected!" } else if (name == "tested") {
results << createEvent(name: "carbonMonoxide", value: "detected", descriptionText: text, displayed: false) text = "$device.displayName was tested"
break results << createEvent(name: "smoke", value: "tested", descriptionText: text, displayed: false)
case "tested": results << createEvent(name: "carbonMonoxide", value: "tested", descriptionText: text, displayed: false)
text = "$device.displayName was tested" } else if (name == "smokeClear") {
results << createEvent(name: "smoke", value: "tested", descriptionText: text, displayed: false) text = "$device.displayName smoke is clear"
results << createEvent(name: "carbonMonoxide", value: "tested", descriptionText: text, displayed: false) results << createEvent(name: "smoke", value: "clear", descriptionText: text, displayed: false)
break name = "clear"
case "smokeClear": } else if (name == "carbonMonoxideClear") {
text = "$device.displayName smoke is clear" text = "$device.displayName carbon monoxide is clear"
results << createEvent(name: "smoke", value: "clear", descriptionText: text, displayed: false) results << createEvent(name: "carbonMonoxide", value: "clear", descriptionText: text, displayed: false)
name = "clear" name = "clear"
break } else if (name == "testClear") {
case "carbonMonoxideClear": text = "$device.displayName smoke is clear"
text = "$device.displayName carbon monoxide is clear" results << createEvent(name: "smoke", value: "clear", descriptionText: text, displayed: false)
results << createEvent(name: "carbonMonoxide", value: "clear", descriptionText: text, displayed: false) results << createEvent(name: "carbonMonoxide", value: "clear", displayed: false)
name = "clear" name = "clear"
break
case "testClear":
text = "$device.displayName test cleared"
results << createEvent(name: "smoke", value: "clear", descriptionText: text, displayed: false)
results << createEvent(name: "carbonMonoxide", value: "clear", displayed: false)
name = "clear"
break
} }
// This composite event is used for updating the tile // This composite event is used for updating the tile
results << createEvent(name: "alarmState", value: name, descriptionText: text) results << createEvent(name: "alarmState", value: name, descriptionText: text)
@@ -124,10 +117,8 @@ def zwaveEvent(physicalgraph.zwave.commands.alarmv2.AlarmReport cmd, results) {
createSmokeOrCOEvents(cmd.alarmLevel ? "tested" : "testClear", results) createSmokeOrCOEvents(cmd.alarmLevel ? "tested" : "testClear", results)
break break
case 13: // sent every hour -- not sure what this means, just a wake up notification? case 13: // sent every hour -- not sure what this means, just a wake up notification?
if (cmd.alarmLevel == 255) { if (cmd.alarmLevel != 255) {
results << createEvent(descriptionText: "$device.displayName checked in", isStateChange: false) results << createEvent(descriptionText: "$device.displayName code 13 is $cmd.alarmLevel", displayed: true)
} else {
results << createEvent(descriptionText: "$device.displayName code 13 is $cmd.alarmLevel", isStateChange:true, displayed:false)
} }
// Clear smoke in case they pulled batteries and we missed the clear msg // Clear smoke in case they pulled batteries and we missed the clear msg
@@ -136,8 +127,9 @@ def zwaveEvent(physicalgraph.zwave.commands.alarmv2.AlarmReport cmd, results) {
} }
// Check battery if we don't have a recent battery event // Check battery if we don't have a recent battery event
if (!state.lastbatt || (now() - state.lastbatt) >= 48*60*60*1000) { def prevBattery = device.currentState("battery")
results << response(zwave.batteryV1.batteryGet()) if (!prevBattery || (new Date().time - prevBattery.date.time)/60000 >= 60 * 53) {
results << new physicalgraph.device.HubAction(zwave.batteryV1.batteryGet().format())
} }
break break
default: default:
@@ -166,17 +158,12 @@ def zwaveEvent(physicalgraph.zwave.commands.sensoralarmv1.SensorAlarmReport cmd,
} }
def zwaveEvent(physicalgraph.zwave.commands.wakeupv1.WakeUpNotification cmd, results) { def zwaveEvent(physicalgraph.zwave.commands.wakeupv1.WakeUpNotification cmd, results) {
results << new physicalgraph.device.HubAction(zwave.wakeUpV1.wakeUpNoMoreInformation().format())
results << createEvent(descriptionText: "$device.displayName woke up", isStateChange: false) results << createEvent(descriptionText: "$device.displayName woke up", isStateChange: false)
if (!state.lastbatt || (now() - state.lastbatt) >= 56*60*60*1000) {
results << response(zwave.batteryV1.batteryGet(), "delay 2000", zwave.wakeUpV1.wakeUpNoMoreInformation())
} else {
results << response(zwave.wakeUpV1.wakeUpNoMoreInformation())
}
} }
def zwaveEvent(physicalgraph.zwave.commands.batteryv1.BatteryReport cmd, results) { def zwaveEvent(physicalgraph.zwave.commands.batteryv1.BatteryReport cmd, results) {
def map = [ name: "battery", unit: "%", isStateChange: true ] def map = [ name: "battery", unit: "%" ]
state.lastbatt = now()
if (cmd.batteryLevel == 0xFF) { if (cmd.batteryLevel == 0xFF) {
map.value = 1 map.value = 1
map.descriptionText = "$device.displayName battery is low!" map.descriptionText = "$device.displayName battery is low!"

View File

@@ -67,11 +67,11 @@ mappings {
def listAllDevices() { def listAllDevices() {
def resp = [] def resp = []
switches.each { switches.each {
resp << [name: it.name, label: it.label, value: it.currentValue("switch"), type: "switch", id: it.id, hub: it.hub?.name] resp << [name: it.name, label: it.label, value: it.currentValue("switch"), type: "switch", id: it.id, hub: it.hub.name]
} }
locks.each { locks.each {
resp << [name: it.name, label: it.label, value: it.currentValue("lock"), type: "lock", id: it.id, hub: it.hub?.name] resp << [name: it.name, label: it.label, value: it.currentValue("lock"), type: "lock", id: it.id, hub: it.hub.name]
} }
return resp return resp
} }

View File

@@ -463,32 +463,29 @@ def pollChildren(child = null) {
} }
// Poll Child is invoked from the Child Device itself as part of the Poll Capability // Poll Child is invoked from the Child Device itself as part of the Poll Capability
def pollChild(){ def pollChild(child){
def devices = getChildDevices() if (pollChildren(child)){
if (!child.device.deviceNetworkId.startsWith("ecobee_sensor")){
if (pollChildren()){ if(atomicState.thermostats[child.device.deviceNetworkId] != null) {
devices.each { child -> def tData = atomicState.thermostats[child.device.deviceNetworkId]
if (!child.device.deviceNetworkId.startsWith("ecobee_sensor")){ log.info "pollChild(child)>> data for ${child.device.deviceNetworkId} : ${tData.data}"
if(atomicState.thermostats[child.device.deviceNetworkId] != null) { child.generateEvent(tData.data) //parse received message from parent
def tData = atomicState.thermostats[child.device.deviceNetworkId] } else if(atomicState.thermostats[child.device.deviceNetworkId] == null) {
log.info "pollChild(child)>> data for ${child.device.deviceNetworkId} : ${tData.data}" log.error "ERROR: Device connection removed? no data for ${child.device.deviceNetworkId}"
child.generateEvent(tData.data) //parse received message from parent return null
} else if(atomicState.thermostats[child.device.deviceNetworkId] == null) {
log.error "ERROR: Device connection removed? no data for ${child.device.deviceNetworkId}"
return null
}
} }
} }
} else { } else {
log.info "ERROR: pollChildren()" log.info "ERROR: pollChildren(child) for ${child.device.deviceNetworkId} after polling"
return null return null
} }
} }
void poll() { void poll() {
pollChild() def devices = getChildDevices()
devices.each {pollChild(it)}
} }
def availableModes(child) { def availableModes(child) {

View File

@@ -24,7 +24,7 @@ definition(
category: "SmartThings Labs", category: "SmartThings Labs",
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Partner/hue.png", iconUrl: "https://s3.amazonaws.com/smartapp-icons/Partner/hue.png",
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Partner/hue@2x.png", iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Partner/hue@2x.png",
singleInstance: true //singleInstance: true
) )
preferences { preferences {
@@ -68,7 +68,7 @@ def bridgeDiscovery(params=[:])
} }
//setup.xml request every 3 seconds except on discoveries //setup.xml request every 3 seconds except on discoveries
if(((bridgeRefreshCount % 3) == 0) && ((bridgeRefreshCount % 5) != 0)) { if(((bridgeRefreshCount % 1) == 0) && ((bridgeRefreshCount % 5) != 0)) {
verifyHueBridges() verifyHueBridges()
} }
@@ -175,7 +175,6 @@ private discoverHueBulbs() {
} }
private verifyHueBridge(String deviceNetworkId, String host) { private verifyHueBridge(String deviceNetworkId, String host) {
log.trace "Verify Hue Bridge $deviceNetworkId"
sendHubCommand(new physicalgraph.device.HubAction([ sendHubCommand(new physicalgraph.device.HubAction([
method: "GET", method: "GET",
path: "/description.xml", path: "/description.xml",
@@ -603,20 +602,6 @@ 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) { def on(childDevice) {
log.debug "Executing 'on'" log.debug "Executing 'on'"
put("lights/${getId(childDevice)}/state", [on: true]) put("lights/${getId(childDevice)}/state", [on: true])
@@ -657,53 +642,35 @@ def setColorTemperature(childDevice, huesettings) {
} }
def setColor(childDevice, huesettings) { def setColor(childDevice, huesettings) {
log.debug "Executing 'setColor($huesettings)'" log.debug "Executing 'setColor($huesettings)'"
def hue = Math.min(Math.round(huesettings.hue * 65535 / 100), 65535)
def value = [:] def sat = Math.min(Math.round(huesettings.saturation * 255 / 100), 255)
def hue = null def alert = huesettings.alert ? huesettings.alert : "none"
def sat = null def transition = huesettings.transition ? huesettings.transition : 4
def xy = null
if (huesettings.hex != null) {
value.xy = getHextoXY(huesettings.hex)
} else {
if (huesettings.hue != null)
value.hue = Math.min(Math.round(huesettings.hue * 65535 / 100), 65535)
if (huesettings.saturation != null)
value.sat = Math.min(Math.round(huesettings.saturation * 255 / 100), 255)
}
// Default behavior is to turn light on
value.on = true
if (huesettings.level != null) { def value = [sat: sat, hue: hue, alert: alert, transitiontime: transition]
if (huesettings.level <= 0) if (huesettings.level != null) {
value.on = false if (huesettings.level == 1) value.bri = 1 else value.bri = Math.min(Math.round(huesettings.level * 255 / 100), 255)
else if (huesettings.level == 1) value.on = value.bri > 0
value.bri = 1 }
else
value.bri = Math.min(Math.round(huesettings.level * 255 / 100), 255)
}
value.alert = huesettings.alert ? huesettings.alert : "none"
value.transition = huesettings.transition ? huesettings.transition : 4
// Make sure to turn off light if requested if (huesettings.switch) {
if (huesettings.switch == "off") value.on = huesettings.switch == "on"
value.on = false }
log.debug "sending command $value" log.debug "sending command $value"
put("lights/${getId(childDevice)}/state", value) put("lights/${getId(childDevice)}/state", value)
return "Color set to $value"
} }
def nextLevel(childDevice) { def nextLevel(childDevice) {
def level = device.latestValue("level") as Integer ?: 0 def level = device.latestValue("level") as Integer ?: 0
if (level < 100) { if (level < 100) {
level = Math.min(25 * (Math.round(level / 25) + 1), 100) as Integer level = Math.min(25 * (Math.round(level / 25) + 1), 100) as Integer
} else { }
level = 25 else {
} level = 25
setLevel(childDevice,level) }
setLevel(childDevice,level)
} }
private getId(childDevice) { private getId(childDevice) {
@@ -776,57 +743,6 @@ private getBridgeIP() {
return host return host
} }
private getHextoXY(String colorStr) {
// For the hue bulb the corners of the triangle are:
// -Red: 0.675, 0.322
// -Green: 0.4091, 0.518
// -Blue: 0.167, 0.04
def cred = Integer.valueOf( colorStr.substring( 1, 3 ), 16 )
def cgreen = Integer.valueOf( colorStr.substring( 3, 5 ), 16 )
def cblue = Integer.valueOf( colorStr.substring( 5, 7 ), 16 )
double[] normalizedToOne = new double[3];
normalizedToOne[0] = (cred / 255);
normalizedToOne[1] = (cgreen / 255);
normalizedToOne[2] = (cblue / 255);
float red, green, blue;
// Make red more vivid
if (normalizedToOne[0] > 0.04045) {
red = (float) Math.pow(
(normalizedToOne[0] + 0.055) / (1.0 + 0.055), 2.4);
} else {
red = (float) (normalizedToOne[0] / 12.92);
}
// Make green more vivid
if (normalizedToOne[1] > 0.04045) {
green = (float) Math.pow((normalizedToOne[1] + 0.055) / (1.0 + 0.055), 2.4);
} else {
green = (float) (normalizedToOne[1] / 12.92);
}
// Make blue more vivid
if (normalizedToOne[2] > 0.04045) {
blue = (float) Math.pow((normalizedToOne[2] + 0.055) / (1.0 + 0.055), 2.4);
} else {
blue = (float) (normalizedToOne[2] / 12.92);
}
float X = (float) (red * 0.649926 + green * 0.103455 + blue * 0.197109);
float Y = (float) (red * 0.234327 + green * 0.743075 + blue * 0.022598);
float Z = (float) (red * 0.0000000 + green * 0.053077 + blue * 1.035763);
float x = (X != 0 ? X / (X + Y + Z) : 0);
float y = (Y != 0 ? Y / (X + Y + Z) : 0);
double[] xy = new double[2];
xy[0] = x;
xy[1] = y;
return xy;
}
private Integer convertHexToInt(hex) { private Integer convertHexToInt(hex) {
Integer.parseInt(hex,16) Integer.parseInt(hex,16)
} }

View File

@@ -1,31 +1,31 @@
'''Acceleration Detected'''.ko=가속 감지되었을 때 '''Acceleration Detected'''.ko=가속 감지
'''Arrival Of'''.ko=도착했을 때 '''Arrival Of'''.ko=도착
'''Both Push and SMS?'''.ko=푸시 알람과 SMS 모두 사용 '''Both Push and SMS?'''.ko=푸시 메시지와 SMS 모두 사용하시겠습니까?
'''Button Pushed'''.ko=버튼이 눌렸을 때 '''Button Pushed'''.ko=버튼이 눌렸습니다
'''Contact Closes'''.ko=닫힘이 감지되었을 때 '''Contact Closes'''.ko=접점 닫힘
'''Contact Opens'''.ko=열림이 감지되었을 때 '''Contact Opens'''.ko=접점 열림
'''Departure Of'''.ko=출발할 때 '''Departure Of'''.ko=출발
'''Message Text'''.ko=문자 메시지 '''Message Text'''.ko=문자 메시지
'''Minutes'''.ko=메시지 전송 간격(분) '''Minutes'''.ko=
'''Motion Here'''.ko=움직임이 감지되었을 때 '''Motion Here'''.ko=동작
'''Phone Number (for SMS, optional)'''.ko=전화번호 (옵션) '''Phone Number (for SMS, optional)'''.ko=휴대전화 번호(문자 메시지 - 옵션)
'''Receive notifications when anything happens in your home.'''.ko=집 안에 무슨 일이 일어나면 알림이 전송됩니다. '''Receive notifications when anything happens in your home.'''.ko=집 안에 무슨 일이 일어나면 알림이 전송됩니다.
'''Smoke Detected'''.ko=연기가 감지되었을 때 '''Smoke Detected'''.ko=연기가 감지되었습니다
'''Switch Turned Off'''.ko=스위치졌을 때 '''Switch Turned Off'''.ko=스위치 꺼
'''Switch Turned On'''.ko=스위치가 켜졌을 때 '''Switch Turned On'''.ko=스위치 꺼짐
'''Choose one or more, when...'''.ko=다음 상황 중 하나 이상 선택 '''Choose one or more, when...'''.ko=다음의 경우 하나 이상 선택
'''Yes'''.ko= '''Yes'''.ko=
'''No'''.ko=아니요 '''No'''.ko=아니요
'''Send this message (optional, sends standard status message if not specified)'''.ko=메시지 작성 (작성하지 않 경우 디폴트 메시지 전송) '''Send this message (optional, sends standard status message if not specified)'''.ko=메시지 전송(선택적, 지정되지 않 경우 표준 상태 메시지를 보냅니다)
'''Via a push notification and/or an SMS message'''.ko=푸시 알 SMS 설정 '''Via a push notification and/or an SMS message'''.ko=푸시 알/또는 문자 메시지를 통해
'''Set for specific mode(s)'''.ko=특정 상태 설정 '''Set for specific mode(s)'''.ko=특정 모드 설정
'''Tap to set'''.ko=눌러서 설정 '''Tap to set'''.ko=눌러서 설정
'''Minimum time between messages (optional, defaults to every message)'''.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=미국 이외 국가에 거주한다면 국가 코드와 함께 입력하여 주세요. '''If outside the US please make sure to enter the proper country code'''.ko=미국 이외 거주자는 적절한 국가 코드를 입력했는지 확인하십시오
'''Water Sensor Wet'''.ko=누수가 감지되었을 때 '''Water Sensor Wet'''.ko=Water Sensor에서 물이 감지되었습니다
'''{{ triggerEvent.linkText }} has arrived at the {{ location.name }}'''.ko={{ triggerEvent.linkText }}님이 {{ location.name }}에 도착했습니다 '''{{ 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 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 the {{ location.name }}'''.ko={{ triggerEvent.linkText }}님이 {{ location.name }}을(를) 떠났습니다
'''{{ triggerEvent.linkText }} has left {{ location.name }}'''.ko={{ triggerEvent.linkText }}님이 {{ location.name }}을(를) 떠났습니다 '''{{ triggerEvent.linkText }} has left {{ location.name }}'''.ko={{ triggerEvent.linkText }}님이 {{ location.name }}을(를) 떠났습니다
'''Assign a name'''.ko=이름 '''Assign a name'''.ko=이름
'''Choose Modes'''.ko=상태 선택 '''Choose Modes'''.ko=모드 선택

View File

@@ -234,7 +234,7 @@ def addSwitches() {
def d def d
if (selectedSwitch) { if (selectedSwitch) {
d = getChildDevices()?.find { d = getChildDevices()?.find {
it.deviceNetworkId == selectedSwitch.value.mac || it.device.getDataValue("mac") == selectedSwitch.value.mac it.dni == selectedSwitch.value.mac || it.device.getDataValue("mac") == selectedSwitch.value.mac
} }
} }
@@ -265,7 +265,7 @@ def addMotions() {
def d def d
if (selectedMotion) { if (selectedMotion) {
d = getChildDevices()?.find { d = getChildDevices()?.find {
it.deviceNetworkId == selectedMotion.value.mac || it.device.getDataValue("mac") == selectedMotion.value.mac it.dni == selectedMotion.value.mac || it.device.getDataValue("mac") == selectedMotion.value.mac
} }
} }
@@ -296,7 +296,7 @@ def addLightSwitches() {
def d def d
if (selectedLightSwitch) { if (selectedLightSwitch) {
d = getChildDevices()?.find { d = getChildDevices()?.find {
it.deviceNetworkId == selectedLightSwitch.value.mac || it.device.getDataValue("mac") == selectedLightSwitch.value.mac it.dni == selectedLightSwitch.value.mac || it.device.getDataValue("mac") == selectedLightSwitch.value.mac
} }
} }

View File

@@ -1,17 +1,11 @@
/** /**
* Vacation Lighting Director * Vacation Lighting Director
* *
* Version 2.5 - Moved scheduling over to Cron and added time as a trigger. * Version 2.4 - Added information paragraphs
* Cleaned up formatting and some typos.
* Updated license.
* Made people option optional
* Added sttement to unschedule on mode change if people option is not selected
*
* Version 2.4 - Added information paragraphs
* *
* Source code can be found here: https://github.com/tslagle13/SmartThings/blob/master/smartapps/tslagle13/vacation-lighting-director.groovy * Source code can be found here: https://github.com/tslagle13/SmartThings/blob/master/smartapps/tslagle13/vacation-lighting-director.groovy
* *
* Copyright 2016 Tim Slagle * Copyright 2015 Tim Slagle
* *
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at: * in compliance with the License. You may obtain a copy of the License at:
@@ -57,7 +51,8 @@ def pageSetup() {
return dynamicPage(pageProperties) { return dynamicPage(pageProperties) {
section(""){ section(""){
paragraph "This app can be used to make your home seem occupied anytime you are away from your home. " + paragraph "This app can be used to make your home seem occupied anytime you are away from your home. " +
"Please use each of the the sections below to setup the different preferences to your liking. " "Please use each othe the sections below to setup the different preferences to your liking. " +
"I recommend this app be used with at least two away modes. An example would be 'Away Day' 'and Away Night'. "
} }
section("Setup Menu") { section("Setup Menu") {
href "Setup", title: "Setup", description: "", state:greyedOut() href "Setup", title: "Setup", description: "", state:greyedOut()
@@ -75,7 +70,7 @@ def Setup() {
def newMode = [ def newMode = [
name: "newMode", name: "newMode",
type: "mode", type: "mode",
title: "Modes", title: "Which?",
multiple: true, multiple: true,
required: true required: true
] ]
@@ -101,6 +96,14 @@ def Setup() {
required: true, required: true,
] ]
def people = [
name: "people",
type: "capability.presenceSensor",
title: "If these people are home do not change light status",
required: true,
multiple: true
]
def pageName = "Setup" def pageName = "Setup"
def pageProperties = [ def pageProperties = [
@@ -113,11 +116,10 @@ def Setup() {
section(""){ section(""){
paragraph "In this section you need to setup the deatils of how you want your lighting to be affected while " + paragraph "In this section you need to setup the deatils of how you want your lighting to be affected while " +
"you are away. All of these settings are required in order for the simulator to run correctly." paragraph "you are away. All of these settings are required in order for the simulator to run correctly."
} }
section("Simulator Triggers") { section("Which mode change triggers the simulator? (This app will only run in selected mode(s))") {
input newMode input newMode
href "timeIntervalInput", title: "Times", description: timeIntervalLabel(), refreshAfterSelection:true
} }
section("Light switches to turn on/off") { section("Light switches to turn on/off") {
input switches input switches
@@ -128,6 +130,9 @@ def Setup() {
section("Number of active lights at any given time") { section("Number of active lights at any given time") {
input number_of_active_lights input number_of_active_lights
} }
section("People") {
input people
}
} }
} }
@@ -157,29 +162,18 @@ def Settings() {
title: "Settings", title: "Settings",
nextPage: "pageSetup" nextPage: "pageSetup"
] ]
def people = [
name: "people",
type: "capability.presenceSensor",
title: "If these people are home do not change light status",
required: false,
multiple: true
]
return dynamicPage(pageProperties) { return dynamicPage(pageProperties) {
section(""){ section(""){
paragraph "In this section you can restrict how your simulator runs. For instance you can restrict on which days it will run " + paragraph "In this section you can restrict how your simulator runs. For instance you can restrict on which days it will run " +
"as well as a delay for the simulator to start after it is in the correct mode. Delaying the simulator helps with false starts based on a incorrect mode change." paragraph "as well as a delay for the simulator to start after it is in the correct mode. Delaying the simulator helps with false starts based on a incorrect mode change."
} }
section("Delay to start simulator") { section("Delay to start simulator") {
input falseAlarmThreshold input falseAlarmThreshold
} }
section("People") {
paragraph "Not using this setting may cause some lights to remain on when you arrive home"
input people
}
section("More options") { section("More options") {
href "timeIntervalInput", title: "Only during a certain time", description: getTimeLabel(starting, ending), state: greyedOutTime(starting, ending), refreshAfterSelection:true
input days input days
} }
} }
@@ -187,24 +181,9 @@ def Settings() {
page(name: "timeIntervalInput", title: "Only during a certain time", refreshAfterSelection:true) { page(name: "timeIntervalInput", title: "Only during a certain time", refreshAfterSelection:true) {
section { section {
input "startTimeType", "enum", title: "Starting at", options: [["time": "A specific time"], ["sunrise": "Sunrise"], ["sunset": "Sunset"]], defaultValue: "time", submitOnChange: true input "starting", "time", title: "Starting", required: false
if (startTimeType in ["sunrise","sunset"]) { input "ending", "time", title: "Ending", required: false
input "startTimeOffset", "number", title: "Offset in minutes (+/-)", range: "*..*", required: false
}
else {
input "starting", "time", title: "Start time", required: false
}
} }
section {
input "endTimeType", "enum", title: "Ending at", options: [["time": "A specific time"], ["sunrise": "Sunrise"], ["sunset": "Sunset"]], defaultValue: "time", submitOnChange: true
if (endTimeType in ["sunrise","sunset"]) {
input "endTimeOffset", "number", title: "Offset in minutes (+/-)", range: "*..*", required: false
}
else {
input "ending", "time", title: "End time", required: false
}
}
} }
def installed() { def installed() {
@@ -222,13 +201,10 @@ def initialize(){
if (newMode != null) { if (newMode != null) {
subscribe(location, modeChangeHandler) subscribe(location, modeChangeHandler)
} }
if (starting != null) {
schedule(starting, modeChangeHandler)
}
log.debug "Installed with settings: ${settings}"
} }
def modeChangeHandler(evt) { def modeChangeHandler(evt) {
log.debug "Mode change to: ${evt.value}"
def delay = (falseAlarmThreshold != null && falseAlarmThreshold != "") ? falseAlarmThreshold * 60 : 2 * 60 def delay = (falseAlarmThreshold != null && falseAlarmThreshold != "") ? falseAlarmThreshold * 60 : 2 * 60
runIn(delay, scheduleCheck) runIn(delay, scheduleCheck)
} }
@@ -236,54 +212,48 @@ def modeChangeHandler(evt) {
//Main logic to pick a random set of lights from the large set of lights to turn on and then turn the rest off //Main logic to pick a random set of lights from the large set of lights to turn on and then turn the rest off
def scheduleCheck(evt) { def scheduleCheck(evt) {
if(allOk){ if(allOk){
log.debug("Running") log.debug("Running")
// turn off all the switches // turn off all the switches
switches.off() switches.off()
// grab a random switch // grab a random switch
def random = new Random() def random = new Random()
def inactive_switches = switches def inactive_switches = switches
for (int i = 0 ; i < number_of_active_lights ; i++) { for (int i = 0 ; i < number_of_active_lights ; i++) {
// if there are no inactive switches to turn on then let's break // if there are no inactive switches to turn on then let's break
if (inactive_switches.size() == 0){ if (inactive_switches.size() == 0){
break break
}
// grab a random switch and turn it on
def random_int = random.nextInt(inactive_switches.size())
inactive_switches[random_int].on()
// then remove that switch from the pool off switches that can be turned on
inactive_switches.remove(random_int)
}
// re-run again when the frequency demands it
schedule("0 0/${frequency_minutes} * 1/1 * ? *", scheduleCheck)
} }
//Check to see if mode is ok but not time/day. If mode is still ok, check again after frequency period.
else if (modeOk) { // grab a random switch and turn it on
log.debug("mode OK. Running again") def random_int = random.nextInt(inactive_switches.size())
switches.off() inactive_switches[random_int].on()
}
//if none is ok turn off frequency check and turn off lights. // then remove that switch from the pool off switches that can be turned on
else { inactive_switches.remove(random_int)
if(people){ }
//don't turn off lights if anyone is home
if(someoneIsHome()){ // re-run again when the frequency demands it
log.debug("Stopping Check for Light") runIn(frequency_minutes * 60, scheduleCheck)
unschedule() }
} //Check to see if mode is ok but not time/day. If mode is still ok, check again after frequency period.
else{ else if (modeOk) {
log.debug("Stopping Check for Light and turning off all lights") log.debug("mode OK. Running again")
switches.off() runIn(frequency_minutes * 60, scheduleCheck)
unschedule() switches.off()
} }
//if none is ok turn off frequency check and turn off lights.
else if(people){
//don't turn off lights if anyone is home
if(someoneIsHome()){
log.debug("Stopping Check for Light")
} }
else if (!modeOk) { else{
unschedule() log.debug("Stopping Check for Light and turning off all lights")
} switches.off()
} }
}
} }
@@ -316,6 +286,26 @@ private getDaysOk() {
result result
} }
private getTimeOk() {
def result = true
if (starting && ending) {
def currTime = now()
def start = timeToday(starting).time
def stop = timeToday(ending).time
result = start < stop ? currTime >= start && currTime <= stop : currTime <= stop || currTime >= start
}
else if (starting){
result = currTime >= start
}
else if (ending){
result = currTime <= stop
}
log.trace "timeOk = $result"
result
}
private getHomeIsEmpty() { private getHomeIsEmpty() {
def result = true def result = true
@@ -340,59 +330,25 @@ private getSomeoneIsHome() {
return result return result
} }
private getTimeOk() {
def result = true //gets the label for time restriction. Label phrasing changes depending on if there is both start and stop times or just one start/stop time.
def start = timeWindowStart() def getTimeLabel(starting, ending){
def stop = timeWindowStop()
if (start && stop && location.timeZone) { def timeLabel = "Tap to set"
result = timeOfDayIsBetween(start, stop, new Date(), location.timeZone)
} if(starting && ending){
log.trace "timeOk = $result" timeLabel = "Between" + " " + hhmm(starting) + " " + "and" + " " + hhmm(ending)
result }
} else if (starting) {
timeLabel = "Start at" + " " + hhmm(starting)
private timeWindowStart() { }
def result = null else if(ending){
if (startTimeType == "sunrise") { timeLabel = "End at" + hhmm(ending)
result = location.currentState("sunriseTime")?.dateValue }
if (result && startTimeOffset) { timeLabel
result = new Date(result.time + Math.round(startTimeOffset * 60000))
}
}
else if (startTimeType == "sunset") {
result = location.currentState("sunsetTime")?.dateValue
if (result && startTimeOffset) {
result = new Date(result.time + Math.round(startTimeOffset * 60000))
}
}
else if (starting && location.timeZone) {
result = timeToday(starting, location.timeZone)
}
log.trace "timeWindowStart = ${result}"
result
}
private timeWindowStop() {
def result = null
if (endTimeType == "sunrise") {
result = location.currentState("sunriseTime")?.dateValue
if (result && endTimeOffset) {
result = new Date(result.time + Math.round(endTimeOffset * 60000))
}
}
else if (endTimeType == "sunset") {
result = location.currentState("sunsetTime")?.dateValue
if (result && endTimeOffset) {
result = new Date(result.time + Math.round(endTimeOffset * 60000))
}
}
else if (ending && location.timeZone) {
result = timeToday(ending, location.timeZone)
}
log.trace "timeWindowStop = ${result}"
result
} }
//fomrats time to readable format for time label
private hhmm(time, fmt = "h:mm a") private hhmm(time, fmt = "h:mm a")
{ {
def t = timeToday(time, location.timeZone) def t = timeToday(time, location.timeZone)
@@ -401,41 +357,6 @@ private hhmm(time, fmt = "h:mm a")
f.format(t) f.format(t)
} }
private timeIntervalLabel() {
def start = ""
switch (startTimeType) {
case "time":
if (ending) {
start += hhmm(starting)
}
break
case "sunrise":
case "sunset":
start += startTimeType[0].toUpperCase() + startTimeType[1..-1]
if (startTimeOffset) {
start += startTimeOffset > 0 ? "+${startTimeOffset} min" : "${startTimeOffset} min"
}
break
}
def finish = ""
switch (endTimeType) {
case "time":
if (ending) {
finish += hhmm(ending)
}
break
case "sunrise":
case "sunset":
finish += endTimeType[0].toUpperCase() + endTimeType[1..-1]
if (endTimeOffset) {
finish += endTimeOffset > 0 ? "+${endTimeOffset} min" : "${endTimeOffset} min"
}
break
}
start && finish ? "${start} to ${finish}" : ""
}
//sets complete/not complete for the setup section on the main dynamic page //sets complete/not complete for the setup section on the main dynamic page
def greyedOut(){ def greyedOut(){
def result = "" def result = ""
@@ -448,7 +369,16 @@ def greyedOut(){
//sets complete/not complete for the settings section on the main dynamic page //sets complete/not complete for the settings section on the main dynamic page
def greyedOutSettings(){ def greyedOutSettings(){
def result = "" def result = ""
if (people || days || falseAlarmThreshold ) { if (starting || ending || days || falseAlarmThreshold) {
result = "complete"
}
result
}
//sets complete/not complete for time restriction section in settings
def greyedOutTime(starting, ending){
def result = ""
if (starting || ending) {
result = "complete" result = "complete"
} }
result result